mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 10:27:49 +00:00
feat: move email linking form into Connected Accounts page
Users couldn't find the email linking form buried in the Profile page. Now the Email row in Connected Accounts shows a "Привязать" button (consistent with Google/Yandex/Discord) that expands an inline form with email, password, and confirm password fields. - Added email to isLinkableProvider check - Inline AnimatePresence form expands under Email card (no popups) - Removed duplicate registration form from Profile page - Profile page now shows a redirect button to Connected Accounts for users without email (email change/verify stays in Profile)
This commit is contained in:
@@ -67,9 +67,6 @@ export default function Profile() {
|
||||
const setUser = useAuthStore((state) => state.setUser);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
@@ -145,37 +142,6 @@ export default function Profile() {
|
||||
window.open(telegramUrl, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const registerEmailMutation = useMutation({
|
||||
mutationFn: ({ email, password }: { email: string; password: string }) =>
|
||||
authApi.registerEmail(email, password),
|
||||
onSuccess: async (response) => {
|
||||
// Backend returns merge_required when email belongs to another user
|
||||
if (response.merge_required && response.merge_token) {
|
||||
navigate(`/merge/${response.merge_token}`, { replace: true });
|
||||
return;
|
||||
}
|
||||
setSuccess(t('profile.emailSent'));
|
||||
setError(null);
|
||||
setEmail('');
|
||||
setPassword('');
|
||||
setConfirmPassword('');
|
||||
const updatedUser = await authApi.getMe();
|
||||
setUser(updatedUser);
|
||||
queryClient.invalidateQueries({ queryKey: ['user'] });
|
||||
},
|
||||
onError: (err: { response?: { data?: { detail?: string } } }) => {
|
||||
const detail = err.response?.data?.detail;
|
||||
if (detail?.includes('already registered')) {
|
||||
setError(t('profile.emailAlreadyRegistered'));
|
||||
} else if (detail?.includes('already have a verified email')) {
|
||||
setError(t('profile.alreadyHaveEmail'));
|
||||
} else {
|
||||
setError(detail || t('common.error'));
|
||||
}
|
||||
setSuccess(null);
|
||||
},
|
||||
});
|
||||
|
||||
const resendVerificationMutation = useMutation({
|
||||
mutationFn: authApi.resendVerification,
|
||||
onSuccess: () => {
|
||||
@@ -339,29 +305,6 @@ export default function Profile() {
|
||||
updateNotificationsMutation.mutate(update);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setSuccess(null);
|
||||
|
||||
if (!email.trim() || !isValidEmail(email.trim())) {
|
||||
setError(t('profile.invalidEmail', 'Please enter a valid email address'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password || password.length < 8) {
|
||||
setError(t('profile.passwordMinLength'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError(t('profile.passwordsMismatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
registerEmailMutation.mutate({ email, password });
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="space-y-6"
|
||||
@@ -669,60 +612,11 @@ export default function Profile() {
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p className="mb-6 text-sm text-dark-400">{t('profile.linkEmailDescription')}</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label className="label">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">{t('auth.password')}</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder={t('profile.passwordPlaceholder')}
|
||||
className="input"
|
||||
/>
|
||||
<p className="mt-2 text-xs text-dark-500">{t('profile.passwordHint')}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">{t('auth.confirmPassword')}</label>
|
||||
<input
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder={t('profile.confirmPasswordPlaceholder')}
|
||||
className="input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-linear border border-error-500/30 bg-error-500/10 p-4 text-sm text-error-400">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<div className="rounded-linear border border-success-500/30 bg-success-500/10 p-4 text-sm text-success-400">
|
||||
{success}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button type="submit" fullWidth loading={registerEmailMutation.isPending}>
|
||||
{t('profile.linkEmail')}
|
||||
</Button>
|
||||
</form>
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-dark-400">{t('profile.linkEmailDescription')}</p>
|
||||
<Button variant="primary" onClick={() => navigate('/profile/accounts')}>
|
||||
{t('profile.linkEmail')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user