create-tigra 2.6.0 → 2.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/template/_claude/commands/create-client.md +1 -1
  3. package/template/_claude/rules/client/01-project-structure.md +4 -5
  4. package/template/_claude/rules/client/04-design-system.md +143 -44
  5. package/template/_claude/rules/client/core.md +8 -7
  6. package/template/client/README.md +1 -1
  7. package/template/client/src/app/globals.css +27 -14
  8. package/template/client/src/app/layout.tsx +7 -7
  9. package/template/client/src/app/page.tsx +5 -5
  10. package/template/client/src/app/providers.tsx +1 -1
  11. package/template/client/src/components/common/ThemeToggle.tsx +59 -0
  12. package/template/client/src/features/admin/hooks/useAdminSessions.ts +68 -0
  13. package/template/client/src/features/admin/hooks/useAdminStats.ts +27 -0
  14. package/template/client/src/features/admin/hooks/useAdminUsers.ts +132 -0
  15. package/template/client/src/features/admin/services/admin.service.ts +94 -0
  16. package/template/client/src/features/admin/types/admin.types.ts +65 -0
  17. package/template/client/src/features/auth/components/AuthInitializer.tsx +18 -1
  18. package/template/client/src/lib/api/axios.config.ts +20 -1
  19. package/template/client/src/lib/constants/api-endpoints.ts +9 -0
  20. package/template/client/src/lib/constants/app.constants.ts +3 -1
  21. package/template/client/src/lib/constants/routes.ts +6 -0
  22. package/template/client/src/lib/env.ts +35 -0
  23. package/template/client/src/styles/fonts/inter-jetbrains.css +16 -0
  24. package/template/client/src/styles/themes/default.css +92 -0
  25. package/template/server/package.json +1 -0
  26. package/template/server/postman/collection.json +168 -50
  27. package/template/server/prisma/schema.prisma +2 -0
  28. package/template/server/src/jobs/cleanup-deleted-accounts.job.ts +14 -4
  29. package/template/server/src/libs/prisma.ts +13 -0
  30. package/template/server/src/modules/admin/admin.controller.ts +130 -1
  31. package/template/server/src/modules/admin/admin.repo.ts +289 -0
  32. package/template/server/src/modules/admin/admin.routes.ts +113 -7
  33. package/template/server/src/modules/admin/admin.schemas.ts +49 -0
  34. package/template/server/src/modules/admin/admin.service.ts +154 -0
  35. package/template/server/src/modules/auth/auth.repo.ts +5 -18
  36. package/template/server/src/modules/auth/auth.service.ts +20 -28
  37. package/template/server/src/modules/auth/session.repo.ts +10 -5
  38. package/template/client/src/components/common/ThemeSwitcher.tsx +0 -112
  39. package/template/client/src/styles/themes/electric-indigo.css +0 -90
  40. package/template/client/src/styles/themes/ocean-teal.css +0 -90
  41. package/template/client/src/styles/themes/rose-pink.css +0 -90
  42. package/template/client/src/styles/themes/warm-orange.css +0 -90
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tigra",
3
- "version": "2.6.0",
3
+ "version": "2.6.8",
4
4
  "type": "module",
5
5
  "description": "Create a production-ready full-stack app with Next.js 16 + Fastify 5 + Prisma + Redis",
