diff --git a/shared/locales/de/website-donate.json b/shared/locales/de/website-donate.json index 876ceaec5..3c59cc829 100644 --- a/shared/locales/de/website-donate.json +++ b/shared/locales/de/website-donate.json @@ -14,9 +14,9 @@ "0": "{{ amount, currency }} legt den Grundstein für eine bedeutende Sache. Es ermöglicht uns, einer Person in Not ein Social Income zu bieten.", "1": "{{ amount, currency }} deckt mehr als ein Drittel eines Social Incomes für eine bedürftige Person.", "2": "{{ amount, currency }} finanziert mehr als die Hälfte eines Social Incomes für eine Person.", - "3": "Großartig! Dein monatlicher Beitrag von {{ amount, currency }} ermöglicht es mindestens einer bedürftigen Person ein vollständiges Social Income bereitzustellen.", + "3": "Dein monatlicher Beitrag von {{ amount, currency }} ermöglicht es mindestens einer bedürftigen Person ein vollständiges Social Income bereitzustellen.", "4": "{{ amount, currency }} ermöglicht ein Social Income für mehr als zwei bedürftige Personen.", - "5": "Wunderbar! Dein Beitrag von {{ amount, currency }} finanziert ein Social Income für mehr als drei bedürftige Personen.", + "5": "Dein Beitrag von {{ amount, currency }} finanziert ein Social Income für mehr als drei bedürftige Personen.", "6": "Dein Beitrag von {{ amount, currency }} sichert ein Social Income für mehr als vier bedürftige Personen.", "7": "Dein Beitrag von {{ amount, currency }} sichert ein Social Income für mehr als fünf bedürftige Personen.", "8": "Dein monatlicher Beitrag von {{ amount, currency }} ist außergewöhnlich, ebenso wie dein Einfluss." diff --git a/shared/locales/de/website-login.json b/shared/locales/de/website-login.json index ab5b324b2..b17f5c78e 100644 --- a/shared/locales/de/website-login.json +++ b/shared/locales/de/website-login.json @@ -2,6 +2,11 @@ "metadata": { "title": "Login | Social Income" }, + "alert": { + "title": "Passwort zurücksetzen", + "text": "Falls du vor Dezember 2023 ein Konto bei uns erstellt hast, setze bitte dein Passwort über \"Passwort vergessen?\" zurück.", + "tooltip": "Mit der neuen Webseite haben unsere Benutzerdatenbank aktualisiert. Deine Daten sind sicher, aber du musst dein Passwort zurücksetzen, um dich wieder anmelden zu können." + }, "title": "Melde dich an", "email": "E-Mail", "invalid-email": "Ungültige E-Mail-Adresse", diff --git a/shared/locales/de/website-me.json b/shared/locales/de/website-me.json index 6203f1cfa..9feb0a9e5 100644 --- a/shared/locales/de/website-me.json +++ b/shared/locales/de/website-me.json @@ -34,6 +34,7 @@ "source": "Quelle", "total": "Total", "amount-currency": "{{ amount, currency }}", + "new-subscription": "Neues Abonnement", "interval": "Interval", "interval-1": "Monatlich", "interval-3": "Quartalsweise", diff --git a/shared/locales/en/website-donate.json b/shared/locales/en/website-donate.json index c6f2cc90a..3a221d431 100644 --- a/shared/locales/en/website-donate.json +++ b/shared/locales/en/website-donate.json @@ -8,15 +8,15 @@ "amount": "Amount", "button-text": "Make a Difference", "donation-impact": { - "monthly-contribution": "Your monthly contribution:", + "monthly-contribution": "Your monthly contribution", "direct-payout": "The people in need receive your contribution directly on their mobile phones.", - "your-impact": "Your impact:", + "your-impact": "Your impact", "0": "{{ amount, currency }} lays the groundwork for a significant cause. It enables us to provide a Social Income to an individual facing hardship.", "1": "{{ amount, currency }} pays for more than a third of a Social Income for one person in need.", "2": "{{ amount, currency }} funds more than half of someone’s Social Income.", - "3": "Great! Your monthly contribution of {{ amount, currency }} will allow at least one person in need to receive a full Social Income.", + "3": "Your monthly contribution of {{ amount, currency }} will allow at least one person in need to receive a full Social Income.", "4": "{{ amount, currency }} provides a Social Income for more than two people in need.", - "5": "Wonderful! Your contribution of {{ amount, currency }} funds a Social Income for more than three people in need.", + "5": "Your contribution of {{ amount, currency }} funds a Social Income for more than three people in need.", "6": "Your contribution of {{ amount, currency }} sustains a Social Income for more than four people in need.", "7": "Your contribution of {{ amount, currency }} sustains a Social Income for more than five people in need.", "8": "Your monthly contribution of {{ amount, currency }} is extraordinary, as well as your impact." diff --git a/shared/locales/en/website-login.json b/shared/locales/en/website-login.json index 3a48899d5..f715ac71a 100644 --- a/shared/locales/en/website-login.json +++ b/shared/locales/en/website-login.json @@ -2,6 +2,11 @@ "metadata": { "title": "Login | Social Income" }, + "alert": { + "title": "Reset Password", + "text": "If you created an account with us before December 2023, please reset your password using \"Forgot password?\" below.", + "tooltip": "With the new website, our user database has been updated. Your data is secure, but you need to reset your password to log in again." + }, "title": "Sign in to your account", "email": "Email", "invalid-email": "Invalid email address", diff --git a/shared/locales/en/website-me.json b/shared/locales/en/website-me.json index 3cafd32bb..a8766af53 100644 --- a/shared/locales/en/website-me.json +++ b/shared/locales/en/website-me.json @@ -34,6 +34,7 @@ "source": "Source", "total": "Total", "amount-currency": "{{ amount, currency }}", + "new-subscription": "New subscription", "interval": "Interval", "interval-1": "Monthly", "interval-3": "Quarterly", diff --git a/shared/src/utils/stats/PaymentStatsCalculator.ts b/shared/src/utils/stats/PaymentStatsCalculator.ts index adc12c30c..067bf6b63 100644 --- a/shared/src/utils/stats/PaymentStatsCalculator.ts +++ b/shared/src/utils/stats/PaymentStatsCalculator.ts @@ -45,7 +45,8 @@ export class PaymentStatsCalculator { static async build(firestoreAdmin: FirestoreAdmin, currency: string): Promise { const payments = await firestoreAdmin.collectionGroup(PAYMENT_FIRESTORE_PATH).get(); const exchangeRate = await getLatestExchangeRate(firestoreAdmin, currency); - const contributions = payments.docs + // TODO: filter out payments from test users + const paymentDocs = payments.docs .filter((payment) => payment.data().amount_chf != undefined) .map((paymentDoc) => { const payment = paymentDoc.data(); @@ -56,7 +57,7 @@ export class PaymentStatsCalculator { month: toDateTime(payment.payment_at).toFormat('yyyy-MM'), } as PaymentStatsEntry; }); - return new PaymentStatsCalculator(_(contributions)); + return new PaymentStatsCalculator(_(paymentDocs)); } totalPayments = () => { diff --git a/ui/src/components/alert.tsx b/ui/src/components/alert.tsx new file mode 100644 index 000000000..4ae93adc8 --- /dev/null +++ b/ui/src/components/alert.tsx @@ -0,0 +1,43 @@ +import { cva, type VariantProps } from 'class-variance-authority'; +import * as React from 'react'; +import { cn } from '../lib/utils'; + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-2 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground', + { + variants: { + variant: { + default: 'bg-popover text-popover-foreground', + primary: 'bg-primary-muted border-primary text-primary-foreground', + secondary: 'bg-secondary-muted border-secondary text-secondary-foreground', + accent: 'bg-accent-muted border-accent text-accent-foreground', + destructive: 'bg-destructive-muted border-destructive text-destructive-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = 'Alert'; + +const AlertTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +); +AlertTitle.displayName = 'AlertTitle'; + +const AlertDescription = React.forwardRef>( + ({ className, ...props }, ref) =>
, +); +AlertDescription.displayName = 'AlertDescription'; + +export { Alert, AlertDescription, AlertTitle }; diff --git a/ui/src/components/badge.tsx b/ui/src/components/badge.tsx index 7a6c076f4..70f506f6c 100644 --- a/ui/src/components/badge.tsx +++ b/ui/src/components/badge.tsx @@ -8,9 +8,9 @@ const badgeVariants = cva( { variants: { variant: { - default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', - secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', - destructive: 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', + default: 'border-transparent bg-primary text-primary-foreground hover:bg-primary-muted', + secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary-muted', + destructive: 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive-muted', outline: 'text-foreground', }, }, diff --git a/ui/src/components/button.tsx b/ui/src/components/button.tsx index 624095e74..01e2be4ab 100644 --- a/ui/src/components/button.tsx +++ b/ui/src/components/button.tsx @@ -1,49 +1,84 @@ import { IconType } from '@icons-pack/react-simple-icons/types'; import { Slot } from '@radix-ui/react-slot'; +import { SpinnerIcon } from '@socialincome/website/src/components/logos/spinner-icon'; import { cva, type VariantProps } from 'class-variance-authority'; import * as React from 'react'; import { cn } from '../lib/utils'; const buttonVariants = cva( - 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + 'inline-flex items-center justify-center rounded-md text-sm ring-offset-background transition-all duration-200 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { - variant: { - default: 'bg-primary text-primary-foreground hover:bg-primary/90', - destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', - outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', - secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', - ghost: 'hover:bg-accent hover:text-accent-foreground', - link: 'text-primary underline-offset-4 hover:underline', - }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-16 rounded-md px-8 text-lg', icon: 'h-10 w-10', }, + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary-muted font-medium', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive-muted font-medium', + outline: 'border border-input bg-background hover:bg-muted font-medium', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary-muted font-medium', + ghost: 'hover:bg-muted font-normal', + link: 'text-primary underline-offset-4 hover:underline p-0 font-normal', + }, }, defaultVariants: { - variant: 'default', size: 'default', + variant: 'default', }, }, ); +const iconVariants = cva('', { + variants: { + size: { + default: 'h-4 w-4 mr-2', + sm: 'h-3 w-3 mr-2', + lg: 'h-6 w-6 mr-3', + icon: 'h-6 w-6', + }, + defaultVariants: { + size: 'default', + }, + }, +}); + export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; + showLoadingSpinner?: boolean; Icon?: IconType; } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, Icon, children, ...props }, ref) => { + ( + { + className, + variant = 'default', + size = 'default', + asChild = false, + Icon, + showLoadingSpinner = false, + children, + ...props + }, + ref, + ) => { const Comp = asChild ? Slot : 'button'; + Icon = showLoadingSpinner ? (SpinnerIcon as IconType) : Icon; return ( - - {Icon && } - {children} + + {Icon ? ( +
+ + {children} +
+ ) : ( + <>{children} + )}
); }, diff --git a/ui/src/components/dialog.tsx b/ui/src/components/dialog.tsx index 412bea7bb..6f42ca2b3 100644 --- a/ui/src/components/dialog.tsx +++ b/ui/src/components/dialog.tsx @@ -17,7 +17,7 @@ const DialogOverlay = React.forwardRef< {children} - + Close diff --git a/ui/src/components/navigation-menu.tsx b/ui/src/components/navigation-menu.tsx index d10d29436..2a8a0b127 100644 --- a/ui/src/components/navigation-menu.tsx +++ b/ui/src/components/navigation-menu.tsx @@ -36,7 +36,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; const NavigationMenuItem = NavigationMenuPrimitive.Item; const navigationMenuTriggerStyle = cva( - 'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-lg font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50', + 'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-lg font-medium transition-colors hover:bg-muted hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50', ); const NavigationMenuTrigger = React.forwardRef< diff --git a/ui/src/components/select.tsx b/ui/src/components/select.tsx index 4d47d421a..b7b4d4941 100644 --- a/ui/src/components/select.tsx +++ b/ui/src/components/select.tsx @@ -76,7 +76,7 @@ const SelectItem = React.forwardRef< ; const FONT_SIZE_MAP: { [key in FontSize]: string } = { - '5xl': 'text-5xl', - '4xl': 'text-4xl', - '3xl': 'text-3xl', - '2xl': 'text-2xl', + '5xl': 'text-4xl md:text-5xl', + '4xl': 'text-3xl md:text-4xl', + '3xl': 'text-2xl md:text-3xl', + '2xl': 'text-xl md:text-2xl', xl: 'text-xl', lg: 'text-lg', md: 'text-base', @@ -18,19 +18,17 @@ const FONT_SIZE_MAP: { [key in FontSize]: string } = { xs: 'text-xs', }; -const FONT_WEIGHTS = ['normal', 'medium', 'semibold', 'bold'] as const; +const FONT_WEIGHTS = ['normal', 'medium', 'bold'] as const; export type FontWeight = (typeof FONT_WEIGHTS)[number]; const FONT_WEIGHT_MAP: { [key in FontWeight]: string } = { normal: 'font-normal', medium: 'font-medium', - semibold: 'font-semibold', bold: 'font-bold', }; const FONT_COLOR_MAP: { [key in FontColor]: string } = { background: 'text-background', foreground: 'text-foreground', - 'si-yellow': 'text-si-yellow', primary: 'text-primary', 'primary-foreground': 'text-primary-foreground', secondary: 'text-secondary', diff --git a/ui/src/globals.css b/ui/src/globals.css index 7c1dd14ea..622157aa9 100644 --- a/ui/src/globals.css +++ b/ui/src/globals.css @@ -23,48 +23,59 @@ @layer base { :root, .theme-default { - --si-yellow: hsl(48, 100%, 49%); - --background: hsl(219, 100%, 97%); + --background: hsl(222, 100%, 97%); --foreground: hsl(20 14.3% 4.1%); + --muted: hsl(222, 100%, 98%); + --muted-foreground: hsl(0, 0%, 30%); --card: hsl(0 100% 100%); + --card-muted: hsl(0, 0%, 98%); --card-foreground: hsl(20 14.3% 4.1%); + --card-foreground-muted: hsl(0, 0%, 40%); --popover: hsl(0 100% 100%); - --popover-foreground: hsl(0, 0%, 40%); + --popover-muted: hsl(0, 0%, 98%); + --popover-foreground: hsl(0, 0%, 35%); + --popover-foreground-muted: hsl(0, 0%, 45%); --primary: hsl(216, 54%, 55%); - --primary-foreground: hsl(60 9.1% 97.8%); + --primary-muted: hsl(216, 54%, 63%); + --primary-foreground: hsl(216 54% 100%); + --primary-foreground-muted: hsl(216, 54%, 97%); --secondary: hsl(8, 89%, 62%); - --secondary-foreground: hsl(60 9.1% 97.8%); + --secondary-muted: hsl(8, 89%, 70%); + --secondary-foreground: hsl(8, 89%, 100%); + --secondary-foreground-muted: hsl(8, 89%, 97%); - --muted: hsl(60 4.8% 95.9%); - --muted-foreground: hsl(25 5.3% 44.7%); - - --accent: hsl(219, 100%, 95%); - --accent-foreground: hsl(24 9.8% 10%); + --accent: hsl(48, 100%, 49%); + --accent-muted: hsl(48, 100%, 57%); + --accent-foreground: hsl(48, 100%, 0%); + --accent-foreground-muted: hsl(48, 100%, 30%); --destructive: hsl(0, 75%, 42%); + --destructive-muted: hsl(0, 73%, 65%); --destructive-foreground: hsl(60 9.1% 97.8%); + --destructive-foreground-muted: hsl(0, 100%, 98%); --border: hsl(20 5.9% 80%); --input: hsl(20 5.9% 90%); --ring: hsl(216, 54%, 55%); - --radius: 0.5rem; } .theme-dark-blue { - --si-yellow: hsl(48, 100%, 49%); --background: hsl(216, 54%, 55%); + --muted: hsl(216, 54%, 61%); --foreground: hsl(60 9.1% 97.8%); - --card: hsl(216, 54%, 95%); - --card-foreground: hsl(0, 0%, 10%); + --card: hsl(222, 100%, 97%); + --card-muted: hsl(222, 100%, 99%); --primary: hsl(48, 100%, 49%); - --primary-foreground: hsl(0, 0%, 20%); + --primary-muted: hsl(48, 100%, 57%); + --primary-foreground: hsl(48, 100%, 0%); + --primary-foreground-muted: hsl(48, 100%, 30%); --border: hsl(60 9.1% 97.8%); } diff --git a/ui/src/index.ts b/ui/src/index.ts index 1968606cf..c45f3a45e 100644 --- a/ui/src/index.ts +++ b/ui/src/index.ts @@ -1,6 +1,7 @@ import './globals.css'; export * from './components/accordion'; +export * from './components/alert'; export * from './components/badge'; export * from './components/button'; export * from './components/card'; diff --git a/ui/src/interfaces/color.ts b/ui/src/interfaces/color.ts index ae668513a..2abc93fc0 100644 --- a/ui/src/interfaces/color.ts +++ b/ui/src/interfaces/color.ts @@ -4,7 +4,6 @@ export const COLORS = [ 'border', 'input', 'ring', - 'si-yellow', 'primary', 'primary-foreground', 'secondary', @@ -34,7 +33,6 @@ export type FontColor = Extract< Color, | 'foreground' | 'background' - | 'si-yellow' | 'primary' | 'primary-foreground' | 'secondary' diff --git a/ui/tailwind.config.ts b/ui/tailwind.config.ts index 0c6db2193..11fb8ef57 100644 --- a/ui/tailwind.config.ts +++ b/ui/tailwind.config.ts @@ -21,35 +21,45 @@ module.exports = { ring: 'var(--ring)', background: 'var(--background)', foreground: 'var(--foreground)', - 'si-yellow': 'var(--si-yellow)', - + muted: { + DEFAULT: 'var(--muted)', + foreground: 'var(--muted-foreground)', + }, primary: { DEFAULT: 'var(--primary)', + muted: 'var(--primary-muted)', foreground: 'var(--primary-foreground)', + 'foreground-muted': 'var(--primary-foreground-muted)', }, secondary: { DEFAULT: 'var(--secondary)', + muted: 'var(--secondary-muted)', foreground: 'var(--secondary-foreground)', + 'foreground-muted': 'var(--secondary-foreground-muted)', }, destructive: { DEFAULT: 'var(--destructive)', + muted: 'var(--destructive-muted)', foreground: 'var(--destructive-foreground)', - }, - muted: { - DEFAULT: 'var(--muted)', - foreground: 'var(--muted-foreground)', + 'foreground-muted': 'var(--destructive-foreground-muted)', }, accent: { DEFAULT: 'var(--accent)', + muted: 'var(--accent-muted)', foreground: 'var(--accent-foreground)', + 'foreground-muted': 'var(--accent-foreground-muted)', }, popover: { DEFAULT: 'var(--popover)', + muted: 'var(--popover-muted)', foreground: 'var(--popover-foreground)', + 'foreground-muted': 'var(--popover-foreground-muted)', }, card: { DEFAULT: 'var(--card)', + muted: 'var(--card-muted)', foreground: 'var(--card-foreground)', + 'foreground-muted': 'var(--card-foreground-muted)', }, }, borderRadius: { diff --git a/website/src/app/[lang]/[region]/(website)/(home)/section-1.tsx b/website/src/app/[lang]/[region]/(website)/(home)/section-1.tsx index 44a5f1947..89d837e90 100644 --- a/website/src/app/[lang]/[region]/(website)/(home)/section-1.tsx +++ b/website/src/app/[lang]/[region]/(website)/(home)/section-1.tsx @@ -10,11 +10,8 @@ export async function Section1({ lang, region }: DefaultParams) { }); return ( - -
+ +
{translator.t('section-1.title-1')} @@ -23,7 +20,7 @@ export async function Section1({ lang, region }: DefaultParams) { {translator.t('section-1.title-3')}
-
+
-
+
{translations.title1} {translations.title2} - + {translations.subtitle1}
diff --git a/website/src/app/[lang]/[region]/(website)/(home)/section-3.tsx b/website/src/app/[lang]/[region]/(website)/(home)/section-3.tsx index 1d8e8c6de..35a29c3dd 100644 --- a/website/src/app/[lang]/[region]/(website)/(home)/section-3.tsx +++ b/website/src/app/[lang]/[region]/(website)/(home)/section-3.tsx @@ -23,15 +23,13 @@ export async function Section3({ lang, region }: DefaultParams) { ))}

-
+
Jackpot
{translator.t('section-3.jackpot-title')} - - {translator.t('section-3.jackpot-text')} - + {translator.t('section-3.jackpot-text')}
@@ -39,7 +37,7 @@ export async function Section3({ lang, region }: DefaultParams) {
-
+
Helping Others
@@ -53,15 +51,13 @@ export async function Section3({ lang, region }: DefaultParams) {
-
+
Together
{translator.t('section-3.together-title')} - - {translator.t('section-3.together-text')} - + {translator.t('section-3.together-text')}
diff --git a/website/src/app/[lang]/[region]/(website)/(home)/section-4.tsx b/website/src/app/[lang]/[region]/(website)/(home)/section-4.tsx index d3944df84..e531d8662 100644 --- a/website/src/app/[lang]/[region]/(website)/(home)/section-4.tsx +++ b/website/src/app/[lang]/[region]/(website)/(home)/section-4.tsx @@ -19,7 +19,7 @@ export async function Section4({ lang }: { lang: WebsiteLanguage }) {

- + {translator.t('section-4.subtitle')}
diff --git a/website/src/app/[lang]/[region]/(website)/(home)/section-5.tsx b/website/src/app/[lang]/[region]/(website)/(home)/section-5.tsx index 02826ab52..9525a3f28 100644 --- a/website/src/app/[lang]/[region]/(website)/(home)/section-5.tsx +++ b/website/src/app/[lang]/[region]/(website)/(home)/section-5.tsx @@ -10,7 +10,7 @@ export async function Section5({ lang }: { lang: WebsiteLanguage }) { }); return ( - + {translator?.t('section-5.title')} diff --git a/website/src/app/[lang]/[region]/(website)/(home)/section-7-carousel.tsx b/website/src/app/[lang]/[region]/(website)/(home)/section-7-carousel.tsx index a9d18f99b..0a9f689bf 100644 --- a/website/src/app/[lang]/[region]/(website)/(home)/section-7-carousel.tsx +++ b/website/src/app/[lang]/[region]/(website)/(home)/section-7-carousel.tsx @@ -11,6 +11,7 @@ import whatsappSVG from './(assets)/whatsapp.svg'; export function Section7Carousel() { const screenSize = useScreenSize(); + if (!screenSize) return null; const logos = [ { logo: liaSVG, name: 'LIA' }, @@ -24,9 +25,11 @@ export function Section7Carousel() { case 'xs': slidesToScroll = 1; break; - default: + case 'sm': slidesToScroll = 2; break; + default: + slidesToScroll = 3; } return ( @@ -39,7 +42,7 @@ export function Section7Carousel() { showDots > {logos.map((entry, index) => ( - + {`${entry.name} ))} diff --git a/website/src/app/[lang]/[region]/(website)/(home)/section-7.tsx b/website/src/app/[lang]/[region]/(website)/(home)/section-7.tsx index 441e2e4af..f738f9c92 100644 --- a/website/src/app/[lang]/[region]/(website)/(home)/section-7.tsx +++ b/website/src/app/[lang]/[region]/(website)/(home)/section-7.tsx @@ -14,17 +14,15 @@ export async function Section7({ lang }: { lang: WebsiteLanguage }) { backgroundColor="bg-red-50" className="flex min-h-screen flex-col justify-center space-y-8 py-16 md:py-32" > -

+ {translator.t('section-7.title-1')} {translator.t('section-7.title-2')} -

- - {translator.t('section-7.subtitle')} + {translator.t('section-7.subtitle')}
); diff --git a/website/src/app/[lang]/[region]/(website)/about-us/(sections)/contact.tsx b/website/src/app/[lang]/[region]/(website)/about-us/(sections)/contact.tsx index a0e707b6f..fe8d45354 100644 --- a/website/src/app/[lang]/[region]/(website)/about-us/(sections)/contact.tsx +++ b/website/src/app/[lang]/[region]/(website)/about-us/(sections)/contact.tsx @@ -39,7 +39,7 @@ export async function Contact({ lang }: { lang: WebsiteLanguage }) {
{translator.t('contact.paragraph')}
- + {translator.t('contact.legal-status')} {translator.t('contact.legal-status-paragraphs').map((text, index) => ( @@ -51,7 +51,7 @@ export async function Contact({ lang }: { lang: WebsiteLanguage }) {
- + {translator.t('contact.find-us')} diff --git a/website/src/app/[lang]/[region]/(website)/csr/page.tsx b/website/src/app/[lang]/[region]/(website)/csr/page.tsx index edbdfdb92..e3d5b5d8c 100644 --- a/website/src/app/[lang]/[region]/(website)/csr/page.tsx +++ b/website/src/app/[lang]/[region]/(website)/csr/page.tsx @@ -19,7 +19,7 @@ export default async function Page({ params }: DefaultPageProps) { href="https://github.com/anthonyray" target="_blank" rel="noopener noreferrer" - className="bg-card hover:bg-accent border-accent flex items-center rounded-full border-2 p-2 text-center" + className="bg-card hover:bg-muted border-accent flex items-center rounded-full border-2 p-2 text-center" > ) { - return {children}; -} diff --git a/website/src/app/[lang]/[region]/(website)/login/login-form.tsx b/website/src/app/[lang]/[region]/(website)/login/login-form.tsx index 8ad5db4d6..8b6b65060 100644 --- a/website/src/app/[lang]/[region]/(website)/login/login-form.tsx +++ b/website/src/app/[lang]/[region]/(website)/login/login-form.tsx @@ -6,7 +6,7 @@ import { Button, Form, FormControl, FormField, FormItem, FormMessage, Input, Typ import { FirebaseError } from 'firebase/app'; import { browserSessionPersistence, signInWithEmailAndPassword } from 'firebase/auth'; import { useRouter } from 'next/navigation'; -import { useCallback } from 'react'; +import { useState } from 'react'; import { useForm } from 'react-hook-form'; import toast from 'react-hot-toast'; import { useAuth } from 'reactfire'; @@ -29,6 +29,7 @@ type LoginFormProps = { export default function LoginForm({ lang, region, translations }: LoginFormProps) { const router = useRouter(); const auth = useAuth(); + const [submitting, setSubmitting] = useState(false); const formSchema = z.object({ email: z.string().email({ message: translations.invalidEmail }), @@ -41,20 +42,18 @@ export default function LoginForm({ lang, region, translations }: LoginFormProps defaultValues: { email: '', password: '' }, }); - const onSubmit = useCallback( - async (values: FormSchema) => { - await auth.setPersistence(browserSessionPersistence); - await signInWithEmailAndPassword(auth, values.email, values.password) - .then(() => { - router.push(`/${lang}/${region}/me`); - }) - .catch((error: FirebaseError) => { - if (error.code === 'auth/wrong-password' || error.code === 'auth/user-not-found') - toast.error(translations.invalidUserOrPassword); - }); - }, - [auth, lang, region, router, translations.invalidUserOrPassword], - ); + const onSubmit = async (values: FormSchema) => { + setSubmitting(true); + await auth.setPersistence(browserSessionPersistence); + await signInWithEmailAndPassword(auth, values.email, values.password) + .then(() => { + router.push(`/${lang}/${region}/me`); + }) + .catch((error: FirebaseError) => { + if (error.code === 'auth/wrong-password' || error.code === 'auth/user-not-found') + toast.error(translations.invalidUserOrPassword); + }); + }; return (
@@ -86,7 +85,7 @@ export default function LoginForm({ lang, region, translations }: LoginFormProps )} /> -
diff --git a/website/src/app/[lang]/[region]/(website)/login/page.tsx b/website/src/app/[lang]/[region]/(website)/login/page.tsx index 0adadd485..825244a03 100644 --- a/website/src/app/[lang]/[region]/(website)/login/page.tsx +++ b/website/src/app/[lang]/[region]/(website)/login/page.tsx @@ -1,14 +1,47 @@ -import { DefaultPageProps } from '@/app/[lang]/[region]'; +import { DefaultLayoutProps, DefaultPageProps } from '@/app/[lang]/[region]'; import LoginForm from '@/app/[lang]/[region]/(website)/login/login-form'; import ResetPasswordDialog from '@/app/[lang]/[region]/(website)/login/reset-password-dialog'; import { SocialSignInButtons } from '@/app/[lang]/[region]/(website)/login/social-sign-in-buttons'; +import { getMetadata } from '@/metadata'; +import { InformationCircleIcon } from '@heroicons/react/24/outline'; import { Translator } from '@socialincome/shared/src/utils/i18n'; +import { + Alert, + AlertDescription, + AlertTitle, + BaseContainer, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, + Typography, +} from '@socialincome/ui'; + +export async function generateMetadata({ params }: DefaultLayoutProps) { + return getMetadata(params.lang, 'website-login'); +} export default async function Page({ params }: DefaultPageProps) { const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-login'] }); return ( -
+ + + + {translator.t('alert.title')} + + + + + + {translator.t('alert.tooltip')} + + + + + {translator.t('alert.text')} + + -
+ ); } diff --git a/website/src/app/[lang]/[region]/(website)/login/reset-password-dialog.tsx b/website/src/app/[lang]/[region]/(website)/login/reset-password-dialog.tsx index 6bf026d36..b5958aced 100644 --- a/website/src/app/[lang]/[region]/(website)/login/reset-password-dialog.tsx +++ b/website/src/app/[lang]/[region]/(website)/login/reset-password-dialog.tsx @@ -39,6 +39,7 @@ type PasswordResetDialogProps = { export default function ResetPasswordDialog({ translations }: PasswordResetDialogProps) { const auth = useAuth(); const [dialogOpen, setDialogOpen] = useState(false); + const [submitting, setSubmitting] = useState(false); const formSchema = z.object({ email: z.string().email({ message: translations.invalidEmail }), @@ -51,6 +52,7 @@ export default function ResetPasswordDialog({ translations }: PasswordResetDialo }); const onSubmit = async (values: { email: string }) => { + setSubmitting(true); await sendPasswordResetEmail(auth, values.email) .catch(async (error: FirebaseError) => { // If the auth user does not exist, we need to call our API and check if there exists a firestore user with the @@ -66,6 +68,7 @@ export default function ResetPasswordDialog({ translations }: PasswordResetDialo .finally(() => { toast.success(translations.resetPasswordToastMessage); setDialogOpen(false); + setSubmitting(false); }); }; @@ -74,10 +77,10 @@ export default function ResetPasswordDialog({ translations }: PasswordResetDialo - +
- {translations.resetPasswordTitle} + {translations.resetPasswordTitle} )} /> - +
diff --git a/website/src/app/[lang]/[region]/(website)/me/layout-client.tsx b/website/src/app/[lang]/[region]/(website)/me/layout-client.tsx index 20ed346e9..81b326481 100644 --- a/website/src/app/[lang]/[region]/(website)/me/layout-client.tsx +++ b/website/src/app/[lang]/[region]/(website)/me/layout-client.tsx @@ -3,6 +3,7 @@ import { DefaultParams } from '@/app/[lang]/[region]'; import { ArrowPathIcon, CurrencyDollarIcon, ShieldCheckIcon, UserCircleIcon } from '@heroicons/react/24/outline'; import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, Typography } from '@socialincome/ui'; +import { LinkProps } from 'next/dist/client/link'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import * as React from 'react'; @@ -11,12 +12,12 @@ import { PropsWithChildren, useState } from 'react'; type NavigationLinkProps = { href: string; Icon: React.ForwardRefExoticComponent>>; -}; +} & LinkProps; -function NavigationLink({ href, Icon, children }: PropsWithChildren) { +function NavigationLink({ href, Icon, children, ...props }: PropsWithChildren) { return ( - -
  • + +
  • {children}
  • @@ -26,7 +27,7 @@ function NavigationLink({ href, Icon, children }: PropsWithChildren + {children} ); @@ -51,17 +52,33 @@ export function LayoutClient({ params, translations, children }: PropsWithChildr const navigationMenu = (
      {translations.contributionsTitle} - + setIsOpen(false)} + > {translations.payments} - + setIsOpen(false)} + > {translations.subscriptions} {translations.accountTitle} - + setIsOpen(false)} + > {translations.personalInfo} - + setIsOpen(false)} + > {translations.security}
    @@ -100,7 +117,7 @@ export function LayoutClient({ params, translations, children }: PropsWithChildr
    {isOpen && navigationMenu} - + {title} {children} diff --git a/website/src/app/[lang]/[region]/(website)/me/payments/contributions-table.tsx b/website/src/app/[lang]/[region]/(website)/me/payments/contributions-table.tsx index 25cf1843e..046482c46 100644 --- a/website/src/app/[lang]/[region]/(website)/me/payments/contributions-table.tsx +++ b/website/src/app/[lang]/[region]/(website)/me/payments/contributions-table.tsx @@ -75,11 +75,11 @@ export function ContributionsTable({ lang, translations }: ContributionsTablePro })} - {translator?.t('contributions.total')} + {translator?.t('contributions.total')} - + {translator?.t('contributions.amount-currency', { context: { amount: _.sum(contributions?.docs.map((contribution) => contribution.get('amount'))), diff --git a/website/src/app/[lang]/[region]/(website)/me/security/sign-out-button.tsx b/website/src/app/[lang]/[region]/(website)/me/security/sign-out-button.tsx index 7b1f4025a..08af0d992 100644 --- a/website/src/app/[lang]/[region]/(website)/me/security/sign-out-button.tsx +++ b/website/src/app/[lang]/[region]/(website)/me/security/sign-out-button.tsx @@ -18,7 +18,7 @@ export function SignOutButton({ translations }: SignOutButtonProps) { return (
    - + {translations.title} ); diff --git a/website/src/app/[lang]/[region]/(website)/me/subscriptions/page.tsx b/website/src/app/[lang]/[region]/(website)/me/subscriptions/page.tsx index 5b1aae910..376253c12 100644 --- a/website/src/app/[lang]/[region]/(website)/me/subscriptions/page.tsx +++ b/website/src/app/[lang]/[region]/(website)/me/subscriptions/page.tsx @@ -1,10 +1,13 @@ import { DefaultPageProps } from '@/app/[lang]/[region]'; import { BillingPortalButton } from '@/app/[lang]/[region]/(website)/me/subscriptions/billing-portal-button'; import { SubscriptionsTable } from '@/app/[lang]/[region]/(website)/me/subscriptions/subscriptions-table'; +import { PlusCircleIcon } from '@heroicons/react/24/outline'; import { Translator } from '@socialincome/shared/src/utils/i18n'; +import { Button } from '@socialincome/ui'; +import Link from 'next/link'; -export default async function Page({ params }: DefaultPageProps) { - const translator = await Translator.getInstance({ language: params.lang, namespaces: ['website-me'] }); +export default async function Page({ params: { lang, region } }: DefaultPageProps) { + const translator = await Translator.getInstance({ language: lang, namespaces: ['website-me'] }); return (
    - +
    + + + + +
    ); } diff --git a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-orgs-carousel.tsx b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-orgs-carousel.tsx index 0c533d138..17def0021 100644 --- a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-orgs-carousel.tsx +++ b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-orgs-carousel.tsx @@ -26,6 +26,7 @@ export function ContributorsOrgsCarousel() { let slidesToScroll; switch (screenSize) { + case null: case 'xs': case 'sm': slidesToScroll = 2; diff --git a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-people-carousel.tsx b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-people-carousel.tsx index 83627aad7..7705fe519 100644 --- a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-people-carousel.tsx +++ b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/contributors-people-carousel.tsx @@ -18,7 +18,7 @@ function Portrait({ name, text, country, image }: PortraitProps) { return ( - Double quotes + Double quotes {text} @@ -42,6 +42,7 @@ export function ContributorsPeopleCarousel({ portraits }: { portraits: PortraitP let slidesToScroll; switch (screenSize) { + case null: case 'xs': slidesToScroll = 1; break; diff --git a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/our-work.tsx b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/our-work.tsx index 4c672ccb2..62b2b8e91 100644 --- a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/our-work.tsx +++ b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/our-work.tsx @@ -33,7 +33,7 @@ export async function OurWork({ params }: DefaultPageProps) { {translator.t('our-work.subtitle')}
    - + diff --git a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/recipients-carousel.tsx b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/recipients-carousel.tsx index a4bd9d9b5..7c224a833 100644 --- a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/recipients-carousel.tsx +++ b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/recipients-carousel.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useScreenSize } from '@/hooks/useScreenSize'; import { MapPinIcon } from '@heroicons/react/24/solid'; import { Card, Carousel, CarouselContent, Typography } from '@socialincome/ui'; import { StaticImport } from 'next/dist/shared/lib/get-img-props'; @@ -15,21 +16,21 @@ type PortraitProps = { function Portrait({ name, text, country, image }: PortraitProps) { return ( - -
    + +
    {`${name}
    -
    - double quotes - - {text} - +
    + double quotes + {text}
    - {name} + + {name} +
    @@ -43,6 +44,8 @@ function Portrait({ name, text, country, image }: PortraitProps) { } export function RecipientsCarousel({ portraits }: { portraits: PortraitProps[] }) { + const screenSize = useScreenSize(); + return ( {portraits.map((portrait, index) => ( diff --git a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/whats-next.tsx b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/whats-next.tsx index 55e80270a..a46258726 100644 --- a/website/src/app/[lang]/[region]/(website)/our-work/(sections)/whats-next.tsx +++ b/website/src/app/[lang]/[region]/(website)/our-work/(sections)/whats-next.tsx @@ -41,7 +41,7 @@ export async function WhatsNext({ params }: DefaultPageProps) {
    @@ -61,7 +61,7 @@ export async function WhatsNext({ params }: DefaultPageProps) {
      {timeline.map(({ Icon, iconColor, title, text }, index) => (
    • -
      +
      {index !== timeline.length - 1 ? (