Skip to content

Commit 4152715

Browse files
authored
Merge pull request #41 from CleanCode366/feature/5-config-theme-system-token-based-multi-theme-architecture
feat: configured token based multi them architecture
2 parents 8845825 + 8d7c11b commit 4152715

23 files changed

Lines changed: 1298 additions & 951 deletions

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"preview": "vite preview",
1414
"storybook": "storybook dev -p 6006",
1515
"build-storybook": "storybook build",
16-
"chromatic": "npx chromatic --project-token=chpt_bd8d0a3ce41cca0"
16+
"chromatic": "npx chromatic --project-token=chpt_bd8d0a3ce41cca0",
17+
"generate:api": "openapi-typescript http://localhost:8080/v3/api-docs -o src/api/generated/types.ts"
1718
},
1819
"lint-staged": {
1920
"*.{js,jsx,ts,tsx,json,css,scss,md}": [
@@ -22,6 +23,7 @@
2223
]
2324
},
2425
"dependencies": {
26+
"@heroicons/react": "^2.2.0",
2527
"@react-oauth/google": "^0.12.2",
2628
"@storybook/react": "^10.3.6",
2729
"@tailwindcss/vite": "^4.2.4",
@@ -30,7 +32,6 @@
3032
"axios": "^1.13.2",
3133
"class-variance-authority": "^0.7.1",
3234
"eslint-plugin-jsx-a11y": "^6.10.2",
33-
"generate:api": "openapi-typescript http://localhost:8080/v3/api-docs -o src/api/generated/types.ts",
3435
"js-cookie": "^3.0.5",
3536
"jwt-decode": "^4.0.0",
3637
"lucide-react": "^0.542.0",
@@ -42,7 +43,8 @@
4243
"react-router-dom": "^7.7.1",
4344
"styled-components": "^6.2.0",
4445
"tailwindcss": "^4.2.4",
45-
"vite-plugin-remove-console": "^2.2.0"
46+
"vite-plugin-remove-console": "^2.2.0",
47+
"zustand": "^5.0.13"
4648
},
4749
"devDependencies": {
4850
"@chromatic-com/storybook": "^5.1.2",
@@ -79,4 +81,4 @@
7981
"vite-tsconfig-paths": "^6.1.1",
8082
"vitest": "^4.1.5"
8183
}
82-
}
84+
}

pnpm-lock.yaml

Lines changed: 570 additions & 554 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/App.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@
44
import { RouterProvider } from 'react-router-dom'
55
import router from './routes'
66
// import styles from "./App.module.css";
7-
import { AuthProvider } from './context/AuthContext'
7+
// import { AuthProvider } from './context/AuthContext'
88
// import * from '@mui/styled-engine-sc' as styledEngineSC;,
99
import { LanguageProvider } from './context/LanguageContext'
1010
import CustomTranslate from './textTranslation/CustomTranslate'
1111

1212
function App() {
1313
return (
14-
<AuthProvider>
14+
// <AuthProvider>
1515
<LanguageProvider>
1616
<CustomTranslate />
1717
{/* <GoogleTranslate/> */}
1818
<RouterProvider router={router} />
19+
{/* <TestCard /> */}
1920
{/* </CustomTranslate> */}
2021
</LanguageProvider>
21-
</AuthProvider>
22+
// </AuthProvider>
2223
)
2324
}
2425
export default App

