Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions apps/frontend/src/components/billing/first.billing.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
FAQSection,
} from '@gitroom/frontend/components/billing/faq.component';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
import { useToaster } from '@gitroom/react/toaster/toaster';
import { useUser } from '@gitroom/frontend/components/layout/user.context';
import { useDubClickId } from '@gitroom/frontend/components/layout/dubAnalytics';
import SafeImage from '@gitroom/react/helpers/safe.image';
Expand Down Expand Up @@ -57,6 +58,7 @@ export const FirstBillingComponent = () => {
const fetch = useFetch();
const modals = useModals();
const t = useT();
const toaster = useToaster();
const [datafast_visitor_id] = useCookie('datafast_visitor_id', '');
const [datafast_session_id] = useCookie('datafast_session_id', '');
const sharedDosBilling = !!user?.sharedDosBilling;
Expand Down Expand Up @@ -87,15 +89,20 @@ export const FirstBillingComponent = () => {
const startDosCheckout = useCallback(async () => {
setDosCheckoutLoading(true);
try {
const result = await (
await fetch('/billing/subscribe', {
method: 'POST',
body: JSON.stringify({
billing: tier,
period: 'MONTHLY',
}),
})
).json();
const response = await fetch('/billing/subscribe', {
method: 'POST',
body: JSON.stringify({
billing: tier,
period: 'MONTHLY',
}),
});
const result = await response.json().catch(() => ({}));
if (!response.ok) {
toaster.show(
result.message || 'Subscription update failed, please try again later'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback error message is hardcoded. To support internationalization (i18n), please wrap the string in the t() translation function.

Suggested change
result.message || 'Subscription update failed, please try again later'
result.message || t('billing_subscription_update_failed', 'Subscription update failed, please try again later')

);
return;
}
if (result.url) {
window.location.href = result.url;
return;
Expand All @@ -106,7 +113,7 @@ export const FirstBillingComponent = () => {
} finally {
setDosCheckoutLoading(false);
}
}, [fetch, tier]);
}, [fetch, tier, toaster]);

const showYouTube = () => {
modals.openModal({
Expand Down
105 changes: 70 additions & 35 deletions apps/frontend/src/components/billing/main.billing.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,14 @@ export const MainBillingComponent: FC<{
setSubscription(sub);
}, [sub]);
const updatePayment = useCallback(async () => {
const { portal } = await (await fetch('/billing/portal')).json();
// dos.me returns 409 no_stripe_subscription for accounts without a
// dos-managed plan - never navigate to an undefined portal.
const response = await fetch('/billing/portal');
const { portal, message } = await response.json().catch(() => ({}));
if (!response.ok || !portal) {
toast.show(message || 'Payment portal is not available for this account');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback error message is hardcoded. Please wrap it in the t() translation function to support internationalization.

Suggested change
toast.show(message || 'Payment portal is not available for this account');
toast.show(message || t('payment_portal_not_available', 'Payment portal is not available for this account'));

return;
}
window.location.href = portal;
}, []);
const currentPackage = useMemo(() => {
Expand All @@ -281,22 +288,35 @@ export const MainBillingComponent: FC<{
}
return subscription?.subscriptionTier;
}, [subscription, initialChannels, monthlyOrYearly, period, sharedDosBilling]);
// dos.me owns checkout/portal/cancel: they only exist for a dos-managed
// paid plan, so hide both buttons for FREE and legacy tiers (e.g.
// comped ULTIMATE) where the calls can only answer 409.
const showPortalAndCancel =
!sharedDosBilling ||
subscription?.subscriptionTier === 'STANDARD' ||
subscription?.subscriptionTier === 'PRO';
const moveToCheckout = useCallback(
(billing: 'STANDARD' | 'PRO' | 'FREE', reactivate = false) =>
async () => {
if (reactivate) {
setLoading(true);
const { cancel_at } = await (
await fetch('/billing/cancel', {
method: 'POST',
body: JSON.stringify({
feedback: '',
}),
headers: {
'Content-Type': 'application/json',
},
})
).json();
const response = await fetch('/billing/cancel', {
method: 'POST',
body: JSON.stringify({
feedback: '',
}),
headers: {
'Content-Type': 'application/json',
},
});
const { cancel_at, message } = await response
.json()
.catch(() => ({}));
if (!response.ok) {
setLoading(false);
toast.show(message || 'Could not reactivate the subscription');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback error message is hardcoded. Please wrap it in the t() translation function to support internationalization.

Suggested change
toast.show(message || 'Could not reactivate the subscription');
toast.show(message || t('could_not_reactivate_subscription', 'Could not reactivate the subscription'));

return;
}
setSubscription((subs) => ({
...subs!,
cancelAt: cancel_at,
Expand Down Expand Up @@ -365,17 +385,23 @@ export const MainBillingComponent: FC<{
});

setLoading(true);
const { cancel_at } = await (
await fetch('/billing/cancel', {
method: 'POST',
body: JSON.stringify({
feedback: info,
}),
headers: {
'Content-Type': 'application/json',
},
})
).json();
const response = await fetch('/billing/cancel', {
method: 'POST',
body: JSON.stringify({
feedback: info,
}),
headers: {
'Content-Type': 'application/json',
},
});
const { cancel_at, message } = await response
.json()
.catch(() => ({}));
if (!response.ok) {
setLoading(false);
toast.show(message || 'Could not cancel the subscription');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback error message is hardcoded. Please wrap it in the t() translation function to support internationalization.

Suggested change
toast.show(message || 'Could not cancel the subscription');
toast.show(message || t('could_not_cancel_subscription', 'Could not cancel the subscription'));

return;
}
setSubscription((subs) => ({
...subs!,
cancelAt: cancel_at,
Expand All @@ -393,17 +419,26 @@ export const MainBillingComponent: FC<{
return;
}
setLoading(true);
const { url, portal, blocked } = await (
await fetch('/billing/subscribe', {
method: 'POST',
body: JSON.stringify({
period: monthlyOrYearly === 'on' ? 'YEARLY' : 'MONTHLY',
utm,
billing,
...(dub ? { dub } : {}),
}),
})
).json();
const response = await fetch('/billing/subscribe', {
method: 'POST',
body: JSON.stringify({
period: monthlyOrYearly === 'on' ? 'YEARLY' : 'MONTHLY',
utm,
billing,
...(dub ? { dub } : {}),
}),
});
const { url, portal, blocked, message } = await response
.json()
.catch(() => ({}));
if (!response.ok) {
// A 4xx (no dos.me plan row, stripe error, ...) used to fall through
// to the success branch and toast "Subscription updated" + mutate
// the tier optimistically - surface the real failure instead.
setLoading(false);
toast.show(message || 'Subscription update failed, please try again later');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback error message is hardcoded. Please wrap it in the t() translation function to support internationalization.

Suggested change
toast.show(message || 'Subscription update failed, please try again later');
toast.show(message || t('subscription_update_failed', 'Subscription update failed, please try again later'));

return;
}
if (blocked) {
setLoading(false);
await deleteDialog(
Expand Down Expand Up @@ -604,7 +639,7 @@ export const MainBillingComponent: FC<{
</div>
))}
</div>
{!!subscription?.id && (
{!!subscription?.id && showPortalAndCancel && (
<div className="flex justify-center mt-[20px] gap-[10px]">
<Button onClick={updatePayment}>
{t(
Expand Down
Loading