From 45068677a718cdd187cb962756f1c74f4d772bfa Mon Sep 17 00:00:00 2001 From: nils Date: Thu, 7 Nov 2024 14:35:31 +0100 Subject: [PATCH 1/4] add coupon code input to the billing page --- src/api/api.ts | 1 + src/intl/en.json | 9 +++ .../organization/billing/billing.page.tsx | 2 + .../settings/organization/billing/coupon.tsx | 60 +++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 src/pages/settings/organization/billing/coupon.tsx diff --git a/src/api/api.ts b/src/api/api.ts index d734955a..6fc74e4a 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -55,6 +55,7 @@ export const api = { getNextInvoice: endpoint('get', '/v1/billing/next_invoice'), hasUnpaidInvoices: endpoint('get', '/v1/billing/has_unpaid_invoices'), getUsageCsv: endpoint('get', '/v1/usages/details'), + redeemCoupon: endpoint('post', '/v1/coupons'), // invitations listInvitations: endpoint('get', '/v1/organization_invitations'), diff --git a/src/intl/en.json b/src/intl/en.json index eeb87c4a..6457d769 100644 --- a/src/intl/en.json +++ b/src/intl/en.json @@ -7,6 +7,7 @@ "back": "Back", "next": "Next", "import": "Import", + "send": "Send", "override": "Override", "noValue": "--", "notApplicable": "N/A", @@ -2389,6 +2390,14 @@ "upgradeRequired": "You have no invoices or payment methods", "cta": "Manage billing" }, + "coupon": { + "title": "Coupon code", + "description": "Redeem a coupon code", + "placeholder": "COUPON_CODE", + "submit": "Redeem", + "couponSent": "The coupon code has been applied to your organization", + "hobbyPlanUpgrade": "Do you have a coupon? Upgrade to the Starter plan to apply it." + }, "billingInformation": { "title": "Billing information", "description": "This information appears on your monthly invoice and should be the legal address of your home or business", diff --git a/src/pages/settings/organization/billing/billing.page.tsx b/src/pages/settings/organization/billing/billing.page.tsx index e7d80d58..913320a6 100644 --- a/src/pages/settings/organization/billing/billing.page.tsx +++ b/src/pages/settings/organization/billing/billing.page.tsx @@ -1,4 +1,5 @@ import { BillingInformation } from './billing-information'; +import { Coupon } from './coupon'; import { StripePortal } from './stripe-portal'; import { Usage } from './usage'; @@ -7,6 +8,7 @@ export function BillingPage() { <> + ); diff --git a/src/pages/settings/organization/billing/coupon.tsx b/src/pages/settings/organization/billing/coupon.tsx new file mode 100644 index 00000000..9d5c91a9 --- /dev/null +++ b/src/pages/settings/organization/billing/coupon.tsx @@ -0,0 +1,60 @@ +import { useMutation } from '@tanstack/react-query'; +import { useForm } from 'react-hook-form'; + +import { Button } from '@koyeb/design-system'; +import { useOrganization } from 'src/api/hooks/session'; +import { useApiMutationFn } from 'src/api/use-api'; +import { notify } from 'src/application/notify'; +import { ControlledInput } from 'src/components/controlled'; +import { SectionHeader } from 'src/components/section-header'; +import { FormValues, handleSubmit } from 'src/hooks/form'; +import { createTranslate } from 'src/intl/translate'; + +const T = createTranslate('pages.organizationSettings.billing.coupon'); + +export function Coupon() { + const organization = useOrganization(); + const t = T.useTranslate(); + + const form = useForm({ + defaultValues: { + code: '', + }, + }); + + const mutation = useMutation({ + ...useApiMutationFn('redeemCoupon', ({ code }: FormValues) => ({ + query: { code }, + })), + onSuccess() { + form.reset(); + notify.success(t('couponSent')); + }, + }); + + return ( +
+ } description={} /> + +
+ + + + + + {organization.plan === 'hobby' && ( +

+ +

+ )} +
+ ); +} From 9fb4247c0e1021c5c826bcc96f3d3b86c28d2704 Mon Sep 17 00:00:00 2001 From: nils Date: Mon, 17 Mar 2025 19:12:47 +0100 Subject: [PATCH 2/4] allow specifying a coupon code in billing page's query parameters --- src/pages/settings/organization/billing/coupon.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/organization/billing/coupon.tsx b/src/pages/settings/organization/billing/coupon.tsx index 9d5c91a9..58b7405a 100644 --- a/src/pages/settings/organization/billing/coupon.tsx +++ b/src/pages/settings/organization/billing/coupon.tsx @@ -8,17 +8,19 @@ import { notify } from 'src/application/notify'; import { ControlledInput } from 'src/components/controlled'; import { SectionHeader } from 'src/components/section-header'; import { FormValues, handleSubmit } from 'src/hooks/form'; +import { useSearchParams } from 'src/hooks/router'; import { createTranslate } from 'src/intl/translate'; const T = createTranslate('pages.organizationSettings.billing.coupon'); export function Coupon() { + const params = useSearchParams(); const organization = useOrganization(); const t = T.useTranslate(); const form = useForm({ defaultValues: { - code: '', + code: params.get('coupon') ?? '', }, }); From 3e68ed76c6f19d7b4cfa66688fba144e101d1365 Mon Sep 17 00:00:00 2001 From: nils Date: Tue, 1 Apr 2025 16:16:09 +0200 Subject: [PATCH 3/4] show the coupon form depending on a feature flag --- src/pages/settings/organization/billing/billing.page.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/organization/billing/billing.page.tsx b/src/pages/settings/organization/billing/billing.page.tsx index 913320a6..e406ae0d 100644 --- a/src/pages/settings/organization/billing/billing.page.tsx +++ b/src/pages/settings/organization/billing/billing.page.tsx @@ -1,3 +1,5 @@ +import { FeatureFlag } from 'src/hooks/feature-flag'; + import { BillingInformation } from './billing-information'; import { Coupon } from './coupon'; import { StripePortal } from './stripe-portal'; @@ -8,7 +10,9 @@ export function BillingPage() { <> - + + + ); From 9168132986ad58d547dba0474063821b70708705 Mon Sep 17 00:00:00 2001 From: nils Date: Tue, 1 Apr 2025 16:20:48 +0200 Subject: [PATCH 4/4] use body instead of query to apply a coupon --- src/pages/settings/organization/billing/coupon.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/organization/billing/coupon.tsx b/src/pages/settings/organization/billing/coupon.tsx index 58b7405a..93800184 100644 --- a/src/pages/settings/organization/billing/coupon.tsx +++ b/src/pages/settings/organization/billing/coupon.tsx @@ -26,7 +26,7 @@ export function Coupon() { const mutation = useMutation({ ...useApiMutationFn('redeemCoupon', ({ code }: FormValues) => ({ - query: { code }, + body: { code }, })), onSuccess() { form.reset();