Commit 577d185d authored by ThinhNC's avatar ThinhNC

feat(auth): make login context notice collapsible with toggle button and remember state

parent eb3c1e63
...@@ -165,6 +165,13 @@ export const ChevronDownIcon: React.FC<IconProps> = ({ size = 18, ...props }) => ...@@ -165,6 +165,13 @@ export const ChevronDownIcon: React.FC<IconProps> = ({ size = 18, ...props }) =>
</svg> </svg>
); );
// Chevron Up
export const ChevronUpIcon: React.FC<IconProps> = ({ size = 18, ...props }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" {...props}>
<polyline points="18 15 12 9 6 15" />
</svg>
);
// Chevron Right // Chevron Right
export const ChevronRightIcon: React.FC<IconProps> = ({ size = 18, ...props }) => ( export const ChevronRightIcon: React.FC<IconProps> = ({ size = 18, ...props }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" {...props}> <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" {...props}>
......
...@@ -182,6 +182,8 @@ ...@@ -182,6 +182,8 @@
"appDescription": "Personal Finance & Expense Management", "appDescription": "Personal Finance & Expense Management",
"contextTitle": "Purpose & Context of Login", "contextTitle": "Purpose & Context of Login",
"contextNotice": "FinWise is a personal finance management app. Logging in helps identify your account, secure wallet balances, and safely sync your financial records to the cloud. You can sign in to save your personal records or choose 'Explore without login' below to experience the app with sample data.", "contextNotice": "FinWise is a personal finance management app. Logging in helps identify your account, secure wallet balances, and safely sync your financial records to the cloud. You can sign in to save your personal records or choose 'Explore without login' below to experience the app with sample data.",
"contextCollapse": "Collapse",
"contextExpand": "Details",
"guestExplore": "Explore without login", "guestExplore": "Explore without login",
"guestExploreSubtext": "Decline login and access the app to experience features with sample data", "guestExploreSubtext": "Decline login and access the app to experience features with sample data",
"orEmail": "Or sign in with Email" "orEmail": "Or sign in with Email"
......
...@@ -182,6 +182,8 @@ ...@@ -182,6 +182,8 @@
"appDescription": "Ứng dụng Quản lý Tài chính & Sổ thu chi Cá nhân", "appDescription": "Ứng dụng Quản lý Tài chính & Sổ thu chi Cá nhân",
"contextTitle": "Mục đích & Lý do đăng nhập", "contextTitle": "Mục đích & Lý do đăng nhập",
"contextNotice": "FinWise là ứng dụng sổ tay quản lý tài chính cá nhân. Việc đăng nhập tài khoản giúp định danh người dùng, bảo mật dữ liệu ví tiền và đồng bộ an toàn sổ thu chi của bạn trên đám mây giữa các thiết bị. Bạn có thể đăng nhập hoặc chọn 'Dùng thử không cần đăng nhập' bên dưới để khám phá ứng dụng.", "contextNotice": "FinWise là ứng dụng sổ tay quản lý tài chính cá nhân. Việc đăng nhập tài khoản giúp định danh người dùng, bảo mật dữ liệu ví tiền và đồng bộ an toàn sổ thu chi của bạn trên đám mây giữa các thiết bị. Bạn có thể đăng nhập hoặc chọn 'Dùng thử không cần đăng nhập' bên dưới để khám phá ứng dụng.",
"contextCollapse": "Thu gọn",
"contextExpand": "Chi tiết",
"guestExplore": "Dùng thử không cần đăng nhập", "guestExplore": "Dùng thử không cần đăng nhập",
"guestExploreSubtext": "Từ chối đăng nhập và truy cập ứng dụng để trải nghiệm với dữ liệu mẫu", "guestExploreSubtext": "Từ chối đăng nhập và truy cập ứng dụng để trải nghiệm với dữ liệu mẫu",
"orEmail": "Hoặc đăng nhập bằng tài khoản Email" "orEmail": "Hoặc đăng nhập bằng tài khoản Email"
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
EyeIcon, EyeIcon,
EyeOffIcon, EyeOffIcon,
ShieldCheckIcon, ShieldCheckIcon,
ChevronDownIcon,
} from "@/components/ui/icons"; } from "@/components/ui/icons";
import { Logo } from "@/components/logo"; import { Logo } from "@/components/logo";
import { useAuthStore } from "@/stores/auth-store"; import { useAuthStore } from "@/stores/auth-store";
...@@ -20,6 +21,7 @@ import { safeStorage } from "@/lib/storage"; ...@@ -20,6 +21,7 @@ import { safeStorage } from "@/lib/storage";
import { TranslationFunction, useI18n } from "@/i18n"; import { TranslationFunction, useI18n } from "@/i18n";
const REMEMBERED_EMAIL_KEY = "finwise.rememberedEmail"; const REMEMBERED_EMAIL_KEY = "finwise.rememberedEmail";
const LOGIN_CONTEXT_STORAGE_KEY = "finwise.loginContextOpen";
const getRememberedEmail = (): string => { const getRememberedEmail = (): string => {
return safeStorage.getItem(REMEMBERED_EMAIL_KEY) || ""; return safeStorage.getItem(REMEMBERED_EMAIL_KEY) || "";
...@@ -54,6 +56,19 @@ const LoginPage: React.FC = () => { ...@@ -54,6 +56,19 @@ const LoginPage: React.FC = () => {
const [rememberedEmail] = useState(getRememberedEmail); const [rememberedEmail] = useState(getRememberedEmail);
const loginSchema = useMemo(() => createLoginSchema(t), [t]); const loginSchema = useMemo(() => createLoginSchema(t), [t]);
const [isContextOpen, setIsContextOpen] = useState<boolean>(() => {
const saved = safeStorage.getItem(LOGIN_CONTEXT_STORAGE_KEY);
return saved !== null ? saved === "true" : true;
});
const toggleContext = () => {
setIsContextOpen((prev) => {
const next = !prev;
safeStorage.setItem(LOGIN_CONTEXT_STORAGE_KEY, String(next));
return next;
});
};
const isBusy = isSubmitting; const isBusy = isSubmitting;
const handleGuestExplore = () => { const handleGuestExplore = () => {
...@@ -138,19 +153,53 @@ const LoginPage: React.FC = () => { ...@@ -138,19 +153,53 @@ const LoginPage: React.FC = () => {
</div> </div>
</div> </div>
{/* Context Notice Card: Explains clearly why login is required per Zalo guidelines */} {/* Context Notice Card: Explains clearly why login is required per Zalo guidelines with toggle */}
<div className="rounded-2xl bg-clay-primary/10 border border-clay-primary/25 p-3.5 shadow-clay-card flex items-start gap-3 text-left"> <div className="rounded-2xl bg-clay-primary/10 border border-clay-primary/25 p-3 shadow-clay-card text-left transition-all">
<div className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-xl bg-clay-primary/20 text-clay-primary"> <div
<ShieldCheckIcon size={16} /> className="flex items-center justify-between cursor-pointer select-none"
</div> onClick={toggleContext}
<div className="space-y-1"> role="button"
<h2 className="font-baloo text-xs font-bold text-clay-primary uppercase tracking-wider"> tabIndex={0}
{t("auth.login.contextTitle")} onKeyDown={(e) => {
</h2> if (e.key === "Enter" || e.key === " ") {
<p className="font-nunito text-[11px] text-clay-text-muted leading-relaxed"> e.preventDefault();
{t("auth.login.contextNotice")} toggleContext();
</p> }
}}
>
<div className="flex items-center gap-2">
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg bg-clay-primary/20 text-clay-primary">
<ShieldCheckIcon size={14} />
</div>
<h2 className="font-baloo text-xs font-bold text-clay-primary uppercase tracking-wider">
{t("auth.login.contextTitle")}
</h2>
</div>
<button
type="button"
className="flex items-center gap-1 text-[11px] font-medium text-clay-primary hover:text-clay-primary-dark transition-colors px-1.5 py-0.5 rounded-md hover:bg-clay-primary/10"
onClick={(e) => {
e.stopPropagation();
toggleContext();
}}
aria-expanded={isContextOpen}
aria-label={isContextOpen ? t("auth.login.contextCollapse") : t("auth.login.contextExpand")}
>
<span>{isContextOpen ? t("auth.login.contextCollapse") : t("auth.login.contextExpand")}</span>
<ChevronDownIcon
size={14}
className={`transition-transform duration-200 ${isContextOpen ? "rotate-180" : ""}`}
/>
</button>
</div> </div>
{isContextOpen && (
<div className="mt-2.5 pt-2.5 border-t border-clay-primary/15">
<p className="font-nunito text-[11px] text-clay-text-muted leading-relaxed">
{t("auth.login.contextNotice")}
</p>
</div>
)}
</div> </div>
{/* Card: Login Form */} {/* Card: Login Form */}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment