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 # FinWise Frontend - Zalo Mini App Client
<p align="center"> <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>
<p align="center"> <p align="center">
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
<meta name="format-detection" content="telephone=no" /> <meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" /> <meta name="msapplication-tap-highlight" content="no" />
<title>Sổ tay Chi tiêu & Báo cáo Tài chính</title> <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.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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"> <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"; ...@@ -2,6 +2,7 @@ import React, { useEffect } from "react";
import { useNavigate } from "zmp-ui"; import { useNavigate } from "zmp-ui";
import { useAuthStore } from "@/stores/auth-store"; import { useAuthStore } from "@/stores/auth-store";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
import { Logo } from "@/components/logo";
export const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { isAuthenticated, isInitialized } = useAuthStore(); const { isAuthenticated, isInitialized } = useAuthStore();
...@@ -16,9 +17,9 @@ export const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children }) ...@@ -16,9 +17,9 @@ export const AuthGuard: React.FC<{ children: React.ReactNode }> = ({ children })
if (!isInitialized) { if (!isInitialized) {
return ( return (
<div className="page flex flex-col items-center justify-center min-h-screen"> <div className="page flex flex-col items-center justify-center min-h-screen gap-4">
<div className="w-16 h-16 border-4 border-clay-primary border-t-transparent rounded-full animate-spin shadow-clay-raised"></div> <Logo size={72} className="animate-pulse" alt="FinWise" />
<p className="clay-caption mt-4 animate-pulse text-clay-primary">{t("common.loading")}</p> <p className="clay-caption animate-pulse text-clay-primary font-medium">{t("common.loading")}</p>
</div> </div>
); );
} }
......
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useLocation } from "zmp-ui"; import { useLocation } from "zmp-ui";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
import finwiseLogo from "@/static/logo.png";
const APP_NAME = "FinWise"; const APP_NAME = "FinWise";
...@@ -49,6 +50,23 @@ export const DocumentTitle: React.FC = () => { ...@@ -49,6 +50,23 @@ export const DocumentTitle: React.FC = () => {
const { pathname } = useLocation(); const { pathname } = useLocation();
const { t } = useI18n(); 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(() => { useEffect(() => {
const key = PAGE_TITLES.find((page) => page.matches(pathname))?.key; const key = PAGE_TITLES.find((page) => page.matches(pathname))?.key;
document.title = `${key ? t(key) : t("document.default")} | ${APP_NAME}`; document.title = `${key ? t(key) : t("document.default")} | ${APP_NAME}`;
......
...@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card"; ...@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import { authService } from "@/services/auth.service"; import { authService } from "@/services/auth.service";
import { IconGradients } from "@/components/ui/icons"; import { IconGradients } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { TranslationFunction, useI18n } from "@/i18n"; import { TranslationFunction, useI18n } from "@/i18n";
import { getErrorMessage } from "@/lib/error-message"; import { getErrorMessage } from "@/lib/error-message";
...@@ -60,14 +61,17 @@ const ForgotPasswordPage: React.FC = () => { ...@@ -60,14 +61,17 @@ const ForgotPasswordPage: React.FC = () => {
}; };
return ( 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")} /> <Header title={t("auth.forgot.header")} showBackIcon={true} onBackClick={() => navigate("/login")} />
<IconGradients /> <IconGradients />
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2"> <div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2 pt-2">
<div className="text-center space-y-2"> <div className="flex flex-col items-center gap-2.5 text-center">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.forgot.title")}</h1> <Logo size={64} alt="FinWise" />
<p className="clay-caption">{t("auth.forgot.subtitle")}</p> <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> </div>
{isSent ? ( {isSent ? (
......
...@@ -8,10 +8,10 @@ import { Card } from "@/components/ui/Card"; ...@@ -8,10 +8,10 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import { import {
IconGradients, IconGradients,
WalletIcon,
EyeIcon, EyeIcon,
EyeOffIcon, EyeOffIcon,
} from "@/components/ui/icons"; } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { useZaloLogin } from "@/hooks/use-zalo-login"; import { useZaloLogin } from "@/hooks/use-zalo-login";
import { useAuthStore } from "@/stores/auth-store"; import { useAuthStore } from "@/stores/auth-store";
import { authService } from "@/services/auth.service"; import { authService } from "@/services/auth.service";
...@@ -113,16 +113,14 @@ const LoginPage: React.FC = () => { ...@@ -113,16 +113,14 @@ const LoginPage: React.FC = () => {
const isBusy = isSubmitting || isZaloLoading; const isBusy = isSubmitting || isZaloLoading;
return ( 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} /> <Header title={t("auth.login.header")} showBackIcon={false} />
<IconGradients /> <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 */} {/* Top: Branding */}
<div className="flex flex-col items-center gap-2.5 text-center"> <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"> <Logo size={72} alt="FinWise" />
<WalletIcon size={34} />
</div>
<div className="space-y-1"> <div className="space-y-1">
<h1 className="clay-title-h1 text-clay-primary">FinWise</h1> <h1 className="clay-title-h1 text-clay-primary">FinWise</h1>
<p className="clay-caption">{t("auth.login.subtitle")}</p> <p className="clay-caption">{t("auth.login.subtitle")}</p>
......
...@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card"; ...@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import { authService } from "@/services/auth.service"; import { authService } from "@/services/auth.service";
import { IconGradients } from "@/components/ui/icons"; import { IconGradients } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { TranslationFunction, useI18n } from "@/i18n"; import { TranslationFunction, useI18n } from "@/i18n";
import { getErrorMessage } from "@/lib/error-message"; import { getErrorMessage } from "@/lib/error-message";
...@@ -85,14 +86,17 @@ const RegisterPage: React.FC = () => { ...@@ -85,14 +86,17 @@ const RegisterPage: React.FC = () => {
}; };
return ( 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")} /> <Header title={t("auth.register.header")} showBackIcon={true} onBackClick={() => navigate("/login")} />
<IconGradients /> <IconGradients />
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2"> <div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2 pt-2">
<div className="text-center space-y-2"> <div className="flex flex-col items-center gap-2.5 text-center">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.register.title")}</h1> <Logo size={64} alt="FinWise" />
<p className="clay-caption">{t("auth.register.subtitle")}</p> <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> </div>
{isRegistered ? ( {isRegistered ? (
......
...@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card"; ...@@ -8,6 +8,7 @@ import { Card } from "@/components/ui/Card";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import { authService } from "@/services/auth.service"; import { authService } from "@/services/auth.service";
import { IconGradients } from "@/components/ui/icons"; import { IconGradients } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { TranslationFunction, useI18n } from "@/i18n"; import { TranslationFunction, useI18n } from "@/i18n";
import { getErrorMessage } from "@/lib/error-message"; import { getErrorMessage } from "@/lib/error-message";
...@@ -76,14 +77,17 @@ const ResetPasswordPage: React.FC = () => { ...@@ -76,14 +77,17 @@ const ResetPasswordPage: React.FC = () => {
}; };
return ( 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")} /> <Header title={t("auth.reset.header")} showBackIcon={true} onBackClick={() => navigate("/login")} />
<IconGradients /> <IconGradients />
<div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2"> <div className="w-full max-w-sm mx-auto flex flex-col gap-6 px-2 pt-2">
<div className="text-center space-y-2"> <div className="flex flex-col items-center gap-2.5 text-center">
<h1 className="clay-title-h1 text-clay-primary">{t("auth.reset.title")}</h1> <Logo size={64} alt="FinWise" />
<p className="clay-caption">{t("auth.reset.subtitle")}</p> <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> </div>
<Card> <Card>
......
import React from "react"; import React from "react";
import { Page, Header, useNavigate } from "zmp-ui"; import { Page, Header, useNavigate } from "zmp-ui";
import { Button } from "@/components/ui/Button"; import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { Avatar } from "@/components/ui/Avatar"; import { Avatar } from "@/components/ui/Avatar";
import { useAuthStore } from "@/stores/auth-store"; import { useAuthStore } from "@/stores/auth-store";
import { import {
...@@ -26,11 +25,11 @@ function HomePage() { ...@@ -26,11 +25,11 @@ function HomePage() {
const showStyleGuide = user?.role.name !== SYSTEM_ROLES.USER; const showStyleGuide = user?.role.name !== SYSTEM_ROLES.USER;
return ( 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} /> <Header title={t("home.header")} showBackIcon={false} />
<IconGradients /> <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 */} {/* Logo/Avatar Area */}
<div className="relative"> <div className="relative">
<Avatar <Avatar
...@@ -53,18 +52,10 @@ function HomePage() { ...@@ -53,18 +52,10 @@ function HomePage() {
{t("home.account")} <span className="font-semibold text-clay-primary">{user?.email}</span> {t("home.account")} <span className="font-semibold text-clay-primary">{user?.email}</span>
</p> </p>
</div> </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> </div>
{/* Navigation CTA */} {/* 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}> <PermissionGate permission={PERMISSIONS.USER_READ}>
<button <button
type="button" type="button"
......
...@@ -706,7 +706,7 @@ const ProfilePage: React.FC = () => { ...@@ -706,7 +706,7 @@ const ProfilePage: React.FC = () => {
)} )}
{/* Global Logout Button */} {/* Global Logout Button */}
<div className="px-4"> <div className="px-4 pb-6">
<Button variant="secondary" onClick={handleLogout} fullWidth className="text-clay-expense"> <Button variant="secondary" onClick={handleLogout} fullWidth className="text-clay-expense">
{t("profile.logout")} {t("profile.logout")}
</Button> </Button>
......
...@@ -26,6 +26,7 @@ import { ...@@ -26,6 +26,7 @@ import {
PlusIcon, PlusIcon,
FoodIcon, FoodIcon,
} from "@/components/ui/icons"; } from "@/components/ui/icons";
import { Logo } from "@/components/logo";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
function getNumberSeparators(locale: string): { group: string; decimal: string } { function getNumberSeparators(locale: string): { group: string; decimal: string } {
...@@ -420,6 +421,25 @@ const StyleGuidePage: React.FC = () => { ...@@ -420,6 +421,25 @@ const StyleGuidePage: React.FC = () => {
</Modal> </Modal>
</Card> </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 */} {/* Section: 3D Icons List */}
<Card> <Card>
<h2 className="clay-title-h2 mb-4 border-b pb-2 text-clay-primary-dark">{t("styleGuide.icons")}</h2> <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