From 772355dce39cbe143ecc26897b72285bd80a0b2f Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Thu, 30 Jan 2025 15:39:59 -0500 Subject: [PATCH 01/14] remove quantity dropdown from nlp addons and fix styling --- jsapp/js/account/addOns/addOnList.component.tsx | 6 +----- jsapp/js/account/addOns/addOnList.module.scss | 4 ---- .../js/account/addOns/oneTimeAddOnRow.component.tsx | 12 ++---------- jsapp/js/account/security/securityRoute.module.scss | 1 + 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/jsapp/js/account/addOns/addOnList.component.tsx b/jsapp/js/account/addOns/addOnList.component.tsx index b3fe648ef9..0955edf4dc 100644 --- a/jsapp/js/account/addOns/addOnList.component.tsx +++ b/jsapp/js/account/addOns/addOnList.component.tsx @@ -125,16 +125,12 @@ const AddOnList = (props: { - {t('##name## x ##quantity##') + {t('##name##') .replace( '##name##', oneTimeAddOnProducts.find( (product) => product.id === oneTimeAddOn.product )?.name || label - ) - .replace( - '##quantity##', - oneTimeAddOn.quantity.toString() )} diff --git a/jsapp/js/account/addOns/addOnList.module.scss b/jsapp/js/account/addOns/addOnList.module.scss index 78a8d43303..f4811eb3e2 100644 --- a/jsapp/js/account/addOns/addOnList.module.scss +++ b/jsapp/js/account/addOns/addOnList.module.scss @@ -149,10 +149,6 @@ .oneTime { justify-content: center; - - :last-child { - margin-left: 8px; - } } .productName { diff --git a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx index 05c19d9735..a3deb5a341 100644 --- a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx +++ b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx @@ -170,7 +170,7 @@ export const OneTimeAddOnRow = ({
{ return {value: product.id, label: product.name}; @@ -186,15 +186,7 @@ export const OneTimeAddOnRow = ({ onChange={onChangePrice} value={selectedPrice.id} /> - ) : ( - - )} + ) : null}
diff --git a/jsapp/js/account/security/securityRoute.module.scss b/jsapp/js/account/security/securityRoute.module.scss index 0f5e482d3a..c424957834 100644 --- a/jsapp/js/account/security/securityRoute.module.scss +++ b/jsapp/js/account/security/securityRoute.module.scss @@ -37,6 +37,7 @@ h2.securityHeaderText { .securityHeaderActions { @include mixins.centerRowFlex; + padding-right: 15px; } // Shared styles for sections From 7c45b8a4124487f190aa97ea34e8ddce5992ad5b Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Thu, 30 Jan 2025 17:20:21 -0500 Subject: [PATCH 02/14] add padding to dropdown --- jsapp/js/components/special/koboAccessibleSelect.module.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jsapp/js/components/special/koboAccessibleSelect.module.scss b/jsapp/js/components/special/koboAccessibleSelect.module.scss index 406f5cd487..a405e71ab5 100644 --- a/jsapp/js/components/special/koboAccessibleSelect.module.scss +++ b/jsapp/js/components/special/koboAccessibleSelect.module.scss @@ -14,6 +14,8 @@ $input-color: colors.$kobo-gray-800; width: 100%; position: relative; font-size: 12px; + padding-left: 5px; + padding-right: 5px; } .m { From 4ea005c0a9520ddc9cfd9030b412a980a07fdb9e Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Mon, 3 Feb 2025 15:08:09 -0500 Subject: [PATCH 03/14] remove styling fixes and fix style for rendering nlp add-on dropdown --- jsapp/js/account/addOns/addOnList.module.scss | 4 ++++ jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx | 4 ++-- jsapp/js/account/security/securityRoute.module.scss | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jsapp/js/account/addOns/addOnList.module.scss b/jsapp/js/account/addOns/addOnList.module.scss index f4811eb3e2..78a8d43303 100644 --- a/jsapp/js/account/addOns/addOnList.module.scss +++ b/jsapp/js/account/addOns/addOnList.module.scss @@ -149,6 +149,10 @@ .oneTime { justify-content: center; + + :last-child { + margin-left: 8px; + } } .productName { diff --git a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx index a3deb5a341..48f895d9ac 100644 --- a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx +++ b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx @@ -178,7 +178,7 @@ export const OneTimeAddOnRow = ({ onChange={(productId) => onChangeProduct(productId as string)} value={selectedProduct.id} /> - {displayName === 'File Storage' ? ( + {displayName === 'File Storage' && ( - ) : null} + )} diff --git a/jsapp/js/account/security/securityRoute.module.scss b/jsapp/js/account/security/securityRoute.module.scss index c424957834..0f5e482d3a 100644 --- a/jsapp/js/account/security/securityRoute.module.scss +++ b/jsapp/js/account/security/securityRoute.module.scss @@ -37,7 +37,6 @@ h2.securityHeaderText { .securityHeaderActions { @include mixins.centerRowFlex; - padding-right: 15px; } // Shared styles for sections From d051bcfbe36417928ab11fc3e2a7a332788cbaf9 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Tue, 4 Feb 2025 17:39:18 -0500 Subject: [PATCH 04/14] remove quantity from addons (backend) and adjust tests --- .../js/account/addOns/addOnList.component.tsx | 21 +++++------ .../addOns/oneTimeAddOnRow.component.tsx | 21 ++--------- jsapp/js/account/stripe.types.ts | 1 - .../oneTimeAddOnList.component.tsx | 6 ---- kobo/apps/stripe/admin.py | 1 - ...0002_remove_planaddon_quantity_and_more.py | 28 +++++++++++++++ kobo/apps/stripe/models.py | 13 +++---- kobo/apps/stripe/serializers.py | 1 - .../stripe/tests/test_customer_portal_api.py | 2 +- .../stripe/tests/test_one_time_addons_api.py | 35 ++++--------------- kobo/apps/trackers/tests/test_utils.py | 7 ++-- 11 files changed, 54 insertions(+), 82 deletions(-) create mode 100644 kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py diff --git a/jsapp/js/account/addOns/addOnList.component.tsx b/jsapp/js/account/addOns/addOnList.component.tsx index 0955edf4dc..833cf6f80e 100644 --- a/jsapp/js/account/addOns/addOnList.component.tsx +++ b/jsapp/js/account/addOns/addOnList.component.tsx @@ -125,13 +125,12 @@ const AddOnList = (props: { - {t('##name##') - .replace( - '##name##', - oneTimeAddOnProducts.find( - (product) => product.id === oneTimeAddOn.product - )?.name || label - )} + {t('##name##').replace( + '##name##', + oneTimeAddOnProducts.find( + (product) => product.id === oneTimeAddOn.product + )?.name || label + )}

@@ -145,11 +144,9 @@ const AddOnList = (props: { {'$##price##'.replace( '##price##', ( - (oneTimeAddOn.quantity * - (oneTimeAddOnProducts.find( - (product) => product.id === oneTimeAddOn.product - )?.prices[0].unit_amount || 0)) / - 100 + (oneTimeAddOnProducts.find( + (product) => product.id === oneTimeAddOn.product + )?.prices[0].unit_amount || 0) / 100 ).toFixed(2) )} diff --git a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx index 48f895d9ac..a0cf5c3f81 100644 --- a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx +++ b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx @@ -20,16 +20,6 @@ interface OneTimeAddOnRowProps { organization: Organization; } -const MAX_ONE_TIME_ADDON_PURCHASE_QUANTITY = 10; - -const quantityOptions = Array.from( - {length: MAX_ONE_TIME_ADDON_PURCHASE_QUANTITY}, - (_, zeroBasedIndex) => { - const index = (zeroBasedIndex + 1).toString(); - return {value: index, label: index}; - } -); - export const OneTimeAddOnRow = ({ products, isBusy, @@ -39,11 +29,10 @@ export const OneTimeAddOnRow = ({ organization, }: OneTimeAddOnRowProps) => { const [selectedProduct, setSelectedProduct] = useState(products[0]); - const [quantity, setQuantity] = useState('1'); const [selectedPrice, setSelectedPrice] = useState( selectedProduct.prices[0] ); - const displayPrice = useDisplayPrice(selectedPrice, parseInt(quantity)); + const displayPrice = useDisplayPrice(selectedPrice); const priceOptions = useMemo( () => selectedProduct.prices.map((price) => { @@ -98,12 +87,6 @@ export const OneTimeAddOnRow = ({ } }; - const onChangeQuantity = (quantity: string | null) => { - if (quantity) { - setQuantity(quantity); - } - }; - // TODO: Merge functionality of onClickBuy and onClickManage so we can unduplicate // the billing button in priceTableCells const onClickBuy = () => { @@ -112,7 +95,7 @@ export const OneTimeAddOnRow = ({ } setIsBusy(true); if (selectedPrice) { - postCheckout(selectedPrice.id, organization.id, parseInt(quantity)) + postCheckout(selectedPrice.id, organization.id) .then((response) => window.location.assign(response.url)) .catch(() => setIsBusy(false)); } diff --git a/jsapp/js/account/stripe.types.ts b/jsapp/js/account/stripe.types.ts index fb3381cdcd..6e4a908650 100644 --- a/jsapp/js/account/stripe.types.ts +++ b/jsapp/js/account/stripe.types.ts @@ -214,7 +214,6 @@ export interface OneTimeAddOn { limits_remaining: Partial; organization: string; product: string; - quantity: number; } export interface OneTimeUsageLimits { diff --git a/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx b/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx index c19dd7981e..c82c61fe6e 100644 --- a/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx +++ b/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx @@ -37,7 +37,6 @@ function OneTimeAddOnList(props: OneTimeAddOnList) { return { productName, remainingLimit, - quantity: addon.quantity, }; }); }, [props.oneTimeAddOns, props.type, productsContext.isLoaded]); @@ -48,11 +47,6 @@ function OneTimeAddOnList(props: OneTimeAddOnList) {

{t('##REMAINING## remaining').replace( diff --git a/kobo/apps/stripe/admin.py b/kobo/apps/stripe/admin.py index b00096b966..ac43d74910 100644 --- a/kobo/apps/stripe/admin.py +++ b/kobo/apps/stripe/admin.py @@ -11,7 +11,6 @@ class PlanAddOnAdmin(ModelAdmin): list_display = ( 'organization', 'product', - 'quantity', 'is_available', 'created', ) diff --git a/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py new file mode 100644 index 0000000000..ce410fdb3c --- /dev/null +++ b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.15 on 2025-02-04 21:15 + +from django.db import migrations, models +import kobo.apps.stripe.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('stripe', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='planaddon', + name='quantity', + ), + migrations.AlterField( + model_name='planaddon', + name='limits_remaining', + field=models.JSONField(default=kobo.apps.stripe.utils.get_default_add_on_limits, help_text="The amount of each of the add-on's individual limits left to use."), + ), + migrations.AlterField( + model_name='planaddon', + name='usage_limits', + field=models.JSONField(default=kobo.apps.stripe.utils.get_default_add_on_limits, help_text='The historical usage limits when the add-on was purchased.\n Possible keys:\n "submission_limit", "asr_seconds_limit", and/or "mt_characters_limit"'), + ), + ] diff --git a/kobo/apps/stripe/models.py b/kobo/apps/stripe/models.py index 66073893e4..f2898e33e1 100644 --- a/kobo/apps/stripe/models.py +++ b/kobo/apps/stripe/models.py @@ -29,11 +29,9 @@ class PlanAddOn(models.Model): null=True, blank=True, ) - quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)]) usage_limits = models.JSONField( default=get_default_add_on_limits, help_text='''The historical usage limits when the add-on was purchased. - Multiply this value by `quantity` to get the total limits for this add-on. Possible keys: "submission_limit", "asr_seconds_limit", and/or "mt_characters_limit"''', ) @@ -70,7 +68,6 @@ def create_or_update_one_time_add_on(charge: Charge): if ( charge.payment_intent.status != PaymentIntentStatus.succeeded or not charge.metadata.get('price_id', None) - or not charge.metadata.get('quantity', None) ): # make sure the charge is for a successful addon purchase return False @@ -101,14 +98,13 @@ def create_or_update_one_time_add_on(charge: Charge): # this user doesn't have the subscription level they need for this addon return False - quantity = int(charge.metadata['quantity']) usage_limits = {} limits_remaining = {} for limit_type in get_default_add_on_limits().keys(): limit_value = charge.metadata.get(limit_type, None) if limit_value is not None: usage_limits[limit_type] = int(limit_value) - limits_remaining[limit_type] = int(limit_value) * quantity + limits_remaining[limit_type] = int(limit_value) if not len(usage_limits): # not a valid plan add-on @@ -119,7 +115,6 @@ def create_or_update_one_time_add_on(charge: Charge): ) if add_on_created: add_on.product = product - add_on.quantity = int(charge.metadata['quantity']) add_on.organization = organization add_on.usage_limits = usage_limits add_on.limits_remaining = limits_remaining @@ -145,7 +140,7 @@ def get_organization_totals( charge__refunded=False, ).aggregate( total_usage_limit=Coalesce( - Sum(Cast(usage_field, output_field=IntegerField()) * F('quantity')), + Sum(Cast(usage_field, output_field=IntegerField())), 0, output_field=IntegerField(), ), @@ -234,9 +229,9 @@ def deduct_add_ons_for_organization( def total_usage_limits(self): """ The total usage limits for this add-on, based on the usage_limits for a single - add-on and the quantity. + add-on. """ - return {key: value * self.quantity for key, value in self.usage_limits.items()} + return {key: value for key, value in self.usage_limits.items()} @property def valid_tags(self) -> List: diff --git a/kobo/apps/stripe/serializers.py b/kobo/apps/stripe/serializers.py index 422511b02f..ab9e05e0c3 100644 --- a/kobo/apps/stripe/serializers.py +++ b/kobo/apps/stripe/serializers.py @@ -18,7 +18,6 @@ class Meta: 'id', 'created', 'is_available', - 'quantity', 'usage_limits', 'total_usage_limits', 'limits_remaining', diff --git a/kobo/apps/stripe/tests/test_customer_portal_api.py b/kobo/apps/stripe/tests/test_customer_portal_api.py index 00763e7d2e..c3757bba2f 100644 --- a/kobo/apps/stripe/tests/test_customer_portal_api.py +++ b/kobo/apps/stripe/tests/test_customer_portal_api.py @@ -98,7 +98,7 @@ def test_gets_portal_configuration_for_price(self, create_config, list_config, s 'livemode': False, 'features': { 'subscription_update': { - 'default_allowed_updates': ['quantity'], + 'default_allowed_updates': [], 'products': [], 'prices': [], }, diff --git a/kobo/apps/stripe/tests/test_one_time_addons_api.py b/kobo/apps/stripe/tests/test_one_time_addons_api.py index 7c699d1ebb..fb1d679dae 100644 --- a/kobo/apps/stripe/tests/test_one_time_addons_api.py +++ b/kobo/apps/stripe/tests/test_one_time_addons_api.py @@ -54,8 +54,8 @@ def _create_product(self, metadata=None): ) self.product.save() - def _create_payment(self, payment_status='succeeded', refunded=False, quantity=1): - payment_total = quantity * 2000 + def _create_payment(self, payment_status='succeeded', refunded=False): + payment_total = 2000 self.payment_intent = baker.make( PaymentIntent, customer=self.customer, @@ -81,7 +81,6 @@ def _create_payment(self, payment_status='succeeded', refunded=False, quantity=1 self.charge.metadata = { 'price_id': self.price.id, 'organization_id': self.organization.id, - 'quantity': quantity, **(self.product.metadata or {}), } self.charge.save() @@ -144,25 +143,6 @@ def test_no_addon_for_cancelled_charge(self): assert response.status_code == status.HTTP_200_OK assert response.data['count'] == 0 - def test_total_limits_reflect_addon_quantity(self): - limit = 2000 - quantity = 9 - self._create_product( - metadata={ - 'product_type': 'addon_onetime', - 'asr_seconds_limit': limit, - 'valid_tags': 'all', - } - ) - self._create_payment(quantity=quantity) - response = self.client.get(self.url) - assert response.status_code == status.HTTP_200_OK - assert response.data['count'] == 1 - asr_seconds = response.data['results'][0]['total_usage_limits'][ - 'asr_seconds_limit' - ] - assert asr_seconds == limit * quantity - def test_anonymous_user(self): self._create_product() self._create_payment() @@ -181,7 +161,6 @@ def test_not_own_addon(self): @data('characters', 'seconds') def test_get_user_totals(self, usage_type): limit = 2000 - quantity = 5 usage_limit_key = f'{USAGE_LIMIT_MAP[usage_type]}_limit' self._create_product( metadata={ @@ -192,19 +171,19 @@ def test_get_user_totals(self, usage_type): ) self._create_payment() self._create_payment() - self._create_payment(quantity=quantity) + self._create_payment() total_limit, remaining = PlanAddOn.get_organization_totals( self.organization, usage_type ) - assert total_limit == limit * (quantity + 2) - assert remaining == limit * (quantity + 2) + assert total_limit == limit * 3 + assert remaining == limit * 3 PlanAddOn.deduct_add_ons_for_organization( - self.organization, usage_type, limit * quantity + self.organization, usage_type, limit ) total_limit, remaining = PlanAddOn.get_organization_totals( self.organization, usage_type ) - assert total_limit == limit * (quantity + 2) + assert total_limit == limit * 3 assert remaining == limit * 2 diff --git a/kobo/apps/trackers/tests/test_utils.py b/kobo/apps/trackers/tests/test_utils.py index 17d020a2af..3abe566ba2 100644 --- a/kobo/apps/trackers/tests/test_utils.py +++ b/kobo/apps/trackers/tests/test_utils.py @@ -53,9 +53,8 @@ def _make_payment( price, customer, payment_status='succeeded', - quantity=1, ): - payment_total = quantity * 2000 + payment_total = 2000 payment_intent = baker.make( PaymentIntent, customer=customer, @@ -81,7 +80,6 @@ def _make_payment( charge.metadata = { 'price_id': price.id, 'organization_id': self.organization.id, - 'quantity': quantity, **(price.product.metadata or {}), } charge.save() @@ -104,7 +102,8 @@ def test_organization_usage_utils(self, usage_type): 'valid_tags': 'all', } product, price = self._create_product(addon_metadata) - self._make_payment(price, subscription.customer, quantity=2) + self._make_payment(price, subscription.customer) + self._make_payment(price, subscription.customer) total_limit = 2000 * 2 + 1000 remaining = get_organization_remaining_usage(self.organization, usage_type) From 65c2ed4bbd1ec5ebba3d1f0f9a8c27b30be61093 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Wed, 5 Feb 2025 09:37:58 -0500 Subject: [PATCH 05/14] formatting --- .../0002_remove_planaddon_quantity_and_more.py | 10 ++++++++-- kobo/apps/stripe/models.py | 3 +-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py index ce410fdb3c..effea26e76 100644 --- a/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py +++ b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py @@ -18,11 +18,17 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='planaddon', name='limits_remaining', - field=models.JSONField(default=kobo.apps.stripe.utils.get_default_add_on_limits, help_text="The amount of each of the add-on's individual limits left to use."), + field=models.JSONField( + default=kobo.apps.stripe.utils.get_default_add_on_limits, + help_text="The amount of each of the add-on's individual limits left to use.", + ), ), migrations.AlterField( model_name='planaddon', name='usage_limits', - field=models.JSONField(default=kobo.apps.stripe.utils.get_default_add_on_limits, help_text='The historical usage limits when the add-on was purchased.\n Possible keys:\n "submission_limit", "asr_seconds_limit", and/or "mt_characters_limit"'), + field=models.JSONField( + default=kobo.apps.stripe.utils.get_default_add_on_limits, + help_text='The historical usage limits when the add-on was purchased.\n Possible keys:\n "submission_limit", "asr_seconds_limit", and/or "mt_characters_limit"', + ), ), ] diff --git a/kobo/apps/stripe/models.py b/kobo/apps/stripe/models.py index f2898e33e1..de0cadce62 100644 --- a/kobo/apps/stripe/models.py +++ b/kobo/apps/stripe/models.py @@ -2,9 +2,8 @@ from django.contrib import admin from django.core.exceptions import ObjectDoesNotExist -from django.core.validators import MinValueValidator from django.db import models -from django.db.models import F, IntegerField, Sum +from django.db.models import IntegerField, Sum from django.db.models.functions import Cast, Coalesce from django.db.models.signals import post_save from django.dispatch import receiver From a5e5aeffb82c50fcfabf212c9d2a718a65b56746 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Wed, 5 Feb 2025 10:00:37 -0500 Subject: [PATCH 06/14] fix formatting --- .../0002_remove_planaddon_quantity_and_more.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py index effea26e76..6440a0b26c 100644 --- a/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py +++ b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py @@ -20,7 +20,10 @@ class Migration(migrations.Migration): name='limits_remaining', field=models.JSONField( default=kobo.apps.stripe.utils.get_default_add_on_limits, - help_text="The amount of each of the add-on's individual limits left to use.", + help_text=( + "The amount of each of the add-on's individual limits " + "left to use." + ), ), ), migrations.AlterField( @@ -28,7 +31,13 @@ class Migration(migrations.Migration): name='usage_limits', field=models.JSONField( default=kobo.apps.stripe.utils.get_default_add_on_limits, - help_text='The historical usage limits when the add-on was purchased.\n Possible keys:\n "submission_limit", "asr_seconds_limit", and/or "mt_characters_limit"', + help_text=( + "The historical usage limits when the add-on was purchased.\n" + "Possible keys:\n" + "\"submission_limit\", \"asr_seconds_limit\", " + "and/or \"mt_characters_limit\"" + ), + ), ), ] From 1092a617a8b5a40eaa766693ecccb25d81f995d1 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Wed, 5 Feb 2025 10:56:54 -0500 Subject: [PATCH 07/14] fix formatting --- .../0002_remove_planaddon_quantity_and_more.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py index 6440a0b26c..2f87baa74f 100644 --- a/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py +++ b/kobo/apps/stripe/migrations/0002_remove_planaddon_quantity_and_more.py @@ -22,7 +22,7 @@ class Migration(migrations.Migration): default=kobo.apps.stripe.utils.get_default_add_on_limits, help_text=( "The amount of each of the add-on's individual limits " - "left to use." + 'left to use.' ), ), ), @@ -32,10 +32,10 @@ class Migration(migrations.Migration): field=models.JSONField( default=kobo.apps.stripe.utils.get_default_add_on_limits, help_text=( - "The historical usage limits when the add-on was purchased.\n" - "Possible keys:\n" - "\"submission_limit\", \"asr_seconds_limit\", " - "and/or \"mt_characters_limit\"" + 'The historical usage limits when the add-on was purchased.\n' + 'Possible keys:\n' + '\"submission_limit\", \"asr_seconds_limit\", ' + 'and/or \"mt_characters_limit\"' ), ), From fd48c39ea3936e5284d0bcea8fbc960989b2cb28 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Thu, 30 Jan 2025 15:39:59 -0500 Subject: [PATCH 08/14] remove quantity dropdown from nlp addons and fix styling --- jsapp/js/account/addOns/addOnList.component.tsx | 6 +----- .../js/account/addOns/oneTimeAddOnRow.component.tsx | 12 ++---------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/jsapp/js/account/addOns/addOnList.component.tsx b/jsapp/js/account/addOns/addOnList.component.tsx index b3fe648ef9..0955edf4dc 100644 --- a/jsapp/js/account/addOns/addOnList.component.tsx +++ b/jsapp/js/account/addOns/addOnList.component.tsx @@ -125,16 +125,12 @@ const AddOnList = (props: { - {t('##name## x ##quantity##') + {t('##name##') .replace( '##name##', oneTimeAddOnProducts.find( (product) => product.id === oneTimeAddOn.product )?.name || label - ) - .replace( - '##quantity##', - oneTimeAddOn.quantity.toString() )} diff --git a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx index 05c19d9735..a3deb5a341 100644 --- a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx +++ b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx @@ -170,7 +170,7 @@ export const OneTimeAddOnRow = ({
{ return {value: product.id, label: product.name}; @@ -186,15 +186,7 @@ export const OneTimeAddOnRow = ({ onChange={onChangePrice} value={selectedPrice.id} /> - ) : ( - - )} + ) : null}
From 44db6393fc5f3fb2cdec69a15f2c36569f00bfd9 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Thu, 30 Jan 2025 17:20:21 -0500 Subject: [PATCH 09/14] add padding to dropdown --- jsapp/js/components/special/koboAccessibleSelect.module.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jsapp/js/components/special/koboAccessibleSelect.module.scss b/jsapp/js/components/special/koboAccessibleSelect.module.scss index 406f5cd487..a405e71ab5 100644 --- a/jsapp/js/components/special/koboAccessibleSelect.module.scss +++ b/jsapp/js/components/special/koboAccessibleSelect.module.scss @@ -14,6 +14,8 @@ $input-color: colors.$kobo-gray-800; width: 100%; position: relative; font-size: 12px; + padding-left: 5px; + padding-right: 5px; } .m { From 5499af9ac24d62986f6010ddb212a2df81e945cb Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Mon, 3 Feb 2025 15:08:09 -0500 Subject: [PATCH 10/14] remove styling fixes and fix style for rendering nlp add-on dropdown --- jsapp/js/account/addOns/addOnList.module.scss | 4 ++++ jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx | 4 ++-- jsapp/js/account/security/securityRoute.module.scss | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jsapp/js/account/addOns/addOnList.module.scss b/jsapp/js/account/addOns/addOnList.module.scss index f4811eb3e2..78a8d43303 100644 --- a/jsapp/js/account/addOns/addOnList.module.scss +++ b/jsapp/js/account/addOns/addOnList.module.scss @@ -149,6 +149,10 @@ .oneTime { justify-content: center; + + :last-child { + margin-left: 8px; + } } .productName { diff --git a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx index a3deb5a341..48f895d9ac 100644 --- a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx +++ b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx @@ -178,7 +178,7 @@ export const OneTimeAddOnRow = ({ onChange={(productId) => onChangeProduct(productId as string)} value={selectedProduct.id} /> - {displayName === 'File Storage' ? ( + {displayName === 'File Storage' && ( - ) : null} + )}
diff --git a/jsapp/js/account/security/securityRoute.module.scss b/jsapp/js/account/security/securityRoute.module.scss index c424957834..0f5e482d3a 100644 --- a/jsapp/js/account/security/securityRoute.module.scss +++ b/jsapp/js/account/security/securityRoute.module.scss @@ -37,7 +37,6 @@ h2.securityHeaderText { .securityHeaderActions { @include mixins.centerRowFlex; - padding-right: 15px; } // Shared styles for sections From a9c18393f35d13a55cf47ffebfad8fc99fb90294 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Wed, 5 Feb 2025 11:17:43 -0500 Subject: [PATCH 11/14] remove quantity from all add-on components and calculations --- .../js/account/addOns/addOnList.component.tsx | 8 +++---- .../addOns/oneTimeAddOnRow.component.tsx | 21 ++----------------- .../oneTimeAddOnList.component.tsx | 6 ------ 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/jsapp/js/account/addOns/addOnList.component.tsx b/jsapp/js/account/addOns/addOnList.component.tsx index 0955edf4dc..cb6606024f 100644 --- a/jsapp/js/account/addOns/addOnList.component.tsx +++ b/jsapp/js/account/addOns/addOnList.component.tsx @@ -145,11 +145,9 @@ const AddOnList = (props: { {'$##price##'.replace( '##price##', ( - (oneTimeAddOn.quantity * - (oneTimeAddOnProducts.find( - (product) => product.id === oneTimeAddOn.product - )?.prices[0].unit_amount || 0)) / - 100 + (oneTimeAddOnProducts.find( + (product) => product.id === oneTimeAddOn.product + )?.prices[0].unit_amount || 0) / 100 ).toFixed(2) )} diff --git a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx index 48f895d9ac..a0cf5c3f81 100644 --- a/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx +++ b/jsapp/js/account/addOns/oneTimeAddOnRow.component.tsx @@ -20,16 +20,6 @@ interface OneTimeAddOnRowProps { organization: Organization; } -const MAX_ONE_TIME_ADDON_PURCHASE_QUANTITY = 10; - -const quantityOptions = Array.from( - {length: MAX_ONE_TIME_ADDON_PURCHASE_QUANTITY}, - (_, zeroBasedIndex) => { - const index = (zeroBasedIndex + 1).toString(); - return {value: index, label: index}; - } -); - export const OneTimeAddOnRow = ({ products, isBusy, @@ -39,11 +29,10 @@ export const OneTimeAddOnRow = ({ organization, }: OneTimeAddOnRowProps) => { const [selectedProduct, setSelectedProduct] = useState(products[0]); - const [quantity, setQuantity] = useState('1'); const [selectedPrice, setSelectedPrice] = useState( selectedProduct.prices[0] ); - const displayPrice = useDisplayPrice(selectedPrice, parseInt(quantity)); + const displayPrice = useDisplayPrice(selectedPrice); const priceOptions = useMemo( () => selectedProduct.prices.map((price) => { @@ -98,12 +87,6 @@ export const OneTimeAddOnRow = ({ } }; - const onChangeQuantity = (quantity: string | null) => { - if (quantity) { - setQuantity(quantity); - } - }; - // TODO: Merge functionality of onClickBuy and onClickManage so we can unduplicate // the billing button in priceTableCells const onClickBuy = () => { @@ -112,7 +95,7 @@ export const OneTimeAddOnRow = ({ } setIsBusy(true); if (selectedPrice) { - postCheckout(selectedPrice.id, organization.id, parseInt(quantity)) + postCheckout(selectedPrice.id, organization.id) .then((response) => window.location.assign(response.url)) .catch(() => setIsBusy(false)); } diff --git a/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx b/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx index c19dd7981e..c82c61fe6e 100644 --- a/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx +++ b/jsapp/js/account/usage/oneTimeAddOnUsageModal/oneTimeAddOnList/oneTimeAddOnList.component.tsx @@ -37,7 +37,6 @@ function OneTimeAddOnList(props: OneTimeAddOnList) { return { productName, remainingLimit, - quantity: addon.quantity, }; }); }, [props.oneTimeAddOns, props.type, productsContext.isLoaded]); @@ -48,11 +47,6 @@ function OneTimeAddOnList(props: OneTimeAddOnList) {
{t('##REMAINING## remaining').replace( From 9cb028cac35e69fb4cf6b72d18ead423e1cb986d Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Wed, 5 Feb 2025 11:46:24 -0500 Subject: [PATCH 12/14] remove auto format --- jsapp/js/account/addOns/addOnList.component.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/jsapp/js/account/addOns/addOnList.component.tsx b/jsapp/js/account/addOns/addOnList.component.tsx index 833cf6f80e..8d8380757b 100644 --- a/jsapp/js/account/addOns/addOnList.component.tsx +++ b/jsapp/js/account/addOns/addOnList.component.tsx @@ -125,12 +125,13 @@ const AddOnList = (props: { - {t('##name##').replace( - '##name##', - oneTimeAddOnProducts.find( - (product) => product.id === oneTimeAddOn.product - )?.name || label - )} + {t('##name##') + .replace( + '##name##', + oneTimeAddOnProducts.find( + (product) => product.id === oneTimeAddOn.product + )?.name || label + )}

From 862805cb4de3fc4ea25b1427ed68a77305afd0e1 Mon Sep 17 00:00:00 2001 From: RuthShryock Date: Wed, 5 Feb 2025 15:24:16 -0500 Subject: [PATCH 13/14] remove formatting changes --- jsapp/js/account/addOns/addOnList.component.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jsapp/js/account/addOns/addOnList.component.tsx b/jsapp/js/account/addOns/addOnList.component.tsx index 8d8380757b..cb6606024f 100644 --- a/jsapp/js/account/addOns/addOnList.component.tsx +++ b/jsapp/js/account/addOns/addOnList.component.tsx @@ -126,12 +126,12 @@ const AddOnList = (props: { {t('##name##') - .replace( - '##name##', - oneTimeAddOnProducts.find( - (product) => product.id === oneTimeAddOn.product - )?.name || label - )} + .replace( + '##name##', + oneTimeAddOnProducts.find( + (product) => product.id === oneTimeAddOn.product + )?.name || label + )}

From 5efedc04e81b183d149ec3552d17725b2c432b87 Mon Sep 17 00:00:00 2001 From: RuthShryock <81720958+RuthShryock@users.noreply.github.com> Date: Mon, 10 Feb 2025 15:21:50 -0500 Subject: [PATCH 14/14] formatting --- jsapp/js/account/stripe.types.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/jsapp/js/account/stripe.types.ts b/jsapp/js/account/stripe.types.ts index 2a59529890..2d93fbded1 100644 --- a/jsapp/js/account/stripe.types.ts +++ b/jsapp/js/account/stripe.types.ts @@ -203,14 +203,14 @@ export type ChangePlan = } export interface OneTimeAddOn { - id: string; - created: string; - is_available: boolean; - usage_limits: Partial; - total_usage_limits: Partial; - limits_remaining: Partial; - organization: string; - product: string; + id: string + created: string + is_available: boolean + usage_limits: Partial + total_usage_limits: Partial + limits_remaining: Partial + organization: string + product: string } export interface OneTimeUsageLimits {