6
6
  "bin": {
@@ -463,7 +463,7 @@ export function Providers({ children }: { children: React.ReactNode }): React.Re
463
463
 
464
464
  #### `src/app/layout.tsx`
465
465
  Root layout:
466
- - Import and use `next/font` (Inter or Geist Sans)
466
+ - Import and use `next/font/google` (fonts defined by the active font preset — default: Inter + JetBrains Mono)
467
467
  - `<html lang="en" suppressHydrationWarning>`
468
468
  - Wrap children in `<Providers>`
469
469
  - Default metadata with app name
@@ -33,11 +33,10 @@ src/
33
33
  │ ├── types/ # <domain>.types.ts
34
34
  │ └── actions/ # <domain>.actions.ts (Server Actions)
35
35
  ├── styles/
36
- └── themes/ # Color theme presets (switch in globals.css import)
37
- ├── warm-orange.css # Default earthy, warm
38
- ├── electric-indigo.css
39
- ├── ocean-teal.css
40
- │ └── rose-pink.css
36
+ ├── themes/ # Color theme (light/dark mode values)
37
+ │ └── default.css # Claude-inspired warm palette (HEX)
38
+ └── fonts/ # Font presets (switch in globals.css import)
39
+ └── inter-jetbrains.css # Default — Inter + JetBrains Mono
41
40
  ├── hooks/ # Global hooks (useDebounce, useLocalStorage, useMediaQuery)
42
41
  ├── lib/
43
42
  │ ├── api/ # axios.config.ts, api.types.ts
@@ -12,85 +12,183 @@ Clean, airy, "expensive" look inspired by Linear, Vercel, Stripe, Arc. Every vis
12
12
 
13
13
  ## CSS Architecture (Source of Truth)
14
14
 
15
- This project uses **Tailwind CSS v4** with **OKLCH color space** and the `@theme inline` directive (not the legacy `tailwind.config.ts`).
15
+ This project uses **Tailwind CSS v4** with **HEX colors** and the `@theme inline` directive (not the legacy `tailwind.config.ts`).
16
16
 
17
17
  **Key differences from Tailwind v3:**
18
18
  - No `tailwind.config.ts` — all config is CSS-based via `@theme inline`
19
- - Colors use **OKLCH** (perceptually uniform) not HSL
19
+ - Colors use **HEX** values (e.g., `#c15f3c`), with `rgba()` for alpha values
20
20
  - `@custom-variant dark` replaces `darkMode: 'class'`
21
- - No `@layer base { :root { } }` — variables defined on `:root` via theme preset files
21
+ - No `@layer base { :root { } }` — variables defined on `:root` via theme file
22
22
 
23
23
  ---
24
24
 
25
- ## Theme Preset System (Color Management)
25
+ ## Theme System (Color Management)
26
26
 
27
- **ALL color variables live in theme preset files, NOT in `globals.css`.** This is the single source of truth for the entire app's color palette.
27
+ **ALL color variables live in `src/styles/themes/default.css`, NOT in `globals.css` or components.** This is the single source of truth for the entire app's color palette.
28
28
 
29
29
  ### How It Works
30
30
 
31
31
  ```
32
32
  src/
33
- ├── app/globals.css ← imports ONE theme preset (switch here)
33
+ ├── app/globals.css ← imports the theme + defines smooth transitions
34
34
  └── styles/themes/
35
- ├── warm-orange.css Earthy, warm (default)
36
- ├── electric-indigo.css ← Modern, bold, tech-forward
37
- ├── ocean-teal.css ← Calm, professional
38
- └── rose-pink.css ← Elegant, creative
35
+ └── default.css Claude-inspired warm palette (HEX)
39
36
  ```
40
37
 
41
- `globals.css` imports the active theme via a single line:
38
+ `globals.css` imports the theme:
42
39
 
43
40
  ```css
44
- @import "../styles/themes/warm-orange.css";
41
+ @import "../styles/themes/default.css";
45
42
  ```
46
43
 
47
- **To switch the entire palette**: change that ONE import line. That's it. Every color in the app updates instantly — light mode, dark mode, charts, sidebar, everything.
44
+ ### Theme File Structure
48
45
 
49
- ### Theme Preset Structure
50
-
51
- Each preset file defines ALL semantic color variables for both `:root` (light) and `.dark` (dark mode):
46
+ `default.css` defines ALL semantic color variables for both `:root` (light) and `.dark` (dark mode) using HEX:
52
47
 
53
48
  ```css
54
49
  :root {
55
50
  --radius: 0.625rem;
56
- --background: oklch(...);
57
- --foreground: oklch(...);
58
- --primary: oklch(...);
59
- --primary-foreground: oklch(...);
51
+ --background: #f4f3ee;
52
+ --foreground: #1a170f;
53
+ --primary: #c15f3c;
54
+ --primary-foreground: #ffffff;
60
55
  /* ... all ~35 semantic tokens */
61
56
  }
62
57
 
63
58
  .dark {
64
- --background: oklch(...);
65
- --foreground: oklch(...);
66
- --primary: oklch(...);
59
+ --background: #15130d;
60
+ --foreground: #e9e8e3;
61
+ --primary: #d6724f;
67
62
  /* ... dark mode overrides for all tokens */
68
63
  }
69
64
  ```
70
65
 
71
- ### Creating a Custom Theme
66
+ ### Customizing Colors
67
+
68
+ To change the brand palette, edit the HEX values in `default.css`. That's it — every color in the app updates instantly for both light and dark modes.
69
+
70
+ ### Smooth Theme Transitions
72
71
 
73
- 1. Copy any existing preset file (e.g., `warm-orange.css`)
74
- 2. Rename it (e.g., `my-brand.css`)
75
- 3. Edit the OKLCH values to match your brand palette
76
- 4. Update the import in `globals.css`: `@import "../styles/themes/my-brand.css";`
72
+ `globals.css` includes a global transition rule in `@layer base` that smoothly animates color changes when toggling light/dark mode:
73
+
74
+ ```css
75
+ *, *::before, *::after {
76
+ transition-property: background-color, color, border-color, box-shadow;
77
+ transition-duration: 200ms;
78
+ transition-timing-function: ease-out;
79
+ }
80
+ ```
77
81
 
78
- ### Available Presets
82
+ ### Light/Dark Mode Toggle
79
83
 
80
- | Preset | Accent | Vibe | Inspired by |
81
- |--------|--------|------|-------------|
82
- | `warm-orange.css` | Terracotta orange | Earthy, warm, approachable | Claude |
83
- | `electric-indigo.css` | Deep indigo-violet | Modern, bold, tech-forward | Linear, Figma |
84
- | `ocean-teal.css` | Deep teal-cyan | Calm, professional, trustworthy | Stripe, Vercel |
85
- | `rose-pink.css` | Soft rose-magenta | Elegant, creative, premium | Dribbble, Notion |
84
+ - Managed by `next-themes` with `attribute="class"` and `defaultTheme="light"`
85
+ - The `ThemeToggle` component (`components/common/ThemeToggle.tsx`) provides the UI
86
+ - The Header component also includes a sun/moon toggle button
86
87
 
87
88
  ### CRITICAL RULES — Color Management
88
89
 
89
- 1. **NEVER add or modify color variables directly in `globals.css`.** All `:root` and `.dark` color variables belong in the active theme preset file only.
90
- 2. **NEVER hardcode OKLCH/hex/rgb values in components.** Always use semantic tokens (`bg-primary`, `text-foreground`).
91
- 3. **To change the brand palette**: switch the import in `globals.css` or edit the active preset file. Never scatter color values across multiple files.
92
- 4. **New semantic tokens**: If you need a new color token (rare), add it to ALL preset files to keep them in sync.
93
- 5. **The `@theme inline` block in `globals.css` maps CSS vars to Tailwind** it does NOT define colors. Colors come from the preset.
90
+ 1. **NEVER add or modify color variables in `globals.css`.** All `:root` and `.dark` color variables belong in `default.css` only.
91
+ 2. **NEVER hardcode hex/rgb values in components.** Always use semantic tokens (`bg-primary`, `text-foreground`).
92
+ 3. **NEVER use OKLCH color values.** All colors must be HEX (e.g., `#c15f3c`). Use `rgba()` only when alpha transparency is needed.
93
+ 4. **NEVER rename CSS variables.** The variable names (`--primary`, `--background`, `--muted`, etc.) are locked for consistency. Only edit their HEX values.
94
+ 5. **NEVER modify the smooth transition rules in `globals.css`.** The `transition-property`, `transition-duration`, and `transition-timing-function` on `*` are part of the theme system and must not be changed or removed.
95
+ 6. **NEVER modify the `@theme inline` block in `globals.css`.** It maps CSS vars to Tailwind — it does NOT define colors. Colors come from `default.css`.
96
+ 7. **To change the brand palette**: edit the HEX values in `default.css`. Never scatter color values across multiple files.
97
+ 8. **New semantic tokens**: If you need a new token (rare), add it to both `:root` and `.dark` in `default.css`.
98
+
99
+ ---
100
+
101
+ ## Font Preset System (Font Management)
102
+
103
+ **Font families are defined in font preset files, NOT hardcoded in components.** This mirrors the color theme preset system — switch the entire font pairing by changing one import.
104
+
105
+ ### How It Works
106
+
107
+ ```
108
+ src/
109
+ ├── app/
110
+ │ ├── layout.tsx ← loads fonts via next/font/google
111
+ │ └── globals.css ← imports ONE font preset (switch here)
112
+ └── styles/fonts/
113
+ └── inter-jetbrains.css ← Default (Inter + JetBrains Mono)
114
+ ```
115
+
116
+ ### Three Semantic Font Roles
117
+
118
+ | Role | CSS Variable | Tailwind Class | Default Font |
119
+ |------|-------------|----------------|--------------|
120
+ | Body text | `--font-sans-value` | `font-sans` | Inter |
121
+ | Headings | `--font-heading-value` | `font-heading` | Inter |
122
+ | Code/mono | `--font-mono-value` | `font-mono` | JetBrains Mono |
123
+
124
+ ### How to Switch Fonts
125
+
126
+ Switching fonts requires two changes:
127
+
128
+ **Step 1 — Update font imports in `layout.tsx`:**
129
+
130
+ ```tsx
131
+ // Change these imports to your desired fonts
132
+ import { Roboto, Fira_Code } from 'next/font/google';
133
+
134
+ const roboto = Roboto({
135
+ variable: '--font-roboto',
136
+ subsets: ['latin'],
137
+ weight: ['400', '500', '600', '700'],
138
+ });
139
+
140
+ const firaCode = Fira_Code({
141
+ variable: '--font-fira-code',
142
+ subsets: ['latin'],
143
+ });
144
+
145
+ // Update the className to use new variables
146
+ <body className={`${roboto.variable} ${firaCode.variable} font-sans antialiased`}>
147
+ ```
148
+
149
+ **Step 2 — Update the font preset file (or create a new one):**
150
+
151
+ ```css
152
+ /* styles/fonts/roboto-fira.css */
153
+ :root {
154
+ --font-sans-value: var(--font-roboto);
155
+ --font-heading-value: var(--font-roboto);
156
+ --font-mono-value: var(--font-fira-code);
157
+ }
158
+ ```
159
+
160
+ Then update the import in `globals.css`:
161
+ ```css
162
+ @import "../styles/fonts/roboto-fira.css";
163
+ ```
164
+
165
+ ### Font Preset Structure
166
+
167
+ Each preset maps raw font variables (set by `next/font/google` in `layout.tsx`) to semantic roles:
168
+
169
+ ```css
170
+ :root {
171
+ --font-sans-value: var(--font-inter); /* body text */
172
+ --font-heading-value: var(--font-inter); /* headings */
173
+ --font-mono-value: var(--font-jetbrains-mono); /* code */
174
+ }
175
+ ```
176
+
177
+ ### Creating a Custom Font Preset
178
+
179
+ 1. Choose your fonts from [Google Fonts](https://fonts.google.com)
180
+ 2. Update `layout.tsx` — import fonts via `next/font/google`, set CSS variable names
181
+ 3. Create a new preset file in `src/styles/fonts/` (or edit the existing one)
182
+ 4. Map your font variables to the three semantic roles
183
+ 5. Update the import in `globals.css` to point to your preset
184
+
185
+ ### CRITICAL RULES — Font Management
186
+
187
+ 1. **NEVER hardcode font-family values in components.** Always use Tailwind classes (`font-sans`, `font-heading`, `font-mono`).
188
+ 2. **ALL font-family mappings live in the font preset file**, not in `globals.css` or components.
189
+ 3. **To change fonts**: update `layout.tsx` imports + update the font preset file. Never scatter font-family values across the codebase.
190
+ 4. **The `@theme inline` block in `globals.css` maps preset variables to Tailwind** — it does NOT define fonts. Fonts come from the preset.
191
+ 5. **Fonts are loaded via `next/font/google`** — this self-hosts fonts automatically at build time. No external requests at runtime, no manual file downloads needed.
94
192
 
95
193
  ---
96
194
 
@@ -154,15 +252,15 @@ Each preset file defines ALL semantic color variables for both `:root` (light) a
154
252
  - Hovered/elevated: `shadow-md` to `shadow-lg`
155
253
  - Modals/popovers: `shadow-xl`
156
254
  - **Glassmorphism**: Only on sticky headers, floating toolbars, modal backdrops. Never on content cards.
157
- `backdrop-filter: blur(12px) saturate(1.5); background: oklch(from var(--background) l c h / 0.8);`
255
+ Use `backdrop-blur-md` + `bg-background/80` in Tailwind.
158
256
  - **No pure black/white**: Use `--background` and `--foreground` tokens (already off-pure).
159
257
 
160
258
  ---
161
259
 
162
260
  ## Typography
163
261
 
164
- - **Font**: Inter v4 (variable) or Geist Sans via `next/font`.
165
- - **Headings**: `text-wrap: balance`, `leading-tight`. Mobile-first responsive sizes:
262
+ - **Font**: Defined by the active font preset (default: Inter for sans/heading, JetBrains Mono for mono). See "Font Preset System" above for how to switch.
263
+ - **Headings**: Use `font-heading`. `text-wrap: balance`, `leading-tight`. Mobile-first responsive sizes:
166
264
  - H1: `text-2xl md:text-3xl lg:text-4xl`
167
265
  - H2: `text-xl md:text-2xl`
168
266
  - H3: `text-lg md:text-xl`
@@ -280,7 +378,8 @@ Link: transition-colors duration-150 active:opacity-70 md:hover:text-primary
280
378
 
281
379
  ## Dark Mode
282
380
 
283
- - Use `next-themes` with `attribute="class"`, `defaultTheme="system"`.
381
+ - Use `next-themes` with `attribute="class"`, `defaultTheme="light"`.
382
+ - Smooth transitions handled by the global CSS transition rules in `globals.css` — do NOT add `disableTransitionOnChange` to `ThemeProvider`.
284
383
  - Reduce shadow visibility in dark mode (use subtle light borders instead).
285
384
  - Consider `brightness-90` on images in dark mode.
286
385
  - Add `suppressHydrationWarning` to `<html>` tag.
@@ -9,7 +9,7 @@
9
9
  | Creating files, folders, feature modules | `01-project-structure.md` |
10
10
  | Building components, writing types/interfaces | `02-components-and-types.md` |
11
11
  | Fetching data, managing state, calling APIs, forms | `03-data-and-state.md` |
12
- | Choosing colors, styling, typography, spacing, motion, **theme presets** | `04-design-system.md` |
12
+ | Choosing colors, styling, typography, spacing, motion, **theme colors**, **font presets** | `04-design-system.md` |
13
13
  | Auth tokens, env vars, security headers | `05-security.md` |
14
14
  | UX psychology, cognitive load, a11y, performance | `06-ux-checklist.md` |
15
15
 
@@ -36,9 +36,10 @@ State: Server data (SSR) → Server Components
36
36
  1. **Mobile-first**: All Tailwind classes start at mobile. Desktop is the enhancement (`md:`, `lg:`). Touch targets min 44x44px. No functionality behind hover-only states.
37
37
  2. **Server Components by default.** Only add `'use client'` when you need hooks, state, or event handlers.
38
38
  3. **Component limits**: Max 250 lines, max 5 props, max 3 JSX nesting levels.
39
- 4. **No hardcoded colors**: Use Tailwind semantic tokens (`bg-primary`, `text-foreground`). Never hex/rgb. **All color variables live in theme preset files (`src/styles/themes/*.css`), NOT in `globals.css` or components.** To change the palette, switch the import in `globals.css` or edit the active preset. Read `04-design-system.md` → "Theme Preset System" for details.
40
- 5. **No inline styles**: Tailwind only. Use `cn()` for conditional classes.
41
- 6. **Import order**: React/Next third-party UI local → hooks → services → types → utils.
42
- 7. **Forms**: Validate with Zod. Always validate client-side AND server-side.
43
- 8. **Security**: Never inject raw HTML without sanitization. Never prefix secrets with `NEXT_PUBLIC_`.
44
- 9. **Deployment**: Never remove `output: "standalone"` from `next.config.ts`. When adding `NEXT_PUBLIC_*` env vars, also add them as `ARG` + `ENV` in the Dockerfile builder stage. Read `07-deployment.md` for details.
39
+ 4. **No hardcoded colors**: Use Tailwind semantic tokens (`bg-primary`, `text-foreground`). Never hardcode hex/rgb in components. **All color variables live in `src/styles/themes/default.css` using HEX values, NOT in `globals.css` or components.** Only edit the HEX values in `default.css` to customize the palette — never rename variables, change the file structure, or move color definitions elsewhere. The smooth transition system in `globals.css` and the variable naming are locked. Read `04-design-system.md` → "Theme System" for details.
40
+ 5. **No hardcoded fonts**: Use Tailwind font classes (`font-sans`, `font-heading`, `font-mono`). Never hardcode `font-family` in components. **Font families are defined in font preset files (`src/styles/fonts/*.css`).** To change fonts, update the `next/font/google` imports in `layout.tsx` and the font preset file. Read `04-design-system.md` → "Font Preset System" for details.
41
+ 6. **No inline styles**: Tailwind only. Use `cn()` for conditional classes.
42
+ 7. **Import order**: React/Next third-party UI local → hooks → services → types → utils.
43
+ 8. **Forms**: Validate with Zod. Always validate client-side AND server-side.
44
+ 9. **Security**: Never inject raw HTML without sanitization. Never prefix secrets with `NEXT_PUBLIC_`.
45
+ 10. **Deployment**: Never remove `output: "standalone"` from `next.config.ts`. When adding `NEXT_PUBLIC_*` env vars, also add them as `ARG` + `ENV` in the Dockerfile builder stage. Read `07-deployment.md` for details.
@@ -18,7 +18,7 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
18
18
 
19
19
  You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
20
 
21
- This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Inter](https://fonts.google.com/specimen/Inter) and [JetBrains Mono](https://fonts.google.com/specimen/JetBrains+Mono). Fonts are managed via the font preset system in `src/styles/fonts/` — see the design system rules for how to switch fonts.
22
22
 
23
23
  ## Learn More
24
24
 
@@ -4,29 +4,37 @@
4
4
 
5
5
  /*
6
6
  * ============================================================
7
- * THEME PRESETS All themes loaded, switched via data-theme
7
+ * THEME — Single theme with light/dark mode
8
8
  * ============================================================
9
- * warm-orange.css is the default (uses :root / .dark selectors).
10
- * Other presets are scoped to [data-theme="<name>"] and activated
11
- * by the ThemeSwitcher component setting data-theme on <html>.
9
+ * Colors are defined in default.css using :root (light) and
10
+ * .dark (dark mode) selectors. Light/dark toggle is handled
11
+ * by next-themes setting the "dark" class on <html>.
12
12
  *
13
- * To create a custom theme: copy any preset, scope it under
14
- * [data-theme="your-name"] / .dark[data-theme="your-name"],
15
- * import it below, and add it to ThemeSwitcher's PALETTES array.
13
+ * To customize colors: edit src/styles/themes/default.css
16
14
  * ============================================================
17
15
  */
18
- @import "../styles/themes/warm-orange.css";
19
- @import "../styles/themes/electric-indigo.css";
20
- @import "../styles/themes/ocean-teal.css";
21
- @import "../styles/themes/rose-pink.css";
16
+ @import "../styles/themes/default.css";
17
+
18
+ /*
19
+ * ============================================================
20
+ * FONT PRESET — Switch the entire font pairing here
21
+ * ============================================================
22
+ * To change fonts:
23
+ * 1. Update the font imports in layout.tsx (next/font/google)
24
+ * 2. Create or edit a preset in src/styles/fonts/
25
+ * 3. Change the import below to your preset
26
+ * ============================================================
27
+ */
28
+ @import "../styles/fonts/inter-jetbrains.css";
22
29
 
23
30
  @custom-variant dark (&:is(.dark *));
24
31
 
25
32
  @theme inline {
26
33
  --color-background: var(--background);
27
34
  --color-foreground: var(--foreground);
28
- --font-sans: var(--font-geist-sans);
29
- --font-mono: var(--font-geist-mono);
35
+ --font-sans: var(--font-sans-value);
36
+ --font-heading: var(--font-heading-value);
37
+ --font-mono: var(--font-mono-value);
30
38
  --color-sidebar-ring: var(--sidebar-ring);
31
39
  --color-sidebar-border: var(--sidebar-border);
32
40
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -72,8 +80,13 @@
72
80
  }
73
81
 
74
82
  @layer base {
75
- * {
83
+ *,
84
+ *::before,
85
+ *::after {
76
86
  @apply border-border outline-ring/50;
87
+ transition-property: background-color, color, border-color, box-shadow;
88
+ transition-duration: 200ms;
89
+ transition-timing-function: ease-out;
77
90
  }
78
91
  body {
79
92
  @apply bg-background text-foreground;
@@ -1,18 +1,18 @@
1
1
  import type { Metadata } from 'next';
2
2
  import type React from 'react';
3
- import { Geist, Geist_Mono } from 'next/font/google';
3
+ import { Inter, JetBrains_Mono } from 'next/font/google';
4
4
 
5
5
  import { Providers } from './providers';
6
6
  import { APP_NAME } from '@/lib/constants/app.constants';
7
7
  import './globals.css';
8
8
 
9
- const geistSans = Geist({
10
- variable: '--font-geist-sans',
9
+ const inter = Inter({
10
+ variable: '--font-inter',
11
11
  subsets: ['latin'],
12
12
  });
13
13
 
14
- const geistMono = Geist_Mono({
15
- variable: '--font-geist-mono',
14
+ const jetbrainsMono = JetBrains_Mono({
15
+ variable: '--font-jetbrains-mono',
16
16
  subsets: ['latin'],
17
17
  });
18
18
 
@@ -27,8 +27,8 @@ export default function RootLayout({
27
27
  children: React.ReactNode;
28
28
  }>): React.ReactElement {
29
29
  return (
30
- <html lang="en" className="dark" suppressHydrationWarning>
31
- <body className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}>
30
+ <html lang="en" suppressHydrationWarning>
31
+ <body className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased`}>
32
32
  <Providers>{children}</Providers>
33
33
  </body>
34
34
  </html>
@@ -3,7 +3,7 @@ import type { Metadata } from 'next';
3
3
 
4
4
  import Image from 'next/image';
5
5
 
6
- import { ThemeSwitcher } from '@/components/common/ThemeSwitcher';
6
+ import { ThemeToggle } from '@/components/common/ThemeToggle';
7
7
 
8
8
  import { APP_NAME } from '@/lib/constants/app.constants';
9
9
 
@@ -46,12 +46,12 @@ export default function WelcomePage(): React.ReactElement {
46
46
  <span className="font-semibold text-foreground">create-tigra</span>
47
47
  </p>
48
48
 
49
- {/* Theme Palette Switcher */}
50
- <div className="mt-8 pb-6">
51
- <ThemeSwitcher />
49
+ {/* Light/Dark mode toggle */}
50
+ <div className="mt-8">
51
+ <ThemeToggle />
52
52
  </div>
53
53
 
54
- <div className="mt-4 flex flex-col gap-3 sm:flex-row sm:gap-4">
54
+ <div className="mt-6 flex flex-col gap-3 sm:flex-row sm:gap-4">
55
55
  <a
56
56
  href="https://github.com/BehzodKarimov/create-tigra"
57
57
  target="_blank"
@@ -29,7 +29,7 @@ export function Providers({ children }: { children: React.ReactNode }): React.Re
29
29
  return (
30
30
  <ReduxProvider store={store}>
31
31
  <QueryClientProvider client={queryClient}>
32
- <ThemeProvider attribute="class" defaultTheme="dark" disableTransitionOnChange>
32
+ <ThemeProvider attribute="class" defaultTheme="light">
33
33
  <AuthInitializer>
34
34
  {children}
35
35
  </AuthInitializer>
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+ import type React from 'react';
4
+ import { useCallback } from 'react';
5
+
6
+ import { useTheme } from 'next-themes';
7
+ import { Sun, Moon } from 'lucide-react';
8
+
9
+ import { cn } from '@/lib/utils';
10
+
11
+ export function ThemeToggle(): React.ReactElement {
12
+ const { theme, setTheme } = useTheme();
13
+
14
+ const selectLight = useCallback((): void => {
15
+ setTheme('light');
16
+ }, [setTheme]);
17
+
18
+ const selectDark = useCallback((): void => {
19
+ setTheme('dark');
20
+ }, [setTheme]);
21
+
22
+ return (
23
+ <div className="flex flex-col items-center gap-3">
24
+ <p className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
25
+ Appearance
26
+ </p>
27
+ <div className="flex gap-2 rounded-xl border border-border/50 bg-muted/50 p-1.5 transition-none">
28
+ <button
29
+ type="button"
30
+ onClick={selectLight}
31
+ aria-label="Switch to light mode"
32
+ className={cn(
33
+ 'flex min-h-11 items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-transform duration-200 active:scale-[0.97]',
34
+ theme !== 'dark'
35
+ ? 'bg-background text-foreground shadow-sm'
36
+ : 'text-muted-foreground md:hover:text-foreground',
37
+ )}
38
+ >
39
+ <Sun className="h-4 w-4" />
40
+ Light
41
+ </button>
42
+ <button
43
+ type="button"
44
+ onClick={selectDark}
45
+ aria-label="Switch to dark mode"
46
+ className={cn(
47
+ 'flex min-h-11 items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-transform duration-200 active:scale-[0.97]',
48
+ theme === 'dark'
49
+ ? 'bg-background text-foreground shadow-sm'
50
+ : 'text-muted-foreground md:hover:text-foreground',
51
+ )}
52
+ >
53
+ <Moon className="h-4 w-4" />
54
+ Dark
55
+ </button>
56
+ </div>
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
4
+ import { toast } from 'sonner';
5
+
6
+ import { getErrorMessage } from '@/lib/utils/error';
7
+ import { adminService } from '../services/admin.service';
8
+ import { adminKeys } from './useAdminUsers';
9
+
10
+ import type { IAdminSession, IGetSessionsParams } from '../types/admin.types';
11
+
12
+ // ─── Session List ───────────────────────────────────────────────────────────
13
+
14
+ interface UseAdminSessionsReturn {
15
+ sessions: IAdminSession[];
16
+ pagination: {
17
+ page: number;
18
+ limit: number;
19
+ totalItems: number;
20
+ totalPages: number;
21
+ hasNextPage: boolean;
22
+ hasPreviousPage: boolean;
23
+ } | undefined;
24
+ isLoading: boolean;
25
+ error: Error | null;
26
+ }
27
+
28
+ export const useAdminSessions = (params: IGetSessionsParams = {}): UseAdminSessionsReturn => {
29
+ const { data, isLoading, error } = useQuery({
30
+ queryKey: adminKeys.sessionList(params),
31
+ queryFn: () => adminService.getSessions(params),
32
+ });
33
+
34
+ return {
35
+ sessions: data?.items ?? [],
36
+ pagination: data?.pagination,
37
+ isLoading,
38
+ error,
39
+ };
40
+ };
41
+
42
+ // ─── Force Expire Session ───────────────────────────────────────────────────
43
+
44
+ interface UseForceExpireSessionReturn {
45
+ expireSession: (sessionId: string) => void;
46
+ isExpiring: boolean;
47
+ }
48
+
49
+ export const useForceExpireSession = (): UseForceExpireSessionReturn => {
50
+ const queryClient = useQueryClient();
51
+
52
+ const mutation = useMutation({
53
+ mutationFn: (sessionId: string) => adminService.deleteSession(sessionId),
54
+ onSuccess: () => {
55
+ toast.success('Session expired successfully');
56
+ queryClient.invalidateQueries({ queryKey: adminKeys.sessions() });
57
+ queryClient.invalidateQueries({ queryKey: adminKeys.stats() });
58
+ },
59
+ onError: (error) => {
60
+ toast.error(getErrorMessage(error));
61
+ },
62
+ });
63
+
64
+ return {
65
+ expireSession: mutation.mutate,
66
+ isExpiring: mutation.isPending,
67
+ };
68
+ };
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import { useQuery } from '@tanstack/react-query';
4
+
5
+ import { adminService } from '../services/admin.service';
6
+ import { adminKeys } from './useAdminUsers';
7
+
8
+ import type { IDashboardStats } from '../types/admin.types';
9
+
10
+ interface UseAdminStatsReturn {
11
+ stats: IDashboardStats | undefined;
12
+ isLoading: boolean;
13
+ error: Error | null;
14
+ }
15
+
16
+ export const useAdminStats = (): UseAdminStatsReturn => {
17
+ const { data, isLoading, error } = useQuery({
18
+ queryKey: adminKeys.stats(),
19
+ queryFn: () => adminService.getDashboardStats(),
20
+ });
21
+
22
+ return {
23
+ stats: data,
24
+ isLoading,
25
+ error,
26
+ };
27
+ };