feat: add reset password functionality for users with social login
- Introduced `ResetPasswordCard` component to guide users who signed up with social providers in setting up a password through the forgot password flow. - Updated `ConditionalUpdatePasswordCard` to conditionally render `ResetPasswordCard` for users without a credential provider but with an email. - Enhanced localization files to include new messages for the password setup process in both English and Chinese. - Implemented email pre-filling in the forgot password form based on URL parameters for improved user experience.
This commit is contained in:
parent
4f8b6d56c9
commit
1fd3ef7eeb
@ -346,6 +346,12 @@
|
||||
"success": "Password updated successfully",
|
||||
"fail": "Failed to update password"
|
||||
},
|
||||
"setupPassword": {
|
||||
"title": "Set Up Password",
|
||||
"description": "Set up a password to enable email login.",
|
||||
"info": "Setting up a password will allow you to sign in using your email and password in addition to your social login methods. You will receive an email with instructions to set your password.",
|
||||
"button": "Set Up Password"
|
||||
},
|
||||
"deleteAccount": {
|
||||
"title": "Delete Account",
|
||||
"description": "Permanently remove your account and all of its contents",
|
||||
|
@ -341,6 +341,12 @@
|
||||
"success": "密码更新成功",
|
||||
"fail": "更新密码失败"
|
||||
},
|
||||
"setupPassword": {
|
||||
"title": "设置密码",
|
||||
"description": "设置密码以启用邮箱登录",
|
||||
"info": "设置密码将允许您除了社交登录方式外,还可以使用邮箱和密码登录,您将收到一封包含设置密码链接的电子邮件",
|
||||
"button": "设置密码"
|
||||
},
|
||||
"deleteAccount": {
|
||||
"title": "删除账号",
|
||||
"description": "永久删除您的账号和所有内容",
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { ForgotPasswordSchema } from '@/lib/schemas';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useState, useTransition } from 'react';
|
||||
import { useState, useTransition, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type * as z from 'zod';
|
||||
import { Icons } from '@/components/icons/icons';
|
||||
@ -23,12 +23,14 @@ import { authClient } from '@/lib/auth-client';
|
||||
import { Routes } from '@/routes';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
export const ForgotPasswordForm = ({ className }: { className?: string }) => {
|
||||
const [error, setError] = useState<string | undefined>('');
|
||||
const [success, setSuccess] = useState<string | undefined>('');
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const t = useTranslations('AuthPage.forgotPassword');
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const form = useForm<z.infer<typeof ForgotPasswordSchema>>({
|
||||
resolver: zodResolver(ForgotPasswordSchema),
|
||||
@ -37,6 +39,14 @@ export const ForgotPasswordForm = ({ className }: { className?: string }) => {
|
||||
},
|
||||
});
|
||||
|
||||
// Pre-fill the email field if it's provided in the URL
|
||||
useEffect(() => {
|
||||
const emailFromUrl = searchParams.get('email');
|
||||
if (emailFromUrl) {
|
||||
form.setValue('email', emailFromUrl);
|
||||
}
|
||||
}, [searchParams, form]);
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof ForgotPasswordSchema>) => {
|
||||
console.log('forgotPassword, values:', values);
|
||||
const { data, error } = await authClient.forgetPassword(
|
||||
|
@ -1,12 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import { UpdatePasswordCard } from '@/components/settings/account/update-password-card';
|
||||
import { ResetPasswordCard } from '@/components/settings/account/reset-password-card';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Conditionally renders the UpdatePasswordCard component
|
||||
* only if the user has a credential provider (email/password login)
|
||||
* Conditionally renders either:
|
||||
* - UpdatePasswordCard: if the user has a credential provider (email/password login)
|
||||
* - ResetPasswordCard: if the user only has social login providers and has an email
|
||||
* - Nothing: if the user has no credential provider and no email
|
||||
*/
|
||||
export function ConditionalUpdatePasswordCard() {
|
||||
const { data: session } = authClient.useSession();
|
||||
@ -27,11 +30,11 @@ export function ConditionalUpdatePasswordCard() {
|
||||
|
||||
// Check if the response is successful and contains accounts data
|
||||
if ('data' in accounts && Array.isArray(accounts.data)) {
|
||||
// Check if any account has a credential provider (provider === 'credentials')
|
||||
const hasCredentials = accounts.data.some(
|
||||
// Check if any account has a credential provider (provider === 'credential')
|
||||
const hasCredential = accounts.data.some(
|
||||
(account) => account.provider === 'credential'
|
||||
);
|
||||
setHasCredentialProvider(hasCredentials);
|
||||
setHasCredentialProvider(hasCredential);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking credential provider:', error);
|
||||
@ -43,10 +46,22 @@ export function ConditionalUpdatePasswordCard() {
|
||||
checkCredentialProvider();
|
||||
}, [session]);
|
||||
|
||||
// Don't render anything while loading or if user doesn't have credential provider
|
||||
if (isLoading || !hasCredentialProvider) {
|
||||
// Don't render anything while loading
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <UpdatePasswordCard />;
|
||||
// If user has credential provider, show UpdatePasswordCard
|
||||
if (hasCredentialProvider) {
|
||||
return <UpdatePasswordCard />;
|
||||
}
|
||||
|
||||
// If user doesn't have credential provider but has an email, show ResetPasswordCard
|
||||
// The forgot password flow requires an email address
|
||||
if (session?.user?.email) {
|
||||
return <ResetPasswordCard />;
|
||||
}
|
||||
|
||||
// If user has no credential provider and no email, don't show anything
|
||||
return null;
|
||||
}
|
70
src/components/settings/account/reset-password-card.tsx
Normal file
70
src/components/settings/account/reset-password-card.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '@/components/ui/card';
|
||||
import { useLocaleRouter } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
/**
|
||||
* Reset Password Card
|
||||
*
|
||||
* This component guides users who signed up with social providers
|
||||
* to set up a password through the forgot password flow.
|
||||
*
|
||||
* How it works:
|
||||
* 1. When a user signs in with a social provider, they don't have a password set up
|
||||
* 2. This card provides a way for them to set up a password using the forgot password flow
|
||||
* 3. The user clicks the button and is redirected to the forgot password page
|
||||
* 4. They enter their email (which is already associated with their account)
|
||||
* 5. They receive a password reset email
|
||||
* 6. After setting a password, they can now login with either:
|
||||
* - Their social provider (as before)
|
||||
* - Their email and the new password
|
||||
*
|
||||
* This effectively adds a credential provider to their account, enabling email/password login.
|
||||
*/
|
||||
export function ResetPasswordCard() {
|
||||
const router = useLocaleRouter();
|
||||
const t = useTranslations('Dashboard.sidebar.settings.items.account');
|
||||
const { data: session } = authClient.useSession();
|
||||
|
||||
const handleSetupPassword = () => {
|
||||
// Pre-fill the email if available to make it easier for the user
|
||||
if (session?.user?.email) {
|
||||
router.push(`/auth/forgot-password?email=${encodeURIComponent(session.user.email)}`);
|
||||
} else {
|
||||
router.push('/auth/forgot-password');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="max-w-md md:max-w-lg overflow-hidden">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg font-bold">
|
||||
{t('setupPassword.title')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('setupPassword.description')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('setupPassword.info')}
|
||||
</p>
|
||||
</CardContent>
|
||||
<CardFooter className="px-6 py-4 flex justify-end items-center bg-muted rounded-none">
|
||||
<Button onClick={handleSetupPassword}>
|
||||
{t('setupPassword.button')}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -46,6 +46,7 @@ export const auth = betterAuth({
|
||||
// https://www.better-auth.com/docs/authentication/email-password#forget-password
|
||||
async sendResetPassword({ user, url }, request) {
|
||||
const locale = getLocaleFromRequest(request);
|
||||
// TODO: add locale to url
|
||||
await send({
|
||||
to: user.email,
|
||||
template: 'forgotPassword',
|
||||
@ -63,6 +64,7 @@ export const auth = betterAuth({
|
||||
// https://www.better-auth.com/docs/authentication/email-password#require-email-verification
|
||||
sendVerificationEmail: async ({ user, url, token }, request) => {
|
||||
const locale = getLocaleFromRequest(request);
|
||||
// TODO: add locale to url
|
||||
await send({
|
||||
to: user.email,
|
||||
template: 'verifyEmail',
|
||||
|
Loading…
Reference in New Issue
Block a user