src/LoginCard.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
function LoginCard() {
2+
3+
const handleGoogleLogin = () => {
4+
// Replace with your actual backend OAuth endpoint
5+
window.location.href = 'http://localhost:8080/oauth2/authorization/google';
6+
};
7+
8+
return (
9+
<div className="fixed inset-0 flex items-center justify-center bg-bg-primary" >
10+
<div className="w-full max-w-sm bg-bg-secondary border border-border-primary rounded-lg shadow-md p-6 space-y-6" >
11+
12+
{/* Heading */}
13+
<div className="space-y-1">
14+
<h2 className="text-2xl text-center font-semibold text-text-primary">
15+
Welcome
16+
</h2>
17+
18+
<p className="text-sm text-center my-3 text-text-secondary">
19+
Sign in using your Google account
20+
</p>
21+
</div>
22+
23+
{/* Google OAuth Button */}
24+
<button
25+
onClick={handleGoogleLogin}
26+
className="
27+
cursor-pointer
28+
w-full
29+
flex
30+
items-center
31+
justify-center
32+
gap-3
33+
py-3
34+
rounded-md
35+
bg-bg-tertiary
36+
text-text-primary
37+
border
38+
border-border-secondary
39+
hover:bg-bg-primary
40+
transition
41+
"
42+
>
43+
{/* Google Icon */}
44+
<svg
45+
xmlns="http://www.w3.org/2000/svg"
46+
viewBox="0 0 48 48"
47+
className="w-5 h-5"
48+
>
49+
<path
50+
fill="#FFC107"
51+
d="M43.6 20.5H42V20H24v8h11.3C33.7 32.7 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3 0 5.7 1.1 7.8 2.9l5.7-5.7C34.1 6.1 29.3 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.3-.4-3.5z"
52+
/>
53+
<path
54+
fill="#FF3D00"
55+
d="M6.3 14.7l6.6 4.8C14.7 15.1 18.9 12 24 12c3 0 5.7 1.1 7.8 2.9l5.7-5.7C34.1 6.1 29.3 4 24 4 16.3 4 9.7 8.3 6.3 14.7z"
56+
/>
57+
<path
58+
fill="#4CAF50"
59+
d="M24 44c5.2 0 10-2 13.5-5.3l-6.2-5.2C29.2 35.1 26.7 36 24 36c-5.3 0-9.7-3.3-11.3-8l-6.5 5C9.5 39.5 16.2 44 24 44z"
60+
/>
61+
<path
62+
fill="#1976D2"
63+
d="M43.6 20.5H42V20H24v8h11.3c-1.1 3.1-3.3 5.5-6.2 7.1l6.2 5.2C39.1 36.7 44 31 44 24c0-1.3-.1-2.3-.4-3.5z"
64+
/>
65+
</svg>
66+
67+
Continue with Google
68+
</button>
69+
70+
{/* Footer */}
71+
<p className="text-xs text-center text-text-tertiary">
72+
By continuing, you agree to our <a href="/terms" className="text-blue-500 hover:underline">Terms of Service</a> and <a href="/privacy" className="text-blue-500 hover:underline">Privacy Policy</a>
73+
</p>
74+
</div>
75+
</div>
76+
);
77+
}
78+
79+
export default LoginCard;

src/TestCard.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// import React from 'react';
2+
import { useTheme } from '@/shared/theme/useTheme';
3+
// import { type ThemeName } from '@/shared/theme/types';
4+
import { themeRegistry } from './shared/theme';
5+
6+
function TestCard() {
7+
const { setTheme, themeName } = useTheme();
8+
// const availableThemes1: ThemeName[] = ['light', 'dark'];
9+
const { availableThemes } = useTheme();
10+
return (
11+
<div className="min-h-screen bg-bg-primary text-text-primary space-y-4 p-4">
12+
<div className="flex gap-2">
13+
{availableThemes.map((theme) => {
14+
const t = themeRegistry[theme];
15+
return (
16+
<button
17+
key={theme}
18+
onClick={() => setTheme(theme)}
19+
className="border px-3 py-1 rounded-md"
20+
disabled={!t.isProductionReady}
21+
>
22+
{theme}
23+
</button>
24+
)
25+
})}
26+
</div>
27+
28+
<div className="bg-bg-primary text-text-primary border-border border-border-secondary rounded-md shadow-md p-4">
29+
Current theme: {themeName}
30+
</div>
31+
32+
<div className="bg-bg-primary text-text-primary border-border border-border-secondary rounded-md shadow-md p-4">
33+
Test Theme
34+
</div>
35+
36+
<div className="bg-bg-primary text-text-primary border-border border-border-secondary rounded-md shadow-md p-4">
37+
Full token test
38+
</div>
39+
</div>
40+
);
41+
}
42+
export default TestCard;

