Skip to content

Commit 1a7e669

Browse files
gianpajCopilot
andauthored
Refactor credits page for dynamic promo translations and theming (#180)
* Refactor credits page for dynamic promo translations and theming * fix: address PR comments * perf(build): sentry - disable telemetry in preview or development build environments * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent a4d6298 commit 1a7e669

File tree

10 files changed

+120
-36
lines changed

10 files changed

+120
-36
lines changed

app/[lang]/(dashboard)/dashboard/credits/credit-topup.tsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,30 @@ import type { Locale } from '@/lib/i18n/i18n-config';
1212
import { getTopupPackages } from '@/lib/stripe/pricing';
1313

1414
interface CreditTopupProps {
15-
dict: (typeof lang)['credits'];
15+
dict: typeof lang;
1616
lang: Locale;
1717
}
1818

19-
type ActionState = {
19+
interface ActionState {
2020
error: string | null;
2121
success: boolean;
22-
};
22+
}
2323

2424
const initialState: ActionState = {
2525
error: null,
2626
success: false,
2727
};
2828

2929
export function CreditTopup({ dict, lang }: CreditTopupProps) {
30+
const promoTheme = process.env.NEXT_PUBLIC_PROMO_THEME || 'pink'; // 'orange' or 'pink'
3031
const isPromoEnabled = process.env.NEXT_PUBLIC_PROMO_ENABLED === 'true';
32+
const translations = process.env.NEXT_PUBLIC_PROMO_TRANSLATIONS || '';
33+
const bannerTranslations =
34+
Object.hasOwn(dict.promos, translations)
35+
? dict.promos[translations as keyof typeof dict.promos]
36+
: undefined;
3137

32-
const { plans: pPlans } = dict;
38+
const { plans: pPlans } = dict.credits;
3339

3440
const TOPUP_PACKAGES = getTopupPackages(lang);
3541

@@ -79,25 +85,28 @@ export function CreditTopup({ dict, lang }: CreditTopupProps) {
7985
];
8086

8187
return (
82-
<div className="grid gap-6 md:grid-cols-1 lg:grid-cols-3">
88+
<div
89+
className="grid gap-6 md:grid-cols-1 lg:grid-cols-3"
90+
data-promo-theme={promoTheme}
91+
>
8392
{plans.map((plan) => (
84-
<CreditCard
85-
dict={dict}
93+
<PlanCard
94+
bannerTranslations={bannerTranslations}
95+
dict={dict.credits}
8696
isPromoEnabled={isPromoEnabled}
8797
key={plan.id}
8898
plan={plan}
89-
pPlans={pPlans}
9099
/>
91100
))}
92101
</div>
93102
);
94103
}
95104

96-
function CreditCard({
105+
function PlanCard({
97106
plan,
98107
dict,
108+
bannerTranslations,
99109
isPromoEnabled,
100-
pPlans,
101110
}: {
102111
plan: {
103112
id: string;
@@ -111,9 +120,9 @@ function CreditCard({
111120
creditsText: string;
112121
promoBonus?: string;
113122
};
114-
dict: (typeof lang)['credits'];
115123
isPromoEnabled: boolean;
116-
pPlans: (typeof lang)['credits']['plans'];
124+
dict: (typeof lang)['credits'];
125+
bannerTranslations?: (typeof lang)['promos'][keyof (typeof lang)['promos']];
117126
}) {
118127
const formAction = async (
119128
_prevState: ActionState,
@@ -148,18 +157,20 @@ function CreditCard({
148157

149158
return (
150159
<Card
151-
className={`grid grid-rows-auto gap-2 p-6 ${plan.isPopular ? 'border-none ring-2 ring-pink-400' : ''} relative overflow-hidden`}
160+
className={`grid grid-rows-auto gap-2 p-6 ${plan.isPopular ? 'border-none ring-2 ring-promo-accent' : ''} relative overflow-hidden`}
152161
>
153162
{isPromoEnabled && plan.price > 0 && (
154-
<div className="absolute top-0 right-0 rounded-bl-lg bg-gradient-to-br from-pink-500 to-pink-600 px-3 py-1 font-bold text-white text-xs">
155-
Black Friday Sale
163+
<div className="absolute top-0 right-0 rounded-bl-lg bg-gradient-to-br from-promo-primary to-promo-primary-dark px-3 py-1 font-bold text-white text-xs">
164+
{bannerTranslations?.pricing.bannerText}
156165
</div>
157166
)}
158167
<div>
159168
<div className="flex items-center justify-between">
160169
<h3 className="font-semibold text-xl">{plan.name}</h3>
161170
{!isPromoEnabled && plan.isPopular ? (
162-
<Badge className="rounded-full bg-pink-600">{pPlans.popular}</Badge>
171+
<Badge className="rounded-full bg-promo-text">
172+
{dict.plans.popular}
173+
</Badge>
163174
) : (
164175
plan.price > 10 && (
165176
<Badge
@@ -193,7 +204,7 @@ function CreditCard({
193204
<div className="font-medium text-sm">
194205
{plan.creditsText}{' '}
195206
{isPromoEnabled && plan.promoBonus && (
196-
<span className="font-semibold text-pink-600 dark:text-pink-400">
207+
<span className="font-semibold text-promo-text-dark">
197208
(+{plan.promoBonus} bonus)
198209
</span>
199210
)}

app/[lang]/(dashboard)/dashboard/credits/page.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default async function CreditsPage(props: {
2727
const { lang } = params;
2828

2929
const supabase = await createClient();
30-
const dict = await getDictionary(lang, 'credits');
30+
const dict = await getDictionary(lang);
3131

3232
const { data } = await supabase.auth.getUser();
3333
const user = data?.user;
@@ -84,11 +84,15 @@ export default async function CreditsPage(props: {
8484

8585
return (
8686
<div className="space-y-8">
87-
<TopupStatus dict={dict} />
87+
<TopupStatus dict={dict.credits} />
8888
<div className="flex flex-col justify-between gap-4 lg:flex-row">
8989
<div className="w-full lg:w-3/4">
90-
<h3 className="mb-4 font-semibold text-lg">{dict.topup.title}</h3>
91-
<p className="text-muted-foreground">{dict.topup.description}</p>
90+
<h3 className="mb-4 font-semibold text-lg">
91+
{dict.credits.topup.title}
92+
</h3>
93+
<p className="text-muted-foreground">
94+
{dict.credits.topup.description}
95+
</p>
9296
</div>
9397
<Button asChild icon={ArrowTopRightIcon} iconPlacement="right">
9498
<Link
@@ -104,8 +108,13 @@ export default async function CreditsPage(props: {
104108
<CreditTopup dict={dict} lang={lang} />
105109

106110
<div className="my-8">
107-
<h3 className="mb-4 font-semibold text-lg">{dict.history.title}</h3>
108-
<CreditHistory dict={dict} transactions={existingTransactions} />
111+
<h3 className="mb-4 font-semibold text-lg">
112+
{dict.credits.history.title}
113+
</h3>
114+
<CreditHistory
115+
dict={dict.credits}
116+
transactions={existingTransactions}
117+
/>
109118
</div>
110119

111120
{shouldShowPricingTable && clientSecret && (

app/globals.css

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** biome-ignore-all lint/correctness/noUnknownFunction: Tailwind CSS directives like theme() are not standard CSS functions */
12
@tailwind base;
23
@tailwind components;
34
@tailwind utilities;
@@ -20,6 +21,29 @@
2021
:root {
2122
--radius: 0.5rem;
2223
--white: #fff;
24+
25+
/* Promo theme colors - default to pink */
26+
--promo-primary: theme('colors.pink.500');
27+
--promo-primary-dark: theme('colors.pink.600');
28+
--promo-accent: theme('colors.pink.400');
29+
--promo-text: theme('colors.pink.600');
30+
--promo-text-dark: theme('colors.pink.400');
31+
}
32+
33+
[data-promo-theme="orange"] {
34+
--promo-primary: theme('colors.orange.500');
35+
--promo-primary-dark: theme('colors.orange.600');
36+
--promo-accent: theme('colors.orange.400');
37+
--promo-text: theme('colors.orange.600');
38+
--promo-text-dark: theme('colors.orange.400');
39+
}
40+
41+
[data-promo-theme="pink"] {
42+
--promo-primary: theme('colors.pink.500');
43+
--promo-primary-dark: theme('colors.pink.600');
44+
--promo-accent: theme('colors.pink.400');
45+
--promo-text: theme('colors.pink.600');
46+
--promo-text-dark: theme('colors.pink.400');
2347
}
2448

2549
.dark {
@@ -62,6 +86,7 @@
6286
* {
6387
@apply border-border;
6488
}
89+
6590
body {
6691
@apply bg-background text-foreground antialiased;
6792
}
@@ -70,10 +95,10 @@
7095
@apply bg-[linear-gradient(to_right,var(--color-border)_1px,transparent_1px),linear-gradient(to_bottom,var(--color-border)_1px,transparent_1px)];
7196
}
7297
}
98+
7399
@-moz-document url-prefix() {
74100
.disable-bg-firefox {
75101
background: none;
76-
/* biome-ignore lint/complexity/noImportantStyles: it's grand */
77102
background-image: none !important;
78103
}
79104
}
@@ -87,6 +112,7 @@
87112
height: var(--ta1-height, auto);
88113
resize: none;
89114
}
115+
90116
.textarea-2 {
91117
height: var(--ta2-height, auto);
92118
resize: none;

components/pricing-table.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import { getTopupPackages } from '@/lib/stripe/pricing';
1010

1111
async function PricingTable({ lang }: { lang: Locale }) {
1212
const credits = await getDictionary(lang, 'credits');
13+
const translations = process.env.NEXT_PUBLIC_PROMO_TRANSLATIONS;
14+
const promos = await getDictionary(lang, 'promos');
15+
const bannerTranslations =
16+
translations && Object.hasOwn(promos, translations)
17+
? promos[translations as keyof typeof promos]
18+
: undefined;
1319
const { plans: pPlans, billing } = credits;
1420

1521
const isPromoEnabled = process.env.NEXT_PUBLIC_PROMO_ENABLED === 'true';
@@ -63,28 +69,33 @@ async function PricingTable({ lang }: { lang: Locale }) {
6369
},
6470
];
6571

72+
const promoTheme = process.env.NEXT_PUBLIC_PROMO_THEME || 'pink'; // 'orange' or 'pink'
73+
6674
return (
67-
<div className="flex flex-col gap-6 py-16 xl:px-28">
75+
<div
76+
className="flex flex-col gap-6 py-16 xl:px-28"
77+
data-promo-theme={promoTheme}
78+
>
6879
<h2 className="mx-auto mb-4 font-semibold text-2xl">
6980
{credits.pricingPlan}
7081
</h2>
7182
<div className="grid gap-6 md:grid-cols-1 lg:grid-cols-3">
7283
{plans.map((plan) => (
7384
<Card
74-
className={`grid grid-rows-[auto_minmax(60px,auto)_auto_1fr] gap-2 p-6 ${plan.isPopular ? 'border-none ring-2 ring-orange-400' : ''} relative overflow-hidden`}
85+
className={`grid grid-rows-[auto_minmax(60px,auto)_auto_1fr] gap-2 p-6 ${plan.isPopular ? 'border-none ring-2 ring-promo-accent' : ''} relative overflow-hidden`}
7586
key={plan.name}
7687
// className={`grid gap-2 grid-rows-[auto_minmax(60px,auto)_auto_1fr] p-6 ${plan.isPopular ? 'border-green-600' : ''}`}
7788
>
7889
{isPromoEnabled && plan.price > 0 && (
79-
<div className="absolute top-0 right-0 rounded-bl-lg bg-gradient-to-br from-orange-500 to-orange-600 px-3 py-1 font-bold text-white text-xs">
80-
🎃 Halloween Special
90+
<div className="absolute top-0 right-0 rounded-bl-lg bg-gradient-to-br from-promo-primary to-promo-primary-dark px-3 py-1 font-bold text-white text-xs">
91+
{bannerTranslations?.pricing.bannerText}
8192
</div>
8293
)}
8394
<div>
8495
<div className="flex items-center justify-between">
8596
<h3 className="font-semibold text-xl">{plan.name}</h3>
8697
{!isPromoEnabled && plan.isPopular ? (
87-
<Badge className="rounded-full bg-orange-600">
98+
<Badge className="rounded-full bg-promo-text">
8899
{/*<Badge className="rounded-full bg-green-600">*/}
89100
{pPlans.popular}
90101
</Badge>
@@ -132,7 +143,7 @@ async function PricingTable({ lang }: { lang: Locale }) {
132143
<div className="font-medium text-sm">
133144
{plan.creditsText}{' '}
134145
{isPromoEnabled && plan.promoBonus && (
135-
<span className="font-semibold text-orange-600 dark:text-orange-400">
146+
<span className="font-semibold text-promo-text-dark">
136147
(+{plan.promoBonus} bonus)
137148
</span>
138149
)}

lib/i18n/dictionaries/de.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,10 @@
323323
"text": "🎃 Halloween Special – Erhalten Sie bis zu 35% Extra-Credits bei einem Kauf 👻\nZeitlich begrenztes Angebot 🦇",
324324
"ctaLoggedIn": "Credits Erhalten",
325325
"ctaLoggedOut": "Jetzt Registrieren",
326-
"arialLabelDismiss": "Halloween-Banner schließen"
326+
"arialLabelDismiss": "Halloween-Banner schließen",
327+
"pricing": {
328+
"bannerText": "🎃 Halloween-Special"
329+
}
327330
},
328331
"blackFridayBanner": {
329332
"text": "🛍️ Black Friday Sale – Erhalten Sie bis zu 50% Extra-Credits bei jedem Kauf!\nZeitlich begrenztes Angebot 🔥",
@@ -336,6 +339,9 @@
336339
"hours": "Stunden",
337340
"minutes": "Min",
338341
"seconds": "Sek"
342+
},
343+
"pricing": {
344+
"bannerText": "Black Friday Verkauf"
339345
}
340346
}
341347
}

lib/i18n/dictionaries/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,10 @@
323323
"text": "🎃 Halloween Special – Get up to 35% extra credits with a purchase 👻\nLimited time offer 🦇",
324324
"ctaLoggedIn": "Claim Offer 🎃",
325325
"ctaLoggedOut": "Sign Up Now 🎃",
326-
"arialLabelDismiss": "Dismiss Halloween banner"
326+
"arialLabelDismiss": "Dismiss Halloween banner",
327+
"pricing": {
328+
"bannerText": "🎃 Halloween Special"
329+
}
327330
},
328331
"blackFridayBanner": {
329332
"text": "🛍️ Black Friday Sale – Get up to 35% extra credits!",
@@ -336,6 +339,9 @@
336339
"hours": "Hours",
337340
"minutes": "Min",
338341
"seconds": "Sec"
342+
},
343+
"pricing": {
344+
"bannerText": "Black Friday Sale"
339345
}
340346
}
341347
}

lib/i18n/dictionaries/es.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,10 @@
323323
"text": "🎃 Especial Halloween – Consigue hasta un 35% de créditos extra con tu compra 👻\nOferta por tiempo limitado 🦇",
324324
"ctaLoggedIn": "Obtener Créditos",
325325
"ctaLoggedOut": "Regístrate Ahora",
326-
"arialLabelDismiss": "Descartar banner de Halloween"
326+
"arialLabelDismiss": "Descartar banner de Halloween",
327+
"pricing": {
328+
"bannerText": "🎃 Especial Halloween"
329+
}
327330
},
328331
"blackFridayBanner": {
329332
"text": "🛍️ Oferta Black Friday – ¡Consigue hasta un 50% de créditos extra con cualquier compra!\nOferta por tiempo limitado 🔥",
@@ -336,6 +339,9 @@
336339
"hours": "Horas",
337340
"minutes": "Min",
338341
"seconds": "Seg"
342+
},
343+
"pricing": {
344+
"bannerText": "Oferta Black Friday"
339345
}
340346
}
341347
}

lib/stripe/pricing.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ export const getTopupPackages = (lang: Locale) => {
66
// Get promo bonuses
77
const promoBonuses = {
88
stater: isPromoEnabled
9-
? Number.parseInt(process.env.NEXT_PUBLIC_PROMO_BONUS_STARTER || '0')
9+
? Number.parseInt(process.env.NEXT_PUBLIC_PROMO_BONUS_STARTER || '0', 10)
1010
: 0,
1111
standard: isPromoEnabled
12-
? Number.parseInt(process.env.NEXT_PUBLIC_PROMO_BONUS_STANDARD || '0')
12+
? Number.parseInt(process.env.NEXT_PUBLIC_PROMO_BONUS_STANDARD || '0', 10)
1313
: 0,
1414
pro: isPromoEnabled
15-
? Number.parseInt(process.env.NEXT_PUBLIC_PROMO_BONUS_PRO || '0')
15+
? Number.parseInt(process.env.NEXT_PUBLIC_PROMO_BONUS_PRO || '0', 10)
1616
: 0,
1717
};
1818

next.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ if (process.env.NODE_ENV === 'production') {
109109
// Only print logs for uploading source maps in CI
110110
silent: !process.env.CI,
111111

112+
telemetry: process.env.VERCEL_ENV === 'production',
113+
112114
// For all available options, see:
113115
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
114116

tailwind.config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ const config: Config = {
7777
border: 'hsl(var(--sidebar-border))',
7878
ring: 'hsl(var(--sidebar-ring))',
7979
},
80+
promo: {
81+
primary: 'var(--promo-primary)',
82+
'primary-dark': 'var(--promo-primary-dark)',
83+
accent: 'var(--promo-accent)',
84+
text: 'var(--promo-text)',
85+
'text-dark': 'var(--promo-text-dark)',
86+
},
8087
},
8188
keyframes: {
8289
'accordion-down': {

0 commit comments

Comments
 (0)