Commit d0d6f326 authored by ThinhNC's avatar ThinhNC

feat(branding): integrate official logo and resolve auth/home layout scrolling issues

parent 6db4e922
# FinWise Frontend - Zalo Mini App Client
<p align="center">
<img src="./src/static/icons/app-icon.png" alt="FinWise Logo" width="96" height="96" onerror="this.style.display='none'"/>
<img src="./src/static/logo.png" alt="FinWise Logo" width="96" height="96" />
</p>
<p align="center">
......
......@@ -15,6 +15,9 @@
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<title>Sổ tay Chi tiêu & Báo cáo Tài chính</title>
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/logo.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@600;700;800&family=Nunito:wght@400;500;600;700&display=swap" rel="stylesheet">
......
This diff is collapsed.
......@@ -2,6 +2,7 @@ import React, { useEffect } from "react";
import { useNavigate } from "zmp-ui";
import { useAuthStore } from "@/stores/auth-store";
import { useI18n } from "@/i18n";
import { Logo } from "@/components/logo";
export const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { isAuthenticated, isInitialized } = useAuthStore();
......@@ -16,9 +17,9 @@ export const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children })
if (!isInitialized) {
return (
<div className="page flex flex-col items-center justify-center min-h-screen">
<div className="w-16 h-16 border-4 border-clay-primary border-t-transparent rounded-full animate-spin shadow-clay-raised"></div>
<p className="clay-caption mt-4 animate-pulse text-clay-primary">{t("common.loading")}</p>
<div className="page flex flex-col items-center justify-center min-h-screen gap-4">
<Logo size={72} className="animate-pulse" alt="FinWise" />
<p className="clay-caption animate-pulse text-clay-primary font-medium">{t("common.loading")}</p>
</div>
);
}
......
import React, { useEffect } from "react";
import { useLocation } from "zmp-ui";
import { useI18n } from "@/i18n";
import finwiseLogo from "@/static/logo.png";
const APP_NAME = "FinWise";
......@@ -49,6 +50,23 @@ export const DocumentTitle: React.FC = () => {
const { pathname } = useLocation();
const { t } = useI18n();
useEffect(() => {
// Dynamically set / update favicon to guarantee browser tab displays FinWise logo
const iconRels = ["icon", "shortcut icon", "apple-touch-icon"];
iconRels.forEach((rel) => {
let link: HTMLLinkElement | null = document.querySelector(`link[rel='${rel}']`);
if (!link) {
link = document.createElement("link");
link.rel = rel;
document.head.appendChild(link);
}
if (rel !== "apple-touch-icon") {
link.type = "image/png";
}
link.href = finwiseLogo;
});
}, []);
useEffect(() => {
const key = PAGE_TITLES.find((page) => page.matches(pathname))?.key;
document.title = `${key ? t(key) : t("document.default")} | ${APP_NAME}`;
......
......@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input";
import { authService } from "@/services/auth.service";
import { IconGradients } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { TranslationFunction, useI18n } from "@/i18n";
import { getErrorMessage } from "@/lib/error-message";
......@@ -60,14 +61,17 @@ const ForgotPasswordPage: React.FC = () => {
};
return (
<Page className="page flex flex-col justify-center py-12">
<Page className="page flex flex-col justify-start overflow-y-auto">
<Header title={t("auth.forgot.header")} showBackIcon={true} onBackClick={() => navigate("/login")} />
<IconGradients />
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2">
<div className="text-center space-y-2">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.forgot.title")}</h1>
<p className="clay-caption">{t("auth.forgot.subtitle")}</p>
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2 pt-2">
<div className="flex flex-col items-center gap-2.5 text-center">
<Logo size={64} alt="FinWise" />
<div className="space-y-1">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.forgot.title")}</h1>
<p className="clay-caption">{t("auth.forgot.subtitle")}</p>
</div>
</div>
{isSent ? (
......
......@@ -8,10 +8,10 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input";
import {
IconGradients,
WalletIcon,
EyeIcon,
EyeOffIcon,
} from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { useZaloLogin } from "@/hooks/use-zalo-login";
import { useAuthStore } from "@/stores/auth-store";
import { authService } from "@/services/auth.service";
......@@ -113,16 +113,14 @@ const LoginPage: React.FC = () => {
const isBusy = isSubmitting || isZaloLoading;
return (
<Page className="page flex flex-col justify-start py-6 px-4">
<Page className="page flex flex-col justify-start overflow-y-auto">
<Header title={t("auth.login.header")} showBackIcon={false} />
<IconGradients />
<div className="w-full max-w-sm mx-auto flex flex-col gap-5 pt-1">
<div className="w-full max-w-sm mx-auto flex flex-col gap-5 pt-2">
{/* Top: Branding */}
<div className="flex flex-col items-center gap-2.5 text-center">
<div className="flex h-16 w-16 items-center justify-center rounded-clay-lg bg-clay-primary text-clay-on-primary shadow-clay-hover border border-clay-highlight/30">
<WalletIcon size={34} />
</div>
<Logo size={72} alt="FinWise" />
<div className="space-y-1">
<h1 className="clay-title-h1 text-clay-primary">FinWise</h1>
<p className="clay-caption">{t("auth.login.subtitle")}</p>
......
......@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input";
import { authService } from "@/services/auth.service";
import { IconGradients } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { TranslationFunction, useI18n } from "@/i18n";
import { getErrorMessage } from "@/lib/error-message";
......@@ -85,14 +86,17 @@ const RegisterPage: React.FC = () => {
};
return (
<Page className="page flex flex-col justify-center py-12">
<Page className="page flex flex-col justify-start overflow-y-auto">
<Header title={t("auth.register.header")} showBackIcon={true} onBackClick={() => navigate("/login")} />
<IconGradients />
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2">
<div className="text-center space-y-2">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.register.title")}</h1>
<p className="clay-caption">{t("auth.register.subtitle")}</p>
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2 pt-2">
<div className="flex flex-col items-center gap-2.5 text-center">
<Logo size={64} alt="FinWise" />
<div className="space-y-1">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.register.title")}</h1>
<p className="clay-caption">{t("auth.register.subtitle")}</p>
</div>
</div>
{isRegistered ? (
......
......@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input";
import { authService } from "@/services/auth.service";
import { IconGradients } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { TranslationFunction, useI18n } from "@/i18n";
import { getErrorMessage } from "@/lib/error-message";
......@@ -76,14 +77,17 @@ const ResetPasswordPage: React.FC = () => {
};
return (
<Page className="page flex flex-col justify-center py-12">
<Page className="page flex flex-col justify-start overflow-y-auto">
<Header title={t("auth.reset.header")} showBackIcon={true} onBackClick={() => navigate("/login")} />
<IconGradients />
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2">
<div className="text-center space-y-2">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.reset.title")}</h1>
<p className="clay-caption">{t("auth.reset.subtitle")}</p>
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2 pt-2">
<div className="flex flex-col items-center gap-2.5 text-center">
<Logo size={64} alt="FinWise" />
<div className="space-y-1">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.reset.title")}</h1>
<p className="clay-caption">{t("auth.reset.subtitle")}</p>
</div>
</div>
<Card>
......
import React from "react";
import { Page, Header, useNavigate } from "zmp-ui";
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { Avatar } from "@/components/ui/Avatar";
import { useAuthStore } from "@/stores/auth-store";
import {
......@@ -26,11 +25,11 @@ function HomePage() {
const showStyleGuide = user?.role.name !== SYSTEM_ROLES.USER;
return (
<Page className="page flex flex-col justify-between py-8">
<Page className="page flex flex-col justify-start overflow-y-auto">
<Header title={t("home.header")} showBackIcon={false} />
<IconGradients />
<div className="flex-1 flex flex-col items-center justify-center gap-6 px-4">
<div className="flex flex-col items-center justify-start gap-5 px-4 pt-2 pb-6">
{/* Logo/Avatar Area */}
<div className="relative">
<Avatar
......@@ -53,18 +52,10 @@ function HomePage() {
{t("home.account")} <span className="font-semibold text-clay-primary">{user?.email}</span>
</p>
</div>
{/* Info Card */}
<Card className="w-full max-w-sm">
<h3 className="font-baloo font-bold text-clay-text text-base mb-1">{t("home.authTitle")}</h3>
<p className="text-xs text-clay-text-muted">
{t("home.authDescription")}
</p>
</Card>
</div>
{/* Navigation CTA */}
<div className="px-4 w-full max-w-sm mx-auto flex flex-col gap-3">
<div className="px-4 w-full max-w-sm mx-auto flex flex-col gap-3 pb-12">
<PermissionGate permission={PERMISSIONS.USER_READ}>
<button
type="button"
......
......@@ -706,7 +706,7 @@ const ProfilePage: React.FC = () => {
)}
{/* Global Logout Button */}
<div className="px-4">
<div className="px-4 pb-6">
<Button variant="secondary" onClick={handleLogout} fullWidth className="text-clay-expense">
{t("profile.logout")}
</Button>
......
......@@ -26,6 +26,7 @@ import {
PlusIcon,
FoodIcon,
} from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { useI18n } from "@/i18n";
function getNumberSeparators(locale: string): { group: string; decimal: string } {
......@@ -420,6 +421,25 @@ const StyleGuidePage: React.FC = () => {
</Modal>
</Card>
{/* Section: App Logo & Brand Identity */}
<Card>
<h2 className="clay-title-h2 mb-4 border-b pb-2 text-clay-primary-dark">FinWise Brand Logo</h2>
<div className="flex items-center justify-around gap-4 p-4 bg-clay-bg rounded-clay-md border border-clay-text/5">
<div className="flex flex-col items-center gap-2">
<Logo size={40} alt="FinWise Logo Small" />
<span className="text-xs text-clay-text-muted font-semibold">Small (40px)</span>
</div>
<div className="flex flex-col items-center gap-2">
<Logo size={56} alt="FinWise Logo Medium" />
<span className="text-xs text-clay-text-muted font-semibold">Medium (56px)</span>
</div>
<div className="flex flex-col items-center gap-2">
<Logo size={72} alt="FinWise Logo Large" />
<span className="text-xs text-clay-text-muted font-semibold">Large (72px)</span>
</div>
</div>
</Card>
{/* Section: 3D Icons List */}
<Card>
<h2 className="clay-title-h2 mb-4 border-b pb-2 text-clay-primary-dark">{t("styleGuide.icons")}</h2>
......
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