src/ThemeToggleSwitch.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { useState, useRef, useEffect } from 'react';
2+
import { useTheme } from '@/shared/theme/useTheme';
3+
import { SunIcon, MoonIcon } from '@heroicons/react/24/outline';
4+
import type { ThemeName } from './shared/theme';
5+
6+
function ThemeToggleSwitch() {
7+
const { themeName, setTheme } = useTheme();
8+
const [open, setOpen] = useState(false);
9+
const ref = useRef<HTMLDivElement>(null);
10+
11+
// Close dropdown on outside click
12+
useEffect(() => {
13+
const handleClickOutside = (e: MouseEvent) => {
14+
if (ref.current && !ref.current.contains(e.target as Node)) {
15+
setOpen(false);
16+
}
17+
};
18+
document.addEventListener('mousedown', handleClickOutside);
19+
return () => document.removeEventListener('mousedown', handleClickOutside);
20+
}, []);
21+
22+
const handleSelect = (theme: string) => {
23+
if (theme === 'digital' || theme === 'monochrome') return; // placeholder
24+
setTheme(theme as ThemeName);
25+
setOpen(false);
26+
};
27+
28+
return (
29+
<div className="relative inline-block absolute top-4 left-4" ref={ref}>
30+
{/* Toggle Button */}
31+
<button
32+
onClick={() => setOpen((prev) => !prev)}
33+
className="cursor-pointer flex items-center justify-center w-9 h-9 rounded-md
34+
bg-bg-secondary border border-border-secondary
35+
text-text-secondary hover:bg-bg-tertiary
36+
transition"
37+
>
38+
{themeName === 'dark' ?
39+
<MoonIcon className="size-6" />
40+
:
41+
<SunIcon className="size-6" />
42+
}
43+
</button>
44+
45+
{/* Dropdown */}
46+
{open && (
47+
<div
48+
className="absolute right-0 mt-2 w-44 rounded-lg
49+
bg-bg-secondary border border-border-primary
50+
shadow-md p-1 space-y-1 z-50"
51+
>
52+
{/* Light */}
53+
<button
54+
onClick={() => handleSelect('light')}
55+
className="cursor-pointer w-full text-left px-3 py-2 rounded-md text-sm
56+
text-text-primary hover:bg-bg-tertiary transition"
57+
>
58+
Light
59+
</button>
60+
61+
{/* Dark */}
62+
<button
63+
onClick={() => handleSelect('dark')}
64+
className="cursor-pointer w-full text-left px-3 py-2 rounded-md text-sm
65+
text-text-primary hover:bg-bg-tertiary transition"
66+
>
67+
Dark
68+
</button>
69+
70+
{/* Divider */}
71+
<div className="h-px bg-border-tertiary my-1" />
72+
73+
{/* Digital (disabled) */}
74+
<button
75+
disabled
76+
className="cursor-pointer w-full text-left px-3 py-2 rounded-md text-sm
77+
text-text-tertiary cursor-not-allowed"
78+
>
79+
Digital (coming soon)
80+
</button>
81+
82+
{/* Monochrome (disabled) */}
83+
<button
84+
disabled
85+
className="cursor-pointer w-full text-left px-3 py-2 rounded-md text-sm
86+
text-text-tertiary cursor-not-allowed"
87+
>
88+
Monochrome (coming soon)
89+
</button>
90+
</div>
91+
)}
92+
</div>
93+
);
94+
}
95+
96+
export default ThemeToggleSwitch;

0 commit comments

Comments
 (0)