feat: vantage_ prefix on localStorage keys; VANTAGE wordmark + favicon; fix form dialog scroll with file preview

This commit is contained in:
Bhanu Prakash Sai Potteri 2026-08-20 12:50:21 +05:30
parent 9b1c3fbc32
commit 591c2ac5d5
11 changed files with 43 additions and 36 deletions

View File

@ -3,11 +3,11 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="./zino.svg" />
<link rel="icon" type="image/svg+xml" href="./vantage.svg" />
<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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<title>P2P</title>
<title>Vantage P2P</title>
<!--BASE_HREF-->
<script src="./config.js"></script>
</head>

1
public/vantage.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="7" fill="#7c3aed"/><path d="M8 9h4.4l3.6 7.5L19.6 9H24l-8 15z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 176 B

View File

@ -1,6 +1,6 @@
import { APP_ID, BASE_URL } from "../config";
const TOKEN_KEY = "p2p_token";
const TOKEN_KEY = "vantage_p2p_token";
// Error carrying the HTTP status so callers can distinguish e.g. a 403
// (authenticated but not entitled to this app) from a generic failure.
@ -29,7 +29,7 @@ function headers(): Record<string, string> {
async function parseResponse<T>(res: Response): Promise<T> {
if (res.status === 401) {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem("p2p_user");
localStorage.removeItem("vantage_p2p_user");
window.location.href = `${import.meta.env.BASE_URL}login`;
throw new ApiError(401, "Unauthorized");
}

23
src/components/Brand.tsx Normal file
View File

@ -0,0 +1,23 @@
import { cn } from "@/lib/utils";
interface BrandProps {
/** Show only "V" (collapsed sidebar rail). */
markOnly?: boolean;
/** Paler gradient for dark backgrounds. */
light?: boolean;
className?: string;
}
/** VANTAGE wordmark — bold, wide-tracked, brand-gradient text. */
export function Brand({ markOnly = false, light = false, className }: BrandProps) {
return (
<span
className={cn("gradient-text text-base font-bold tracking-[0.25em]", className)}
style={
light ? { backgroundImage: "linear-gradient(135deg, #c4b5fd, #f0abfc)" } : undefined
}
>
{markOnly ? "V" : "VANTAGE"}
</span>
);
}

View File

@ -13,6 +13,7 @@ import {
Sun,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { Brand } from "@/components/Brand";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Sheet, SheetContent, SheetTitle } from "@/components/ui/sheet";
@ -149,13 +150,7 @@ export default function DynamicHeader({
{/* On desktop the sidebar carries the identity; repeating the logo
here would double it up. */}
{sidebar.isMobile && (
<img
src={import.meta.env.BASE_URL + "zino.svg"}
alt="Zino"
className="h-5 w-auto dark:brightness-0 dark:invert"
/>
)}
{sidebar.isMobile && <Brand />}
</div>
<div className="flex items-center gap-1.5">
@ -267,13 +262,8 @@ function SidebarBody({
railed && "px-0",
)}
>
{/* 43×20 wordmark fits the 56px rail as-is, so it needs no separate
collapsed mark. Inverted in dark mode like the top-bar copy. */}
<img
src={import.meta.env.BASE_URL + "zino.svg"}
alt="Zino"
className="h-5 w-auto shrink-0 dark:brightness-0 dark:invert"
/>
{/* Full wordmark is too wide for the 56px rail, so collapse to the mark. */}
<Brand markOnly={railed} />
{!railed && (
<span className="truncate text-2xs font-medium tracking-wide text-muted-foreground">
Procure to Pay

View File

@ -218,7 +218,7 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
{/* Radix supplies the focus trap, Escape handling, aria-modal, scroll lock
and focus restore that the hand-rolled overlay never had. */}
<DialogContent
className="max-h-[90vh] gap-0 overflow-hidden p-0 sm:max-w-xl"
className="flex max-h-[90vh] flex-col gap-0 overflow-hidden p-0 sm:max-w-xl"
onInteractOutside={(e) => submitting && e.preventDefault()}
>
<DialogHeader className="border-b border-border px-5 py-4 text-left">
@ -228,7 +228,7 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
</DialogDescription>
</DialogHeader>
<div className="scrollbar-thin flex-1 overflow-y-auto px-5 py-4">
<div className="scrollbar-thin min-h-0 flex-1 overflow-y-auto px-5 py-4">
{isPending ? (
<div className="space-y-4">
{Array.from({ length: 4 }).map((_, r) => (

View File

@ -14,7 +14,7 @@ const ThemeContext = createContext<ThemeContextValue>({
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() => {
const stored = localStorage.getItem("p2p_theme");
const stored = localStorage.getItem("vantage_p2p_theme");
if (stored === "dark" || stored === "light") return stored;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
});
@ -22,7 +22,7 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
const root = document.documentElement;
root.classList.toggle("dark", theme === "dark");
localStorage.setItem("p2p_theme", theme);
localStorage.setItem("vantage_p2p_theme", theme);
}, [theme]);
// The colour-transition is opt-in per switch rather than a standing global

View File

@ -2,8 +2,8 @@ import { useState, useCallback } from "react";
import type { User } from "../zino-sdk/types";
import { BASE_URL } from "../config";
const TOKEN_KEY = "p2p_token";
const USER_KEY = "p2p_user";
const TOKEN_KEY = "vantage_p2p_token";
const USER_KEY = "vantage_p2p_user";
interface LoginParams {
email: string;

View File

@ -7,7 +7,7 @@
import { useCallback, useEffect, useState } from "react";
const KEY = "p2p_sidebar_open";
const KEY = "vantage_p2p_sidebar_open";
/** Below this the sidebar becomes an overlay drawer rather than a rail. */
export const MOBILE_BREAKPOINT = 1024;

View File

@ -1,6 +1,7 @@
import { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { ArrowRight, BarChart3, Bot, Loader2, ShieldCheck, Zap } from "lucide-react";
import { Brand } from "@/components/Brand";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@ -75,11 +76,7 @@ export default function Login() {
/>
</div>
<img
src={import.meta.env.BASE_URL + "zino.svg"}
alt="Zino"
className="absolute left-16 top-10 z-10 h-5 w-auto brightness-0 invert"
/>
<Brand light className="absolute left-16 top-10 z-10" />
<div className="relative z-10 max-w-lg">
<span className="mb-8 inline-flex items-center gap-2 rounded-full border border-violet-300/30 bg-primary/20 px-3 py-1.5 text-sm font-medium text-violet-200">
@ -117,11 +114,7 @@ export default function Login() {
{/* ── Right — form ──────────────────────────────────────────────────── */}
<div className="flex flex-1 items-center justify-center px-6">
<div className="w-full max-w-[340px]">
<img
src={import.meta.env.BASE_URL + "zino.svg"}
alt="Zino"
className="mb-10 h-5 w-auto dark:invert lg:hidden"
/>
<Brand className="mb-10 lg:hidden" />
<div className="mb-8">
<h2 className="text-4xl font-bold tracking-tight text-foreground">Sign in</h2>

View File

@ -1,6 +1,6 @@
import type { ApiError, ZinoClientConfig } from "./types";
const TOKEN_KEY = "p2p_token";
const TOKEN_KEY = "vantage_p2p_token";
/**
* Core HTTP client for the Zino API.