fix: polish authentication navigation and autofill
This commit is contained in:
@@ -30,6 +30,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Link } from "@/lib/router";
|
||||
import { buildAuthPath } from "@/lib/auth-return";
|
||||
import { cn, formatJalaliDate, resolveErrorMessage, toPersianDigits } from "@/lib/utils";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
|
||||
@@ -436,7 +437,7 @@ export default function BlogPostInteractions({
|
||||
<div className="rounded-3xl border border-dashed border-border/70 bg-muted/20 p-5 text-center text-sm leading-7 text-muted-foreground">
|
||||
برای ثبت کامنت باید وارد حساب کاربری شوید.
|
||||
<Button asChild className="mr-3" size="sm">
|
||||
<Link to="/auth">ورود / ثبتنام</Link>
|
||||
<Link to={buildAuthPath(`/blog/${slug}`)}>ورود / ثبتنام</Link>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CalendarDays, CircleUserRound, Home, Newspaper } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { Link } from "@/lib/router";
|
||||
import { buildAuthPath } from "@/lib/auth-return";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { SiteContent } from "@/lib/content-cms";
|
||||
|
||||
@@ -55,7 +56,7 @@ export default function MobileBottomNav({ siteContent }: { siteContent: SiteCont
|
||||
{
|
||||
key: "account",
|
||||
label: isAuthenticated ? "پروفایل" : "حساب",
|
||||
href: isAuthenticated ? "/profile" : "/auth",
|
||||
href: isAuthenticated ? "/profile" : buildAuthPath(pathname),
|
||||
icon: CircleUserRound,
|
||||
matches: isProfileOrAuthPath,
|
||||
},
|
||||
|
||||
@@ -16,14 +16,6 @@ export default function ModeToggle({ className }: { className?: string }) {
|
||||
setTheme(theme === "dark" ? "light" : "dark");
|
||||
};
|
||||
|
||||
const isDark =
|
||||
theme === "dark" ||
|
||||
(theme === "system" &&
|
||||
typeof document !== "undefined" &&
|
||||
document.documentElement.classList.contains("dark"));
|
||||
|
||||
const nextThemeLabel = isDark ? "روشن" : "تاریک";
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -32,8 +24,8 @@ export default function ModeToggle({ className }: { className?: string }) {
|
||||
"rounded-full border-0 bg-transparent text-foreground shadow-none transition hover:bg-secondary hover:text-secondary-foreground hover:shadow-sm dark:hover:bg-secondary dark:hover:text-secondary-foreground",
|
||||
className,
|
||||
)}
|
||||
aria-label={`تغییر تم به حالت ${nextThemeLabel}`}
|
||||
title={`تغییر تم به حالت ${nextThemeLabel}`}
|
||||
aria-label="تغییر حالت نمایش"
|
||||
title="تغییر حالت نمایش"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useMemo } from "react";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { LayoutDashboard, LogOut, UserRound } from "lucide-react";
|
||||
import { Link, NavLink } from "@/lib/router";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buildAuthPath } from "@/lib/auth-return";
|
||||
import type { SiteContent } from "@/lib/content-cms";
|
||||
|
||||
const NavItem = ({ to, children }: { to: string; children: ReactNode }) => (
|
||||
@@ -38,6 +40,7 @@ const NavItem = ({ to, children }: { to: string; children: ReactNode }) => (
|
||||
);
|
||||
|
||||
function ProfileAvatarMenu() {
|
||||
const pathname = usePathname() || "/";
|
||||
const { user, isAuthenticated } = useAuth();
|
||||
const isAdminUser = isAuthenticated && Boolean(user?.is_staff || user?.is_superuser);
|
||||
|
||||
@@ -52,7 +55,7 @@ function ProfileAvatarMenu() {
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<Link to="/auth">
|
||||
<Link to={buildAuthPath(pathname)}>
|
||||
<Button className="rounded-full px-5">ورود / ثبتنام</Button>
|
||||
</Link>
|
||||
);
|
||||
@@ -85,14 +88,14 @@ function ProfileAvatarMenu() {
|
||||
{[user?.first_name, user?.last_name].filter(Boolean).join(" ") || user?.username || "حساب کاربری"}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl">
|
||||
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl transition-colors hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground">
|
||||
<Link to="/profile">
|
||||
مشاهده پروفایل
|
||||
<UserRound className="h-4 w-4" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
{isAdminUser ? (
|
||||
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl">
|
||||
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl transition-colors hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground">
|
||||
<Link to="/admin">
|
||||
داشبورد مدیریت
|
||||
<LayoutDashboard className="h-4 w-4" />
|
||||
@@ -100,7 +103,7 @@ function ProfileAvatarMenu() {
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl text-destructive focus:text-destructive">
|
||||
<DropdownMenuItem asChild className="flex-row-reverse justify-end gap-2 rounded-xl text-destructive transition-colors hover:bg-secondary hover:text-destructive focus:bg-secondary focus:text-destructive">
|
||||
<Link to="/logout">
|
||||
خروج از حساب
|
||||
<LogOut className="h-4 w-4" />
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useNotifications } from "@/contexts/NotificationsContext";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { api } from "@/lib/api";
|
||||
import { buildAuthPath } from "@/lib/auth-return";
|
||||
import { Link } from "@/lib/router";
|
||||
import type { NotificationDetailSchema, NotificationSchema } from "@/lib/types";
|
||||
import { cn, formatJalaliNumericDate } from "@/lib/utils";
|
||||
@@ -157,7 +158,7 @@ export default function NotificationsPageContent() {
|
||||
<main className="mx-auto max-w-6xl px-4 py-16 text-center" dir="rtl">
|
||||
<h1 className="text-2xl font-bold">اعلانهای من</h1>
|
||||
<p className="mt-4 text-muted-foreground">برای دیدن اعلانهای خود وارد حساب شوید.</p>
|
||||
<Button asChild className="mt-6"><Link to="/auth">ورود به حساب</Link></Button>
|
||||
<Button asChild className="mt-6"><Link to={buildAuthPath("/notifications")}>ورود به حساب</Link></Button>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type * as Types from './types';
|
||||
import { apiBaseUrl } from '@/lib/site';
|
||||
import { AUTH_RETURN_TO_STORAGE_KEY, DEFAULT_AUTH_RETURN_TO, getSafeAuthReturnTo } from '@/lib/auth-return';
|
||||
|
||||
const API_BASE_URL =
|
||||
apiBaseUrl;
|
||||
@@ -221,8 +222,13 @@ class ApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async startGoogleLogin() {
|
||||
async startGoogleLogin(returnTo = DEFAULT_AUTH_RETURN_TO) {
|
||||
if (typeof window !== 'undefined') {
|
||||
try {
|
||||
window.sessionStorage.setItem(AUTH_RETURN_TO_STORAGE_KEY, getSafeAuthReturnTo(returnTo));
|
||||
} catch {
|
||||
// The Google flow can still continue if session storage is unavailable.
|
||||
}
|
||||
window.location.href = `${this.baseUrl}/api/auth/oauth/google/start`;
|
||||
}
|
||||
}
|
||||
|
||||
62
src/lib/auth-return.ts
Normal file
62
src/lib/auth-return.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export const DEFAULT_AUTH_RETURN_TO = "/profile";
|
||||
export const AUTH_RETURN_TO_STORAGE_KEY = "auth_return_to";
|
||||
|
||||
const BLOCKED_AUTH_PATHS = ["/auth", "/logout", "/reset-password"];
|
||||
const LOCAL_ORIGIN = "https://guilan-ace.invalid";
|
||||
|
||||
export function getSafeAuthReturnTo(value: string | null | undefined): string {
|
||||
const containsControlCharacter = value
|
||||
? Array.from(value).some((character) => {
|
||||
const codePoint = character.charCodeAt(0);
|
||||
return codePoint < 0x20 || codePoint === 0x7f;
|
||||
})
|
||||
: false;
|
||||
|
||||
if (!value || !value.startsWith("/") || value.startsWith("//") || value.includes("\\") || containsControlCharacter) {
|
||||
return DEFAULT_AUTH_RETURN_TO;
|
||||
}
|
||||
|
||||
try {
|
||||
const destination = new URL(value, LOCAL_ORIGIN);
|
||||
if (destination.origin !== LOCAL_ORIGIN) {
|
||||
return DEFAULT_AUTH_RETURN_TO;
|
||||
}
|
||||
|
||||
const pathname = destination.pathname.toLowerCase();
|
||||
if (BLOCKED_AUTH_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {
|
||||
return DEFAULT_AUTH_RETURN_TO;
|
||||
}
|
||||
|
||||
return `${destination.pathname}${destination.search}${destination.hash}`;
|
||||
} catch {
|
||||
return DEFAULT_AUTH_RETURN_TO;
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAuthPath(returnTo: string | null | undefined): string {
|
||||
return `/auth?next=${encodeURIComponent(getSafeAuthReturnTo(returnTo))}`;
|
||||
}
|
||||
|
||||
export function readStoredAuthReturnTo(): string {
|
||||
if (typeof window === "undefined") {
|
||||
return DEFAULT_AUTH_RETURN_TO;
|
||||
}
|
||||
|
||||
try {
|
||||
return getSafeAuthReturnTo(window.sessionStorage.getItem(AUTH_RETURN_TO_STORAGE_KEY));
|
||||
} catch {
|
||||
return DEFAULT_AUTH_RETURN_TO;
|
||||
}
|
||||
}
|
||||
|
||||
export function clearStoredAuthReturnTo(): void {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.sessionStorage.removeItem(AUTH_RETURN_TO_STORAGE_KEY);
|
||||
} catch {
|
||||
// Session storage can be unavailable in restricted browser contexts.
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { api } from "@/lib/api";
|
||||
import { clearStoredAuthReturnTo, DEFAULT_AUTH_RETURN_TO, getSafeAuthReturnTo } from "@/lib/auth-return";
|
||||
import { Navigate, useNavigate } from "@/lib/router";
|
||||
import { resolveErrorMessage } from "@/lib/utils";
|
||||
|
||||
@@ -72,6 +73,9 @@ export default function Auth() {
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const { login, loginWithOtp, isAuthenticated } = useAuth();
|
||||
const returnTo = typeof window === "undefined"
|
||||
? DEFAULT_AUTH_RETURN_TO
|
||||
: getSafeAuthReturnTo(new URLSearchParams(window.location.search).get("next"));
|
||||
const [step, setStep] = useState<AuthStep>("mobile");
|
||||
const [authLoading, setAuthLoading] = useState(false);
|
||||
const [formError, setFormError] = useState("");
|
||||
@@ -148,7 +152,7 @@ export default function Auth() {
|
||||
}, [step]);
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Navigate to="/profile" replace />;
|
||||
return <Navigate to={returnTo} replace />;
|
||||
}
|
||||
|
||||
const updateRegisterForm = <K extends keyof ReturnType<typeof createEmptyRegisterForm>>(
|
||||
@@ -252,9 +256,15 @@ export default function Auth() {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePasswordLogin = async (event: React.FormEvent) => {
|
||||
const handlePasswordLogin = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
if (!password) {
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const submittedMobile = ensureValidMobile(String(formData.get("username") ?? ""));
|
||||
const submittedPassword = String(formData.get("password") ?? "");
|
||||
if (!submittedMobile) {
|
||||
return;
|
||||
}
|
||||
if (!submittedPassword) {
|
||||
setFormError("برای ادامه، رمز عبور حساب را وارد کنید.");
|
||||
return;
|
||||
}
|
||||
@@ -263,13 +273,14 @@ export default function Auth() {
|
||||
}
|
||||
|
||||
try {
|
||||
await login(mobile, password);
|
||||
await login(submittedMobile, submittedPassword);
|
||||
toast({
|
||||
title: "ورود انجام شد",
|
||||
description: "خوش آمدید. حساب شما آماده استفاده است.",
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/profile");
|
||||
clearStoredAuthReturnTo();
|
||||
navigate(returnTo, { replace: true });
|
||||
} catch (error: unknown) {
|
||||
setFormError(resolveErrorMessage(error, "اطلاعات ورود صحیح نیست."));
|
||||
} finally {
|
||||
@@ -294,7 +305,8 @@ export default function Auth() {
|
||||
description: "با موفقیت وارد حساب کاربری خود شدید.",
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/profile");
|
||||
clearStoredAuthReturnTo();
|
||||
navigate(returnTo, { replace: true });
|
||||
} catch (error: unknown) {
|
||||
setFormError(resolveErrorMessage(error, "کد واردشده معتبر نیست."));
|
||||
} finally {
|
||||
@@ -376,7 +388,8 @@ export default function Auth() {
|
||||
description: "حساب شما ساخته شد و وارد سایت شدید.",
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/profile");
|
||||
clearStoredAuthReturnTo();
|
||||
navigate(returnTo, { replace: true });
|
||||
} catch {
|
||||
setStep("password");
|
||||
setFormError("حساب ساخته شد، اما ورود خودکار کامل نشد. با همین رمز وارد شوید.");
|
||||
@@ -388,14 +401,40 @@ export default function Auth() {
|
||||
}
|
||||
};
|
||||
|
||||
const renderStepHeader = () =>
|
||||
const renderStepHeader = (includeUsernameField = false) =>
|
||||
step !== "mobile" ? (
|
||||
<div className="mb-6 flex items-center justify-between rounded-[1.5rem] border border-border/70 bg-muted/25 p-4">
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-muted-foreground">شماره موبایل انتخابشده</p>
|
||||
<p className="mt-1 font-semibold" dir="ltr">
|
||||
{mobile}
|
||||
</p>
|
||||
<div className="min-w-0 text-right">
|
||||
{includeUsernameField ? (
|
||||
<>
|
||||
<Label htmlFor="auth-username" className="text-xs text-muted-foreground">
|
||||
شماره موبایل انتخابشده
|
||||
</Label>
|
||||
<Input
|
||||
id="auth-username"
|
||||
name="username"
|
||||
type="text"
|
||||
inputMode="tel"
|
||||
autoComplete="username"
|
||||
required
|
||||
maxLength={11}
|
||||
dir="ltr"
|
||||
value={mobile}
|
||||
onChange={(event) => {
|
||||
setMobile(sanitizeMobile(event.target.value));
|
||||
setFormError("");
|
||||
}}
|
||||
className="mt-1 h-auto rounded-md border-0 bg-transparent px-0 py-0 text-left text-base font-semibold shadow-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-xs text-muted-foreground">شماره موبایل انتخابشده</p>
|
||||
<p className="mt-1 font-semibold" dir="ltr">
|
||||
{mobile}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button type="button" variant="ghost" className="rounded-xl text-sm" onClick={resetToMobileStep} disabled={authLoading}>
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
@@ -458,7 +497,7 @@ export default function Auth() {
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="h-12 w-full rounded-2xl border-border/70 bg-background/80"
|
||||
onClick={() => void api.startGoogleLogin()}
|
||||
onClick={() => void api.startGoogleLogin(returnTo)}
|
||||
disabled={authLoading}
|
||||
>
|
||||
<GoogleIcon />
|
||||
@@ -469,7 +508,7 @@ export default function Auth() {
|
||||
|
||||
const renderPasswordStep = () => (
|
||||
<form className="space-y-4" onSubmit={handlePasswordLogin}>
|
||||
{renderStepHeader()}
|
||||
{renderStepHeader(true)}
|
||||
|
||||
<div>
|
||||
<Label htmlFor="password" className="mb-2 block text-right">
|
||||
@@ -477,6 +516,7 @@ export default function Auth() {
|
||||
</Label>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
toPersianDigits,
|
||||
} from "@/lib/utils";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { buildAuthPath } from "@/lib/auth-return";
|
||||
|
||||
const typeLabel: Record<string, string> = {
|
||||
online: "آنلاین",
|
||||
@@ -119,7 +120,7 @@ export default function EventDetail({ initialEvent = null }: EventDetailProps) {
|
||||
description: "برای ثبتنام در رویداد باید وارد حساب کاربری خود شوید.",
|
||||
variant: "destructive",
|
||||
});
|
||||
navigate("/auth");
|
||||
navigate(buildAuthPath(`/events/${event.slug}`));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -158,7 +159,7 @@ export default function EventDetail({ initialEvent = null }: EventDetailProps) {
|
||||
description: "برای ثبتنام در رویداد باید وارد حساب کاربری خود شوید.",
|
||||
variant: "destructive",
|
||||
});
|
||||
navigate("/auth");
|
||||
navigate(buildAuthPath(`/events/${event.slug}`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ import { Label } from "@/components/ui/label";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { api } from "@/lib/api";
|
||||
import {
|
||||
clearStoredAuthReturnTo,
|
||||
DEFAULT_AUTH_RETURN_TO,
|
||||
buildAuthPath,
|
||||
readStoredAuthReturnTo,
|
||||
} from "@/lib/auth-return";
|
||||
import { Link, useNavigate, useSearchParams } from "@/lib/router";
|
||||
import type { GoogleFlowResponseSchema } from "@/lib/types";
|
||||
import { resolveErrorMessage } from "@/lib/utils";
|
||||
@@ -34,6 +40,8 @@ export default function GoogleAuthCallback() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [otpCooldown, setOtpCooldown] = useState(0);
|
||||
const [googleFlow, setGoogleFlow] = useState<GoogleFlowResponseSchema | null>(null);
|
||||
const [flowError, setFlowError] = useState("");
|
||||
const [returnTo, setReturnTo] = useState(DEFAULT_AUTH_RETURN_TO);
|
||||
const [profileForm, setProfileForm] = useState({
|
||||
mobile: "",
|
||||
username: "",
|
||||
@@ -62,6 +70,10 @@ export default function GoogleAuthCallback() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setReturnTo(readStoredAuthReturnTo());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (otpCooldown <= 0) {
|
||||
return;
|
||||
@@ -75,12 +87,14 @@ export default function GoogleAuthCallback() {
|
||||
throw new Error("توکنهای ورود گوگل دریافت نشد.");
|
||||
}
|
||||
await setSessionTokens(payload.access_token, payload.refresh_token);
|
||||
const destination = readStoredAuthReturnTo();
|
||||
clearStoredAuthReturnTo();
|
||||
toast({
|
||||
title: "ورود با گوگل کامل شد",
|
||||
description: "حساب شما با موفقیت بازیابی یا متصل شد.",
|
||||
variant: "success",
|
||||
});
|
||||
navigate("/profile", { replace: true });
|
||||
navigate(destination, { replace: true });
|
||||
}, [navigate, setSessionTokens, toast]);
|
||||
|
||||
const applyFlow = useCallback(async (payload: GoogleFlowResponseSchema) => {
|
||||
@@ -110,11 +124,13 @@ export default function GoogleAuthCallback() {
|
||||
return;
|
||||
}
|
||||
|
||||
setFlowError("ورود با گوگل کامل نشد. دوباره تلاش کنید یا با موبایل وارد شوید.");
|
||||
setStep("error");
|
||||
}, [handleAuthenticatedFlow]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!flow) {
|
||||
setFlowError("نشانی بازگشت گوگل ناقص است. از صفحه ورود دوباره شروع کنید.");
|
||||
setStep("error");
|
||||
return;
|
||||
}
|
||||
@@ -129,11 +145,7 @@ export default function GoogleAuthCallback() {
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (!cancelled) {
|
||||
toast({
|
||||
title: "اتصال گوگل کامل نشد",
|
||||
description: resolveErrorMessage(error, "لینک گوگل نامعتبر یا منقضی شده است."),
|
||||
variant: "destructive",
|
||||
});
|
||||
setFlowError("لینک ورود با گوگل معتبر نیست یا منقضی شده است. دوباره از صفحه ورود شروع کنید.");
|
||||
setStep("error");
|
||||
}
|
||||
} finally {
|
||||
@@ -146,7 +158,7 @@ export default function GoogleAuthCallback() {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [applyFlow, flow, toast]);
|
||||
}, [applyFlow, flow]);
|
||||
|
||||
const handleCompleteProfile = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
@@ -233,7 +245,7 @@ export default function GoogleAuthCallback() {
|
||||
? "ایمیل گوگل شما با یک حساب قدیمی تطابق دارد. موبایل همان حساب را تأیید کنید تا بازیابی کامل شود."
|
||||
: "این اولین ورود شما با گوگل است. اطلاعات تکمیلی را ثبت کنید تا حساب شما ساخته شود."
|
||||
: step === "error"
|
||||
? "لینک این فرایند منقضی شده یا از سمت گوگل کامل نشده است."
|
||||
? "برای ادامه، ورود با گوگل را دوباره شروع کنید یا با شماره موبایل وارد شوید."
|
||||
: "چند لحظه صبر کنید تا وضعیت حساب گوگل شما بررسی شود.";
|
||||
|
||||
return (
|
||||
@@ -432,23 +444,21 @@ export default function GoogleAuthCallback() {
|
||||
|
||||
{step === "error" ? (
|
||||
<div className="space-y-5">
|
||||
<div className="rounded-[1.5rem] border border-amber-400/30 bg-amber-500/10 p-4 text-right text-sm leading-7 text-amber-900 dark:text-amber-100">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div role="alert" aria-live="assertive" className="rounded-[1.5rem] border border-destructive/40 bg-destructive/10 p-4 text-right text-sm leading-7 text-destructive dark:border-red-400/40 dark:bg-red-950/60 dark:text-red-200">
|
||||
<div className="flex items-start justify-start gap-3">
|
||||
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-semibold">فرایند گوگل کامل نشد</p>
|
||||
<p className="mt-2">
|
||||
دوباره از صفحه ورود شروع کنید. اگر مشکل ادامه داشت، از ورود با موبایل یا بازیابی با موبایل استفاده کنید.
|
||||
</p>
|
||||
<p className="mt-2">{flowError || "لینک این فرایند منقضی شده یا از سمت گوگل کامل نشده است."}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<Button type="button" className="h-12 rounded-2xl" onClick={() => void api.startGoogleLogin()}>
|
||||
<Button type="button" className="h-12 rounded-2xl" onClick={() => void api.startGoogleLogin(returnTo)}>
|
||||
شروع دوباره با گوگل
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="h-12 rounded-2xl">
|
||||
<Link to="/auth">
|
||||
<Link to={buildAuthPath(returnTo)}>
|
||||
<ArrowLeft className="ml-2 h-4 w-4" />
|
||||
بازگشت به ورود
|
||||
</Link>
|
||||
|
||||
@@ -23,6 +23,7 @@ import ConfirmAction from "@/components/ConfirmAction";
|
||||
import Markdown from "@/components/Markdown";
|
||||
import { Helmet } from "@/lib/helmet";
|
||||
import { Link, Navigate } from "@/lib/router";
|
||||
import { buildAuthPath } from "@/lib/auth-return";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { api } from "@/lib/api";
|
||||
import { blogPostPath } from "@/lib/blog-routes";
|
||||
@@ -495,7 +496,7 @@ export default function Profile() {
|
||||
<title>{pageTitle}</title>
|
||||
<meta name="description" content={pageDescription} />
|
||||
</Helmet>
|
||||
<Navigate to="/auth" replace />
|
||||
<Navigate to={buildAuthPath("/profile")} replace />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user