Unify workspace dropdowns; Update layouts (#458)

Unified Account and Workspace drop-downs; Layout updates, now header lives within the PageBody component; Sidebars now use floating variant
This commit is contained in:
Giancarlo Buomprisco
2026-03-11 14:45:42 +08:00
committed by GitHub
parent ca585e09be
commit 4bc8448a1d
530 changed files with 14398 additions and 11198 deletions

View File

@@ -28,15 +28,14 @@
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@marsidev/react-turnstile": "catalog:",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "catalog:",
"@tanstack/react-query": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:",
"lucide-react": "catalog:",
"next": "catalog:",
"next-intl": "catalog:",
"react-hook-form": "catalog:",
"react-i18next": "catalog:",
"sonner": "^2.0.7",
"zod": "catalog:"
},

View File

@@ -1,4 +1,4 @@
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { TriangleAlert } from 'lucide-react';
import {
WeakPasswordError,
@@ -33,23 +33,25 @@ export function AuthErrorAlert({
return <WeakPasswordErrorAlert reasons={error.reasons} />;
}
const DefaultError = <Trans i18nKey="auth:errors.default" />;
const errorCode = error instanceof Error ? error.message : error;
const DefaultError = <Trans i18nKey="auth.errors.default" />;
const errorCode =
error instanceof Error
? 'code' in error && typeof error.code === 'string'
? error.code
: error.message
: error;
return (
<Alert variant={'destructive'}>
<ExclamationTriangleIcon className={'w-4'} />
<TriangleAlert className={'w-4'} />
<AlertTitle>
<Trans i18nKey={`auth:errorAlertHeading`} />
<Trans i18nKey={`auth.errorAlertHeading`} />
</AlertTitle>
<AlertDescription data-test={'auth-error-message'}>
<Trans
i18nKey={`auth:errors.${errorCode}`}
defaults={'<DefaultError />'}
components={{ DefaultError }}
/>
<Trans i18nKey={`auth.errors.${errorCode}`} defaults={DefaultError} />
</AlertDescription>
</Alert>
);
@@ -62,21 +64,21 @@ function WeakPasswordErrorAlert({
}) {
return (
<Alert variant={'destructive'}>
<ExclamationTriangleIcon className={'w-4'} />
<TriangleAlert className={'w-4'} />
<AlertTitle>
<Trans i18nKey={'auth:errors.weakPassword.title'} />
<Trans i18nKey={'auth.errors.weakPassword.title'} />
</AlertTitle>
<AlertDescription data-test={'auth-error-message'}>
<Trans i18nKey={'auth:errors.weakPassword.description'} />
<Trans i18nKey={'auth.errors.weakPassword.description'} />
{reasons.length > 0 && (
<ul className="mt-2 list-inside list-disc space-y-1 text-xs">
{reasons.map((reason) => (
<li key={reason}>
<Trans
i18nKey={`auth:errors.weakPassword.reasons.${reason}`}
i18nKey={`auth.errors.weakPassword.reasons.${reason}`}
defaults={reason}
/>
</li>

View File

@@ -1,7 +1,7 @@
'use client';
import { Mail } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useTranslations } from 'next-intl';
import {
InputGroup,
@@ -10,7 +10,7 @@ import {
} from '@kit/ui/input-group';
export function EmailInput(props: React.ComponentProps<'input'>) {
const { t } = useTranslation('auth');
const t = useTranslations('auth');
return (
<InputGroup className="dark:bg-background">

View File

@@ -7,7 +7,7 @@ import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { UserCheck } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useTranslations } from 'next-intl';
import { Alert, AlertDescription } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
@@ -36,7 +36,7 @@ export function ExistingAccountHintImpl({
useLastAuthMethod();
const params = useSearchParams();
const { t } = useTranslation();
const t = useTranslations();
const isInvite = params.get('invite_token');
@@ -53,13 +53,13 @@ export function ExistingAccountHintImpl({
switch (methodType) {
case 'password':
return 'auth:methodPassword';
return 'auth.methodPassword';
case 'otp':
return 'auth:methodOtp';
return 'auth.methodOtp';
case 'magic_link':
return 'auth:methodMagicLink';
return 'auth.methodMagicLink';
default:
return 'auth:methodDefault';
return 'auth.methodDefault';
}
}, [methodType, isOAuth, providerName]);
@@ -73,10 +73,10 @@ export function ExistingAccountHintImpl({
<Alert data-test={'existing-account-hint'} className={className}>
<UserCheck className="h-4 w-4" />
<AlertDescription>
<AlertDescription className={'text-xs'}>
<Trans
i18nKey="auth:existingAccountHint"
values={{ method: t(methodDescription) }}
i18nKey="auth.existingAccountHint"
values={{ methodName: t(methodDescription) }}
components={{
method: <span className="font-medium" />,
signInLink: (

View File

@@ -32,13 +32,13 @@ function LastAuthMethodHintImpl({ className }: LastAuthMethodHintProps) {
const methodKey = useMemo(() => {
switch (methodType) {
case 'password':
return 'auth:methodPassword';
return 'auth.methodPassword';
case 'otp':
return 'auth:methodOtp';
return 'auth.methodOtp';
case 'magic_link':
return 'auth:methodMagicLink';
return 'auth.methodMagicLink';
case 'oauth':
return 'auth:methodOauth';
return 'auth.methodOauth';
default:
return null;
}
@@ -61,10 +61,10 @@ function LastAuthMethodHintImpl({ className }: LastAuthMethodHintProps) {
<Lightbulb className="h-3 w-3" />
<span>
<Trans i18nKey="auth:lastUsedMethodPrefix" />{' '}
<Trans i18nKey="auth.lastUsedMethodPrefix" />{' '}
<If condition={isOAuth && Boolean(providerName)}>
<Trans
i18nKey="auth:methodOauthWithProvider"
i18nKey="auth.methodOauthWithProvider"
values={{ provider: providerName }}
components={{
provider: <span className="text-muted-foreground font-medium" />,

View File

@@ -1,10 +1,10 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { CheckIcon, ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { Check, TriangleAlert } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import * as z from 'zod';
import { useAppEvents } from '@kit/shared/events';
import { useSignInWithOtp } from '@kit/supabase/hooks/use-sign-in-with-otp';
@@ -44,7 +44,7 @@ export function MagicLinkAuthContainer({
};
}) {
const captcha = useCaptcha({ siteKey: captchaSiteKey });
const { t } = useTranslation();
const t = useTranslations();
const signInWithOtpMutation = useSignInWithOtp();
const appEvents = useAppEvents();
const { recordAuthMethod } = useLastAuthMethod();
@@ -90,9 +90,9 @@ export function MagicLinkAuthContainer({
};
toast.promise(promise, {
loading: t('auth:sendingEmailLink'),
success: t(`auth:sendLinkSuccessToast`),
error: t(`auth:errors.linkTitle`),
loading: t('auth.sendingEmailLink'),
success: t(`auth.sendLinkSuccessToast`),
error: t(`auth.errors.linkTitle`),
});
captcha.reset();
@@ -116,7 +116,7 @@ export function MagicLinkAuthContainer({
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:emailAddress'} />
<Trans i18nKey={'common.emailAddress'} />
</FormLabel>
<FormControl>
@@ -133,17 +133,20 @@ export function MagicLinkAuthContainer({
<TermsAndConditionsFormField />
</If>
<Button disabled={signInWithOtpMutation.isPending || captchaLoading}>
<Button
type="submit"
disabled={signInWithOtpMutation.isPending || captchaLoading}
>
<If condition={captchaLoading}>
<Trans i18nKey={'auth:verifyingCaptcha'} />
<Trans i18nKey={'auth.verifyingCaptcha'} />
</If>
<If condition={signInWithOtpMutation.isPending && !captchaLoading}>
<Trans i18nKey={'auth:sendingEmailLink'} />
<Trans i18nKey={'auth.sendingEmailLink'} />
</If>
<If condition={!signInWithOtpMutation.isPending && !captchaLoading}>
<Trans i18nKey={'auth:sendEmailLink'} />
<Trans i18nKey={'auth.sendEmailLink'} />
</If>
</Button>
</div>
@@ -155,14 +158,14 @@ export function MagicLinkAuthContainer({
function SuccessAlert() {
return (
<Alert variant={'success'}>
<CheckIcon className={'h-4'} />
<Check className={'h-4'} />
<AlertTitle>
<Trans i18nKey={'auth:sendLinkSuccess'} />
<Trans i18nKey={'auth.sendLinkSuccess'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'auth:sendLinkSuccessDescription'} />
<Trans i18nKey={'auth.sendLinkSuccessDescription'} />
</AlertDescription>
</Alert>
);
@@ -171,14 +174,14 @@ function SuccessAlert() {
function ErrorAlert() {
return (
<Alert variant={'destructive'}>
<ExclamationTriangleIcon className={'h-4'} />
<TriangleAlert className={'h-4'} />
<AlertTitle>
<Trans i18nKey={'auth:errors.linkTitle'} />
<Trans i18nKey={'auth.errors.linkTitle'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'auth:errors.linkDescription'} />
<Trans i18nKey={'auth.errors.linkDescription'} />
</AlertDescription>
</Alert>
);

View File

@@ -5,10 +5,10 @@ import { useEffect, useEffectEvent } from 'react';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { useMutation } from '@tanstack/react-query';
import { TriangleAlert } from 'lucide-react';
import { useForm, useWatch } from 'react-hook-form';
import { z } from 'zod';
import * as z from 'zod';
import { useFetchAuthFactors } from '@kit/supabase/hooks/use-fetch-mfa-factors';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
@@ -94,7 +94,7 @@ export function MultiFactorChallengeContainer({
<div className={'flex flex-col items-center gap-y-6'}>
<div className="flex flex-col items-center gap-y-4">
<Heading level={5}>
<Trans i18nKey={'auth:verifyCodeHeading'} />
<Trans i18nKey={'auth.verifyCodeHeading'} />
</Heading>
</div>
@@ -102,15 +102,15 @@ export function MultiFactorChallengeContainer({
<div className={'flex flex-col gap-y-4'}>
<If condition={verifyMFAChallenge.error}>
<Alert variant={'destructive'}>
<ExclamationTriangleIcon className={'h-5'} />
<TriangleAlert className={'h-5'} />
<AlertTitle>
<Trans i18nKey={'account:invalidVerificationCodeHeading'} />
<Trans i18nKey={'account.invalidVerificationCodeHeading'} />
</AlertTitle>
<AlertDescription>
<Trans
i18nKey={'account:invalidVerificationCodeDescription'}
i18nKey={'account.invalidVerificationCodeDescription'}
/>
</AlertDescription>
</Alert>
@@ -143,7 +143,7 @@ export function MultiFactorChallengeContainer({
<FormDescription className="text-center">
<Trans
i18nKey={'account:verifyActivationCodeDescription'}
i18nKey={'account.verifyActivationCodeDescription'}
/>
</FormDescription>
@@ -156,6 +156,7 @@ export function MultiFactorChallengeContainer({
</div>
<Button
type="submit"
className="w-full"
data-test={'submit-mfa-button'}
disabled={
@@ -166,13 +167,13 @@ export function MultiFactorChallengeContainer({
>
<If condition={verifyMFAChallenge.isPending}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'account:verifyingCode'} />
<Trans i18nKey={'account.verifyingCode'} />
</span>
</If>
<If condition={verifyMFAChallenge.isSuccess}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'auth:redirecting'} />
<Trans i18nKey={'auth.redirecting'} />
</span>
</If>
@@ -181,7 +182,7 @@ export function MultiFactorChallengeContainer({
!verifyMFAChallenge.isPending && !verifyMFAChallenge.isSuccess
}
>
<Trans i18nKey={'account:submitVerificationCode'} />
<Trans i18nKey={'account.submitVerificationCode'} />
</If>
</Button>
</div>
@@ -255,7 +256,7 @@ function FactorsListContainer({
<Spinner />
<div className={'text-sm'}>
<Trans i18nKey={'account:loadingFactors'} />
<Trans i18nKey={'account.loadingFactors'} />
</div>
</div>
);
@@ -265,14 +266,14 @@ function FactorsListContainer({
return (
<div className={'w-full'}>
<Alert variant={'destructive'}>
<ExclamationTriangleIcon className={'h-4'} />
<TriangleAlert className={'h-4'} />
<AlertTitle>
<Trans i18nKey={'account:factorsListError'} />
<Trans i18nKey={'account.factorsListError'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'account:factorsListErrorDescription'} />
<Trans i18nKey={'account.factorsListErrorDescription'} />
</AlertDescription>
</Alert>
</div>
@@ -285,7 +286,7 @@ function FactorsListContainer({
<div className={'animate-in fade-in flex flex-col space-y-4 duration-500'}>
<div>
<span className={'font-medium'}>
<Trans i18nKey={'account:selectFactor'} />
<Trans i18nKey={'account.selectFactor'} />
</span>
</div>

View File

@@ -114,7 +114,7 @@ export const OauthProviders: React.FC<{
}}
>
<Trans
i18nKey={'auth:signInWithProvider'}
i18nKey={'auth.signInWithProvider'}
values={{
provider: getProviderName(provider),
}}

View File

@@ -4,7 +4,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm, useWatch } from 'react-hook-form';
import { z } from 'zod';
import * as z from 'zod';
import { useSignInWithOtp } from '@kit/supabase/hooks/use-sign-in-with-otp';
import { useVerifyOtp } from '@kit/supabase/hooks/use-verify-otp';
@@ -132,7 +132,7 @@ export function OtpSignInContainer(props: OtpSignInContainerProps) {
</FormControl>
<FormDescription>
<Trans i18nKey="common:otp.enterCodeFromEmail" />
<Trans i18nKey="common.otp.enterCodeFromEmail" />
</FormDescription>
<FormMessage />
@@ -149,10 +149,10 @@ export function OtpSignInContainer(props: OtpSignInContainerProps) {
{verifyMutation.isPending ? (
<>
<Spinner className="mr-2 h-4 w-4" />
<Trans i18nKey="common:otp.verifying" />
<Trans i18nKey="common.otp.verifying" />
</>
) : (
<Trans i18nKey="common:otp.verifyCode" />
<Trans i18nKey="common.otp.verifyCode" />
)}
</Button>
@@ -166,7 +166,7 @@ export function OtpSignInContainer(props: OtpSignInContainerProps) {
});
}}
>
<Trans i18nKey="common:otp.requestNewCode" />
<Trans i18nKey="common.otp.requestNewCode" />
</Button>
</div>
</form>
@@ -191,7 +191,7 @@ function OtpEmailForm({
defaultValues: { email: '' },
});
const handleSendOtp = async ({ email }: z.infer<typeof EmailSchema>) => {
const handleSendOtp = async ({ email }: z.output<typeof EmailSchema>) => {
await signInMutation.mutateAsync({
email,
options: { captchaToken: captcha.token, shouldCreateUser },
@@ -230,10 +230,10 @@ function OtpEmailForm({
{signInMutation.isPending ? (
<>
<Spinner className="mr-2 h-4 w-4" />
<Trans i18nKey="common:otp.sendingCode" />
<Trans i18nKey="common.otp.sendingCode" />
</>
) : (
<Trans i18nKey="common:otp.sendVerificationCode" />
<Trans i18nKey="common.otp.sendVerificationCode" />
)}
</Button>
</form>

View File

@@ -1,9 +1,9 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslations } from 'next-intl';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import * as z from 'zod';
import { useRequestResetPassword } from '@kit/supabase/hooks/use-request-reset-password';
import { Alert, AlertDescription } from '@kit/ui/alert';
@@ -31,7 +31,7 @@ export function PasswordResetRequestContainer(params: {
redirectPath: string;
captchaSiteKey?: string;
}) {
const { t } = useTranslation('auth');
const t = useTranslations('auth');
const resetPasswordMutation = useRequestResetPassword();
const captcha = useCaptcha({ siteKey: params.captchaSiteKey });
const captchaLoading = !captcha.isReady;
@@ -51,7 +51,7 @@ export function PasswordResetRequestContainer(params: {
<If condition={success}>
<Alert variant={'success'}>
<AlertDescription>
<Trans i18nKey={'auth:passwordResetSuccessMessage'} />
<Trans i18nKey={'auth.passwordResetSuccessMessage'} />
</AlertDescription>
</Alert>
</If>
@@ -85,7 +85,7 @@ export function PasswordResetRequestContainer(params: {
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:emailAddress'} />
<Trans i18nKey={'common.emailAddress'} />
</FormLabel>
<FormControl>
@@ -111,15 +111,15 @@ export function PasswordResetRequestContainer(params: {
!resetPasswordMutation.isPending && !captchaLoading
}
>
<Trans i18nKey={'auth:passwordResetLabel'} />
<Trans i18nKey={'auth.passwordResetLabel'} />
</If>
<If condition={resetPasswordMutation.isPending}>
<Trans i18nKey={'auth:passwordResetLabel'} />
<Trans i18nKey={'auth.passwordResetLabel'} />
</If>
<If condition={captchaLoading}>
<Trans i18nKey={'auth:verifyingCaptcha'} />
<Trans i18nKey={'auth.verifyingCaptcha'} />
</If>
</Button>
</div>

View File

@@ -27,7 +27,7 @@ export function PasswordSignInContainer({
const captchaLoading = !captcha.isReady;
const onSubmit = useCallback(
async (credentials: z.infer<typeof PasswordSignInSchema>) => {
async (credentials: z.output<typeof PasswordSignInSchema>) => {
try {
const data = await signInMutation.mutateAsync({
...credentials,

View File

@@ -4,8 +4,8 @@ import Link from 'next/link';
import { zodResolver } from '@hookform/resolvers/zod';
import { ArrowRight, Mail } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { z } from 'zod';
import { Button } from '@kit/ui/button';
@@ -33,12 +33,12 @@ export function PasswordSignInForm({
loading = false,
redirecting = false,
}: {
onSubmit: (params: z.infer<typeof PasswordSignInSchema>) => unknown;
onSubmit: (params: z.output<typeof PasswordSignInSchema>) => unknown;
captchaLoading: boolean;
loading: boolean;
redirecting: boolean;
}) {
const { t } = useTranslation('auth');
const t = useTranslations('auth');
const form = useForm({
resolver: zodResolver(PasswordSignInSchema),
@@ -94,15 +94,14 @@ export function PasswordSignInForm({
<div>
<Button
asChild
nativeButton={false}
render={<Link href={'/auth/password-reset'} />}
type={'button'}
size={'sm'}
variant={'link'}
className={'text-xs'}
>
<Link href={'/auth/password-reset'}>
<Trans i18nKey={'auth:passwordForgottenQuestion'} />
</Link>
<Trans i18nKey={'auth.passwordForgottenQuestion'} />
</Button>
</div>
</FormItem>
@@ -118,19 +117,19 @@ export function PasswordSignInForm({
>
<If condition={redirecting}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'auth:redirecting'} />
<Trans i18nKey={'auth.redirecting'} />
</span>
</If>
<If condition={loading}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'auth:signingIn'} />
<Trans i18nKey={'auth.signingIn'} />
</span>
</If>
<If condition={captchaLoading}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'auth:verifyingCaptcha'} />
<Trans i18nKey={'auth.verifyingCaptcha'} />
</span>
</If>
@@ -140,7 +139,7 @@ export function PasswordSignInForm({
'animate-in fade-in slide-in-from-bottom-24 flex items-center'
}
>
<Trans i18nKey={'auth:signInWithEmail'} />
<Trans i18nKey={'auth.signInWithEmail'} />
<ArrowRight
className={

View File

@@ -1,6 +1,6 @@
'use client';
import { CheckCircledIcon } from '@radix-ui/react-icons';
import { CheckCircle } from 'lucide-react';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { If } from '@kit/ui/if';
@@ -71,14 +71,14 @@ export function EmailPasswordSignUpContainer({
function SuccessAlert() {
return (
<Alert variant={'success'}>
<CheckCircledIcon className={'w-4'} />
<CheckCircle className={'w-4'} />
<AlertTitle>
<Trans i18nKey={'auth:emailConfirmationAlertHeading'} />
<Trans i18nKey={'auth.emailConfirmationAlertHeading'} />
</AlertTitle>
<AlertDescription data-test={'email-confirmation-alert'}>
<Trans i18nKey={'auth:emailConfirmationAlertBody'} />
<Trans i18nKey={'auth.emailConfirmationAlertBody'} />
</AlertDescription>
</Alert>
);

View File

@@ -102,7 +102,7 @@ export function PasswordSignUpForm({
</FormControl>
<FormDescription>
<Trans i18nKey={'auth:repeatPasswordDescription'} />
<Trans i18nKey={'auth.repeatPasswordDescription'} />
</FormDescription>
<FormMessage />
@@ -123,13 +123,13 @@ export function PasswordSignUpForm({
>
<If condition={captchaLoading}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'auth:verifyingCaptcha'} />
<Trans i18nKey={'auth.verifyingCaptcha'} />
</span>
</If>
<If condition={loading && !captchaLoading}>
<span className={'animate-in fade-in slide-in-from-bottom-24'}>
<Trans i18nKey={'auth:signingUp'} />
<Trans i18nKey={'auth.signingUp'} />
</span>
</If>
@@ -139,7 +139,7 @@ export function PasswordSignUpForm({
'animate-in fade-in slide-in-from-bottom-24 flex items-center'
}
>
<Trans i18nKey={'auth:signUpWithEmail'} />
<Trans i18nKey={'auth.signUpWithEmail'} />
<ArrowRight
className={

View File

@@ -3,7 +3,7 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import * as z from 'zod';
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
@@ -40,12 +40,12 @@ export function ResendAuthLinkForm(props: {
return (
<Alert variant={'success'}>
<AlertTitle>
<Trans i18nKey={'auth:resendLinkSuccess'} />
<Trans i18nKey={'auth.resendLinkSuccess'} />
</AlertTitle>
<AlertDescription>
<Trans
i18nKey={'auth:resendLinkSuccessDescription'}
i18nKey={'auth.resendLinkSuccessDescription'}
defaults={'Success!'}
/>
</AlertDescription>
@@ -85,17 +85,17 @@ export function ResendAuthLinkForm(props: {
}}
/>
<Button disabled={resendLink.isPending || captchaLoading}>
<Button type="submit" disabled={resendLink.isPending || captchaLoading}>
<If condition={captchaLoading}>
<Trans i18nKey={'auth:verifyingCaptcha'} />
<Trans i18nKey={'auth.verifyingCaptcha'} />
</If>
<If condition={resendLink.isPending && !captchaLoading}>
<Trans i18nKey={'auth:resendingLink'} />
<Trans i18nKey={'auth.resendingLink'} />
</If>
<If condition={!resendLink.isPending && !captchaLoading}>
<Trans i18nKey={'auth:resendLink'} defaults={'Resend Link'} />
<Trans i18nKey={'auth.resendLink'} defaults={'Resend Link'} />
</If>
</Button>
</form>

View File

@@ -86,7 +86,7 @@ export function SignInMethodsContainer(props: {
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">
<Trans i18nKey="auth:orContinueWith" />
<Trans i18nKey="auth.orContinueWith" />
</span>
</div>
</div>

View File

@@ -78,7 +78,7 @@ export function SignUpMethodsContainer(props: {
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">
<Trans i18nKey="auth:orContinueWith" />
<Trans i18nKey="auth.orContinueWith" />
</span>
</div>
</div>

View File

@@ -21,7 +21,7 @@ export function TermsAndConditionsFormField(
<div className={'text-xs'}>
<Trans
i18nKey={'auth:acceptTermsAndConditions'}
i18nKey={'auth.acceptTermsAndConditions'}
components={{
TermsOfServiceLink: (
<Link
@@ -29,7 +29,7 @@ export function TermsAndConditionsFormField(
className={'underline'}
href={'/terms-of-service'}
>
<Trans i18nKey={'auth:termsOfService'} />
<Trans i18nKey={'auth.termsOfService'} />
</Link>
),
PrivacyPolicyLink: (
@@ -38,7 +38,7 @@ export function TermsAndConditionsFormField(
className={'underline'}
href={'/privacy-policy'}
>
<Trans i18nKey={'auth:privacyPolicy'} />
<Trans i18nKey={'auth.privacyPolicy'} />
</Link>
),
}}

View File

@@ -3,9 +3,9 @@
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { TriangleAlert } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import { useUpdateUser } from '@kit/supabase/hooks/use-update-user-mutation';
@@ -31,7 +31,7 @@ export function UpdatePasswordForm(params: {
}) {
const updateUser = useUpdateUser();
const router = useRouter();
const { t } = useTranslation();
const t = useTranslations();
const form = useForm({
resolver: zodResolver(PasswordResetSchema),
@@ -68,7 +68,7 @@ export function UpdatePasswordForm(params: {
router.replace(params.redirectTo);
toast.success(t('account:updatePasswordSuccessMessage'));
toast.success(t('account.updatePasswordSuccessMessage'));
})}
>
<div className={'flex-col space-y-2.5'}>
@@ -94,7 +94,7 @@ export function UpdatePasswordForm(params: {
</FormControl>
<FormDescription>
<Trans i18nKey={'common:repeatPassword'} />
<Trans i18nKey={'common.repeatPassword'} />
</FormDescription>
<FormMessage />
@@ -107,7 +107,7 @@ export function UpdatePasswordForm(params: {
type="submit"
className={'w-full'}
>
<Trans i18nKey={'auth:passwordResetLabel'} />
<Trans i18nKey={'auth.passwordResetLabel'} />
</Button>
</div>
</form>
@@ -122,7 +122,7 @@ function ErrorState(props: {
code: string;
};
}) {
const { t } = useTranslation('auth');
const t = useTranslations('auth');
const errorMessage = t(`errors.${props.error.code}`, {
defaultValue: t('errors.resetPasswordError'),
@@ -131,17 +131,17 @@ function ErrorState(props: {
return (
<div className={'flex flex-col space-y-4'}>
<Alert variant={'destructive'}>
<ExclamationTriangleIcon className={'s-6'} />
<TriangleAlert className={'s-6'} />
<AlertTitle>
<Trans i18nKey={'common:genericError'} />
<Trans i18nKey={'common.genericError'} />
</AlertTitle>
<AlertDescription>{errorMessage}</AlertDescription>
</Alert>
<Button onClick={props.onRetry} variant={'outline'}>
<Trans i18nKey={'common:retry'} />
<Trans i18nKey={'common.retry'} />
</Button>
</div>
);

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
import { RefinedPasswordSchema, refineRepeatPassword } from './password.schema';

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
import { PasswordSchema } from './password.schema';

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
import { RefinedPasswordSchema, refineRepeatPassword } from './password.schema';

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
/**
* Password requirements
@@ -36,13 +36,11 @@ export function refineRepeatPassword(
) {
if (data.password !== data.repeatPassword) {
ctx.addIssue({
message: 'auth:errors.passwordsDoNotMatch',
message: 'auth.errors.passwordsDoNotMatch',
path: ['repeatPassword'],
code: 'custom',
});
}
return true;
}
function validatePassword(password: string, ctx: z.RefinementCtx) {
@@ -52,7 +50,7 @@ function validatePassword(password: string, ctx: z.RefinementCtx) {
if (specialCharsCount < 1) {
ctx.addIssue({
message: 'auth:errors.minPasswordSpecialChars',
message: 'auth.errors.minPasswordSpecialChars',
code: 'custom',
});
}
@@ -63,7 +61,7 @@ function validatePassword(password: string, ctx: z.RefinementCtx) {
if (numbersCount < 1) {
ctx.addIssue({
message: 'auth:errors.minPasswordNumbers',
message: 'auth.errors.minPasswordNumbers',
code: 'custom',
});
}
@@ -72,11 +70,9 @@ function validatePassword(password: string, ctx: z.RefinementCtx) {
if (requirements.uppercase) {
if (!/[A-Z]/.test(password)) {
ctx.addIssue({
message: 'auth:errors.uppercasePassword',
message: 'auth.errors.uppercasePassword',
code: 'custom',
});
}
}
return true;
}