Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
@@ -1,3 +0,0 @@
|
||||
import eslintConfigBase from '@kit/eslint-config/base.js';
|
||||
|
||||
export default eslintConfigBase;
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "@kit/auth",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"format": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"private": true,
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
"./sign-in": "./src/sign-in.ts",
|
||||
@@ -19,33 +20,25 @@
|
||||
"./resend-email-link": "./src/components/resend-auth-link-form.tsx",
|
||||
"./oauth-provider-logo-image": "./src/components/oauth-provider-logo-image.tsx"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@hookform/resolvers": "catalog:",
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/supabase": "workspace:*",
|
||||
"@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",
|
||||
"sonner": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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: (
|
||||
|
||||
@@ -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" />,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ export const OauthProviders: React.FC<{
|
||||
}}
|
||||
>
|
||||
<Trans
|
||||
i18nKey={'auth:signInWithProvider'}
|
||||
i18nKey={'auth.signInWithProvider'}
|
||||
values={{
|
||||
provider: getProviderName(provider),
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { RefinedPasswordSchema, refineRepeatPassword } from './password.schema';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { PasswordSchema } from './password.schema';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { RefinedPasswordSchema, refineRepeatPassword } from './password.schema';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user