Commit 9c34634c authored by ThinhNC's avatar ThinhNC

Merge branch 'feat/guest-trial-mode' into 'develop'

Feat/guest trial mode

See merge request !63
parents d6fa2a5d 6a2f8c14
......@@ -165,6 +165,13 @@ export const ChevronDownIcon: React.FC<IconProps> = ({ size = 18, ...props }) =>
</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
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}>
......@@ -604,6 +611,24 @@ export const SparklesIcon: React.FC<IconProps> = ({ size = 18, ...props }) => (
</svg>
);
// Shield Check Icon (Security / Authentication context)
export const ShieldCheckIcon: React.FC<IconProps> = ({ size = 18, ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
<path d="m9 12 2 2 4-4" />
</svg>
);
......
......@@ -180,9 +180,12 @@
"ai": "AI Assistant"
},
"appDescription": "Personal Finance & Expense Management",
"contextNotice": "FinWise requires an account to secure your wallet data and synchronize personal expenses to the cloud. You can sign in or explore the app in demo mode below.",
"guestExplore": "Explore Now",
"guestExploreSubtext": "Experience the app with sample data, no sign-in required",
"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 'Try Demo' below to experience the app with sample data.",
"contextCollapse": "Collapse",
"contextExpand": "Details",
"guestExplore": "Try Demo",
"guestExploreSubtext": "Decline login and access the app to experience features with sample data",
"orEmail": "Or sign in with Email"
},
"guest": {
......
......@@ -180,9 +180,12 @@
"ai": "Trợ lý AI"
},
"appDescription": "Ứng dụng Quản lý Tài chính & Sổ thu chi Cá nhân",
"contextNotice": "FinWise cần tài khoản để bảo mật dữ liệu ví tiền và đồng bộ sổ thu chi cá nhân của bạn trên đám mây. Bạn có thể đăng nhập hoặc tiếp tục trải nghiệm ở chế độ dùng thử bên dưới.",
"guestExplore": "Khám phá ngay",
"guestExploreSubtext": "Trải nghiệm tính năng với dữ liệu mẫu, không cần đă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ử' bên dưới để khám phá ứng dụng.",
"contextCollapse": "Thu gọn",
"contextExpand": "Chi tiết",
"guestExplore": "Dùng thử",
"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"
},
"guest": {
......
......@@ -10,6 +10,8 @@ import {
IconGradients,
EyeIcon,
EyeOffIcon,
ShieldCheckIcon,
ChevronDownIcon,
} from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { useAuthStore } from "@/stores/auth-store";
......@@ -19,6 +21,7 @@ import { safeStorage } from "@/lib/storage";
import { TranslationFunction, useI18n } from "@/i18n";
const REMEMBERED_EMAIL_KEY = "finwise.rememberedEmail";
const LOGIN_CONTEXT_STORAGE_KEY = "finwise.loginContextOpen";
const getRememberedEmail = (): string => {
return safeStorage.getItem(REMEMBERED_EMAIL_KEY) || "";
......@@ -53,6 +56,19 @@ const LoginPage: React.FC = () => {
const [rememberedEmail] = useState(getRememberedEmail);
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 handleGuestExplore = () => {
......@@ -137,6 +153,55 @@ const LoginPage: React.FC = () => {
</div>
</div>
{/* 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 shadow-clay-card text-left transition-all">
<div
className="flex items-center justify-between cursor-pointer select-none"
onClick={toggleContext}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggleContext();
}
}}
>
<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>
{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>
{/* Card: Login Form */}
<Card className="p-5 flex flex-col gap-4 shadow-clay-raised">
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
......
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