feat(web): rebrand to FOOD MARKET green (#00B207) per mobile app logo
- Extract brand colors from food-market-app/.../AppIcon/appicon.svg (background #00B207, white FOOD, pale-green E8F5E9 MARKET) - Add @theme custom colors in index.css: --color-brand #00B207, --color-brand-hover #009305, --color-brand-dark #007605, --color-brand-light #E8F5E9, --color-brand-tint #D7F2D9, --color-brand-foreground #FFFFFF - Replace all violet-* Tailwind classes with var(--color-brand*) in: LoginPage, Button, Field (input+checkbox), SearchBar, AppLayout (nav active state) - New Logo component: FM square badge + "FOOD" + "MARKET" typography in brand colors - Put Logo in sidebar header and on LoginPage - Replace Vite default favicon with branded SVG (green square + FOOD MARKET) - Page title "FOOD MARKET", theme-color meta tag for mobile browsers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b2b5393fa
commit
5af8f74b5e
|
|
@ -4,7 +4,8 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>food-market.web</title>
|
<title>FOOD MARKET</title>
|
||||||
|
<meta name="theme-color" content="#00B207" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 387 B |
4
src/food-market.web/public/logo-bg.svg
Normal file
4
src/food-market.web/public/logo-bg.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="456" height="456" viewBox="0 0 456 456">
|
||||||
|
<rect width="456" height="456" fill="#00B207"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 186 B |
7
src/food-market.web/public/logo-fg.svg
Normal file
7
src/food-market.web/public/logo-fg.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="456" height="456" viewBox="0 0 456 456">
|
||||||
|
<g font-family="Arial, Helvetica, sans-serif" font-weight="900" fill="#FFFFFF" text-anchor="middle">
|
||||||
|
<text x="228" y="220" font-size="92" letter-spacing="4">FOOD</text>
|
||||||
|
<text x="228" y="306" font-size="64" letter-spacing="6" fill="#E8F5E9">MARKET</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 417 B |
|
|
@ -7,6 +7,7 @@ import {
|
||||||
LayoutDashboard, Package, FolderTree, Ruler, Percent, Tag,
|
LayoutDashboard, Package, FolderTree, Ruler, Percent, Tag,
|
||||||
Users, Warehouse, Store as StoreIcon, Globe, Coins, LogOut,
|
Users, Warehouse, Store as StoreIcon, Globe, Coins, LogOut,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
import { Logo } from './Logo'
|
||||||
|
|
||||||
interface MeResponse {
|
interface MeResponse {
|
||||||
sub: string
|
sub: string
|
||||||
|
|
@ -51,7 +52,7 @@ export function AppLayout() {
|
||||||
<div className="min-h-screen flex bg-slate-50 dark:bg-slate-950">
|
<div className="min-h-screen flex bg-slate-50 dark:bg-slate-950">
|
||||||
<aside className="w-60 flex-shrink-0 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col">
|
<aside className="w-60 flex-shrink-0 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col">
|
||||||
<div className="h-14 flex items-center px-5 border-b border-slate-200 dark:border-slate-800">
|
<div className="h-14 flex items-center px-5 border-b border-slate-200 dark:border-slate-800">
|
||||||
<span className="font-semibold text-slate-900 dark:text-slate-100">food-market</span>
|
<Logo size={28} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="flex-1 overflow-y-auto py-3">
|
<nav className="flex-1 overflow-y-auto py-3">
|
||||||
|
|
@ -66,7 +67,7 @@ export function AppLayout() {
|
||||||
className={({ isActive }) => cn(
|
className={({ isActive }) => cn(
|
||||||
'flex items-center gap-2.5 px-5 py-1.5 text-sm transition-colors',
|
'flex items-center gap-2.5 px-5 py-1.5 text-sm transition-colors',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-violet-50 dark:bg-violet-950/30 text-violet-700 dark:text-violet-300 font-medium border-r-2 border-violet-600'
|
? 'bg-[var(--color-brand-light)] dark:bg-[var(--color-brand-dark)]/20 text-[var(--color-brand-dark)] dark:text-[var(--color-brand-light)] font-medium border-r-2 border-[var(--color-brand)]'
|
||||||
: 'text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800/50'
|
: 'text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-800/50'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const variants: Record<Variant, string> = {
|
const variants: Record<Variant, string> = {
|
||||||
primary: 'bg-violet-600 hover:bg-violet-700 text-white',
|
primary: 'bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] text-white',
|
||||||
secondary: 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700',
|
secondary: 'bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700',
|
||||||
ghost: 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800',
|
ghost: 'text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800',
|
||||||
danger: 'bg-red-600 hover:bg-red-700 text-white',
|
danger: 'bg-red-600 hover:bg-red-700 text-white',
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export function Field({ label, error, children, className }: FieldProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputClass = 'w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500 disabled:opacity-60'
|
const inputClass = 'w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)] disabled:opacity-60'
|
||||||
|
|
||||||
export function TextInput(props: InputHTMLAttributes<HTMLInputElement>) {
|
export function TextInput(props: InputHTMLAttributes<HTMLInputElement>) {
|
||||||
return <input {...props} className={cn(inputClass, props.className)} />
|
return <input {...props} className={cn(inputClass, props.className)} />
|
||||||
|
|
@ -50,7 +50,7 @@ export function Checkbox({
|
||||||
checked={checked}
|
checked={checked}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(e) => onChange(e.target.checked)}
|
onChange={(e) => onChange(e.target.checked)}
|
||||||
className="w-4 h-4 rounded border-slate-300 text-violet-600 focus:ring-violet-500"
|
className="w-4 h-4 rounded border-slate-300 text-[var(--color-brand)] focus:ring-[var(--color-brand)] accent-[var(--color-brand)]"
|
||||||
/>
|
/>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
25
src/food-market.web/src/components/Logo.tsx
Normal file
25
src/food-market.web/src/components/Logo.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
export function Logo({ size = 28, showText = true, className }: { size?: number; showText?: boolean; className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={cn('flex items-center gap-2.5', className)}>
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center rounded-md font-black text-white leading-none"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--color-brand)',
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
fontSize: Math.floor(size * 0.38),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
FM
|
||||||
|
</div>
|
||||||
|
{showText && (
|
||||||
|
<div className="leading-tight">
|
||||||
|
<div className="font-black text-slate-900 dark:text-slate-100 tracking-wide">FOOD</div>
|
||||||
|
<div className="font-black text-xs tracking-[0.2em]" style={{ color: 'var(--color-brand)' }}>MARKET</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ export function SearchBar({ value, onChange, placeholder = 'Поиск…' }: Se
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className="pl-8 pr-3 py-1.5 w-64 rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500"
|
className="pl-8 pr-3 py-1.5 w-64 rounded-md border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-brand: #6d28d9;
|
--color-brand: #00B207;
|
||||||
--color-brand-foreground: #ffffff;
|
--color-brand-hover: #009305;
|
||||||
|
--color-brand-dark: #007605;
|
||||||
|
--color-brand-light: #E8F5E9;
|
||||||
|
--color-brand-tint: #D7F2D9;
|
||||||
|
--color-brand-foreground: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, type FormEvent } from 'react'
|
import { useState, type FormEvent } from 'react'
|
||||||
import { useNavigate, useLocation } from 'react-router-dom'
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
import { login } from '@/lib/auth'
|
import { login } from '@/lib/auth'
|
||||||
|
import { Logo } from '@/components/Logo'
|
||||||
|
|
||||||
export function LoginPage() {
|
export function LoginPage() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
@ -32,9 +33,9 @@ export function LoginPage() {
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="w-full max-w-md bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8 space-y-5"
|
className="w-full max-w-md bg-white dark:bg-slate-800 rounded-xl shadow-lg p-8 space-y-5"
|
||||||
>
|
>
|
||||||
<div>
|
<div className="space-y-3">
|
||||||
<h1 className="text-2xl font-semibold text-slate-900 dark:text-slate-100">food-market</h1>
|
<Logo size={44} />
|
||||||
<p className="text-sm text-slate-500 mt-1">Вход в систему</p>
|
<p className="text-sm text-slate-500">Вход в систему</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="block space-y-1.5">
|
<label className="block space-y-1.5">
|
||||||
|
|
@ -44,7 +45,7 @@ export function LoginPage() {
|
||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500"
|
className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)]"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -56,7 +57,7 @@ export function LoginPage() {
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-violet-500"
|
className="w-full rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-brand)]"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -70,7 +71,7 @@ export function LoginPage() {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="w-full rounded-md bg-violet-600 hover:bg-violet-700 disabled:opacity-60 text-white font-medium py-2.5 text-sm transition-colors"
|
className="w-full rounded-md bg-[var(--color-brand)] hover:bg-[var(--color-brand-hover)] disabled:opacity-60 text-white font-medium py-2.5 text-sm transition-colors"
|
||||||
>
|
>
|
||||||
{loading ? 'Выполняется вход…' : 'Войти'}
|
{loading ? 'Выполняется вход…' : 'Войти'}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue