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.
- package/package.json +1 -1
- package/template/_claude/commands/create-client.md +1 -1
- package/template/_claude/rules/client/01-project-structure.md +4 -5
- package/template/_claude/rules/client/04-design-system.md +143 -44
- package/template/_claude/rules/client/core.md +8 -7
- package/template/client/README.md +1 -1
- package/template/client/src/app/globals.css +27 -14
- package/template/client/src/app/layout.tsx +7 -7
- package/template/client/src/app/page.tsx +5 -5
- package/template/client/src/app/providers.tsx +1 -1
- package/template/client/src/components/common/ThemeToggle.tsx +59 -0
- package/template/client/src/features/admin/hooks/useAdminSessions.ts +68 -0
- package/template/client/src/features/admin/hooks/useAdminStats.ts +27 -0
- package/template/client/src/features/admin/hooks/useAdminUsers.ts +132 -0
- package/template/client/src/features/admin/services/admin.service.ts +94 -0
- package/template/client/src/features/admin/types/admin.types.ts +65 -0
- package/template/client/src/features/auth/components/AuthInitializer.tsx +18 -1
- package/template/client/src/lib/api/axios.config.ts +20 -1
- package/template/client/src/lib/constants/api-endpoints.ts +9 -0
- package/template/client/src/lib/constants/app.constants.ts +3 -1
- package/template/client/src/lib/constants/routes.ts +6 -0
- package/template/client/src/lib/env.ts +35 -0
- package/template/client/src/styles/fonts/inter-jetbrains.css +16 -0
- package/template/client/src/styles/themes/default.css +92 -0
- package/template/server/package.json +1 -0
- package/template/server/postman/collection.json +168 -50
- package/template/server/prisma/schema.prisma +2 -0
- package/template/server/src/jobs/cleanup-deleted-accounts.job.ts +14 -4
- package/template/server/src/libs/prisma.ts +13 -0
- package/template/server/src/modules/admin/admin.controller.ts +130 -1
- package/template/server/src/modules/admin/admin.repo.ts +289 -0
- package/template/server/src/modules/admin/admin.routes.ts +113 -7
- package/template/server/src/modules/admin/admin.schemas.ts +49 -0
- package/template/server/src/modules/admin/admin.service.ts +154 -0
- package/template/server/src/modules/auth/auth.repo.ts +5 -18
- package/template/server/src/modules/auth/auth.service.ts +20 -28
- package/template/server/src/modules/auth/session.repo.ts +10 -5
- package/template/client/src/components/common/ThemeSwitcher.tsx +0 -112
- package/template/client/src/styles/themes/electric-indigo.css +0 -90
- package/template/client/src/styles/themes/ocean-teal.css +0 -90
- package/template/client/src/styles/themes/rose-pink.css +0 -90
- package/template/client/src/styles/themes/warm-orange.css +0 -90
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
│
|
|
37
|
-
│
|
|
38
|
-
│
|
|
39
|
-
│
|
|
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 **
|
|
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 **
|
|
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
|
|
21
|
+
- No `@layer base { :root { } }` — variables defined on `:root` via theme file
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
## Theme
|
|
25
|
+
## Theme System (Color Management)
|
|
26
26
|
|
|
27
|
-
**ALL color variables live in
|
|
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
|
|
33
|
+
├── app/globals.css ← imports the theme + defines smooth transitions
|
|
34
34
|
└── styles/themes/
|
|
35
|
-
|
|
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
|
|
38
|
+
`globals.css` imports the theme:
|
|
42
39
|
|
|
43
40
|
```css
|
|
44
|
-
@import "../styles/themes/
|
|
41
|
+
@import "../styles/themes/default.css";
|
|
45
42
|
```
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
### Theme File Structure
|
|
48
45
|
|
|
49
|
-
|
|
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:
|
|
57
|
-
--foreground:
|
|
58
|
-
--primary:
|
|
59
|
-
--primary-foreground:
|
|
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:
|
|
65
|
-
--foreground:
|
|
66
|
-
--primary:
|
|
59
|
+
--background: #15130d;
|
|
60
|
+
--foreground: #e9e8e3;
|
|
61
|
+
--primary: #d6724f;
|
|
67
62
|
/* ... dark mode overrides for all tokens */
|
|
68
63
|
}
|
|
69
64
|
```
|
|
70
65
|
|
|
71
|
-
###
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
###
|
|
82
|
+
### Light/Dark Mode Toggle
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
90
|
-
2. **NEVER hardcode
|
|
91
|
-
3. **
|
|
92
|
-
4. **
|
|
93
|
-
5. **
|
|
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-
|
|
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
|
|
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="
|
|
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
|
|
40
|
-
5. **No
|
|
41
|
-
6. **
|
|
42
|
-
7. **
|
|
43
|
-
8. **
|
|
44
|
-
9. **
|
|
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 [
|
|
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
|
|
7
|
+
* THEME — Single theme with light/dark mode
|
|
8
8
|
* ============================================================
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* by
|
|
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
|
|
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/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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-
|
|
29
|
-
--font-
|
|
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 {
|
|
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
|
|
10
|
-
variable: '--font-
|
|
9
|
+
const inter = Inter({
|
|
10
|
+
variable: '--font-inter',
|
|
11
11
|
subsets: ['latin'],
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
variable: '--font-
|
|
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"
|
|
31
|
-
<body className={`${
|
|
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 {
|
|
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
|
-
{/*
|
|
50
|
-
<div className="mt-8
|
|
51
|
-
<
|
|
49
|
+
{/* Light/Dark mode toggle */}
|
|
50
|
+
<div className="mt-8">
|
|
51
|
+
<ThemeToggle />
|
|
52
52
|
</div>
|
|
53
53
|
|
|
54
|
-
<div className="mt-
|
|
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="
|
|
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
|
+
};
|