create-tigra 2.7.0 → 2.7.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tigra",
3
- "version": "2.7.0",
3
+ "version": "2.7.2",
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": {
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: clean-ui
3
+ description: Remove the starter welcome page UI and replace with a blank canvas, preserving all client-side functionality (auth, hooks, services, store, middleware, utils)
4
+ ---
5
+
6
+ The user wants to remove the starter/welcome UI from the scaffolded client and start with a blank page.
7
+
8
+ ## What this skill does
9
+
10
+ Replaces the demo welcome page (`src/app/page.tsx`) with the absolute bare minimum — just a centered "Ready to build." text. **Nothing else is touched** — all functional infrastructure remains intact.
11
+
12
+ ## What gets replaced
13
+
14
+ | File | Action | Reason |
15
+ |------|--------|--------|
16
+ | `src/app/page.tsx` | **Replace** with blank canvas | Remove ALL demo content — hero, ambient glow, GitHub links, buttons, everything |
17
+
18
+ ## What is NOT touched (preserved as-is)
19
+
20
+ - `src/components/layout/` — Header, Footer, MainLayout
21
+ - `src/app/layout.tsx` — root layout with providers
22
+ - `src/app/providers.tsx` — Redux, React Query, themes, AuthInitializer
23
+ - `src/app/error.tsx`, `src/app/not-found.tsx`, `src/app/loading.tsx`
24
+ - `src/middleware.ts` — route protection
25
+ - `src/features/auth/**` — entire auth system
26
+ - `src/components/common/**` — ThemeToggle, EmptyState, LoadingSpinner, etc.
27
+ - `src/components/ui/**` — all shadcn/ui components
28
+ - `src/hooks/**`, `src/store/**`, `src/lib/**`, `src/styles/**`
29
+ - All config files
30
+
31
+ ## Steps
32
+
33
+ 1. Read `src/app/page.tsx` to confirm it exists.
34
+ 2. Replace its contents with the clean page below. Use the **exact** template — do not add anything.
35
+ 3. Confirm to the user what was done.
36
+
37
+ ## Clean page template
38
+
39
+ Replace `src/app/page.tsx` with **exactly** this — no additions, no modifications:
40
+
41
+ ```tsx
42
+ import type React from 'react';
43
+
44
+ export default function HomePage(): React.ReactElement {
45
+ return (
46
+ <main className="flex min-h-dvh items-center justify-center">
47
+ <p className="text-sm text-muted-foreground">Ready to build.</p>
48
+ </main>
49
+ );
50
+ }
51
+ ```
52
+
53
+ **CRITICAL**: Do NOT add anything beyond what is in the template above. No heading, no links, no buttons, no metadata, no imports beyond React. The entire point is a blank canvas.
54
+
55
+ ## Response format
56
+
57
+ After completing, respond with:
58
+
59
+ ```
60
+ Starter UI cleaned. `src/app/page.tsx` is now a blank canvas.
61
+
62
+ Everything else is untouched — auth, hooks, services, store, middleware, components, and design system are all intact.
63
+ ```
@@ -0,0 +1,109 @@
1
+ ---
2
+ name: theme
3
+ description: Change the color palette in the client theme file. Only swaps HEX values — never touches variable names, file structure, or conventions.
4
+ argument-hint: "[color description or specific HEX values]"
5
+ ---
6
+
7
+ # Theme Palette Changer
8
+
9
+ The user wants to change the color palette. Their input: **$ARGUMENTS**
10
+
11
+ ## Your ONLY job
12
+
13
+ Replace HEX color values in `src/styles/themes/default.css` (or the equivalent theme file in the active project's client directory). That's it.
14
+
15
+ ## What you MUST NOT do
16
+
17
+ - **DO NOT** rename, add, or remove any CSS variable names (e.g., `--primary`, `--background`, `--muted`)
18
+ - **DO NOT** change the file structure, move files, or create new files
19
+ - **DO NOT** touch `globals.css`, components, or any other file
20
+ - **DO NOT** change `--radius` or any non-color value
21
+ - **DO NOT** convert HEX to OKLCH, HSL, or any other format — stay in HEX
22
+ - **DO NOT** remove the `.dark` selector or the `:root` selector
23
+ - **DO NOT** remove or rewrite comments unless updating the palette description at the top
24
+ - **DO NOT** change `rgba()` values to HEX — keep `rgba()` where it already exists (e.g., dark mode `--border`, `--input`)
25
+ - **DO NOT** touch any code outside the theme file — no components, no Tailwind config, no globals
26
+
27
+ ## Before making changes — ask these questions if not answered
28
+
29
+ You MUST gather enough information before editing. If the user's input doesn't cover these, ask BEFORE making any changes:
30
+
31
+ ### Required information
32
+
33
+ 1. **Primary/brand color** — What is the main accent color? (at minimum, you need this)
34
+
35
+ ### Clarifying questions (ask if not addressed)
36
+
37
+ 2. **Dark mode** — one of:
38
+ - "Will you provide dark mode colors yourself, or should I generate them from your palette?"
39
+ - Skip if user explicitly says "light mode only" or provides both sets
40
+
41
+ 3. **Palette vibe/mood** — if the user only gave a single color or vague description:
42
+ - "What's the mood? (e.g., warm, cool, corporate, playful, minimal, luxury)"
43
+ - This helps you pick complementary background, muted, secondary, and accent colors
44
+
45
+ 4. **Background preference** — if not obvious from context:
46
+ - "Do you want a light cream/warm background, a cool/gray background, or pure white?"
47
+
48
+ 5. **Destructive/success/warning/info** — if the user provides a full custom palette but skips these:
49
+ - "Should I keep the current red/green/amber/blue for status colors, or adjust them to match your new palette?"
50
+
51
+ ### When you have enough
52
+
53
+ - User gives a full palette with explicit HEX values for most tokens → just apply them, generate any missing ones
54
+ - User gives a brand color + mood → generate a cohesive palette and present it for approval before applying
55
+ - User gives a full set of colors for both light and dark → apply directly
56
+
57
+ ## How to apply changes
58
+
59
+ 1. **Read** the current `default.css` theme file first
60
+ 2. **Locate** the correct theme file:
61
+ - In a scaffolded project: `client/src/styles/themes/default.css`
62
+ - In the template: `template/client/src/styles/themes/default.css`
63
+ - Check which one exists in the current working directory
64
+ 3. **Present** your proposed palette to the user in a readable table BEFORE editing:
65
+
66
+ | Token | Current | New (Light) | New (Dark) |
67
+ |-------|---------|-------------|------------|
68
+ | --background | #f4f3ee | #... | #... |
69
+ | --primary | #c15f3c | #... | #... |
70
+ | ... | ... | ... | ... |
71
+
72
+ 4. **Wait for user approval** — do not edit until they confirm
73
+ 5. **Edit** the file using the Edit tool — only change HEX values
74
+ 6. **Update** the comment block at the top to reflect the new palette name/vibe (e.g., "Ocean blue palette" instead of "Claude-inspired warm palette")
75
+ 7. **Confirm** what was changed in a brief summary
76
+
77
+ ## Palette generation guidelines
78
+
79
+ When generating colors from a brand color, follow these principles:
80
+
81
+ - **Background**: Very desaturated, light tint of the brand hue (light mode) / very dark shade (dark mode)
82
+ - **Foreground**: Near-black with a hint of the brand hue (light mode) / near-white (dark mode)
83
+ - **Primary**: The brand color itself (light) / slightly lighter/more vibrant version (dark)
84
+ - **Primary-foreground**: White or near-white for contrast against primary
85
+ - **Secondary/muted/accent**: Desaturated, low-contrast versions of the brand palette
86
+ - **Card/popover**: White or very slight tint (light) / slightly elevated dark shade (dark)
87
+ - **Border/input**: Very subtle, low-contrast separator colors
88
+ - **Ring**: Same as primary (focus ring should match brand)
89
+ - **Destructive**: Red family (#e7000b light / #ff6467 dark) — adjust warmth/coolness to match palette
90
+ - **Success**: Green family — adjust to match palette temperature
91
+ - **Warning**: Amber/yellow family — adjust to match palette temperature
92
+ - **Info**: Blue family — adjust to match palette temperature
93
+ - **Chart colors**: 5 distinct, harmonious colors for data visualization
94
+ - **Sidebar**: Slightly different shade than main background for visual separation
95
+
96
+ ### Dark mode rules
97
+
98
+ - Increase brightness of the primary color (not just invert)
99
+ - Background should be very dark (not pure black) with a hint of the brand hue
100
+ - Borders use `rgba()` for subtle transparency — keep this pattern
101
+ - Foreground colors should be off-white, not pure #ffffff
102
+ - Reduce contrast slightly compared to light mode to reduce eye strain
103
+
104
+ ## Edge cases
105
+
106
+ - If user says "make it blue" → ask for a specific shade or suggest 3 options (e.g., ocean #0066cc, royal #4169e1, navy #1a237e)
107
+ - If user provides only RGB or HSL → convert to HEX yourself, don't ask them to convert
108
+ - If user wants to keep some colors and change others → only change the ones they specified
109
+ - If the theme file doesn't exist → tell the user to scaffold the project first, don't create the file
@@ -1,47 +1,47 @@
1
- {
2
- "name": "{{PROJECT_NAME}}-client",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start",
9
- "lint": "eslint src/",
10
- "generate:env": "node -e \"import('fs').then(f=>{if(f.existsSync('.env'))console.log('.env already exists, skipping');else{f.copyFileSync('.env.example','.env');console.log('.env created from .env.example')}})\""
11
- },
12
- "dependencies": {
13
- "@hookform/resolvers": "^5.2.2",
14
- "@reduxjs/toolkit": "^2.11.2",
15
- "@tanstack/react-query": "^5.90.21",
16
- "axios": "^1.13.5",
17
- "class-variance-authority": "^0.7.1",
18
- "clsx": "^2.1.1",
19
- "lucide-react": "^0.563.0",
20
- "next": "16.1.6",
21
- "next-themes": "^0.4.6",
22
- "radix-ui": "^1.4.3",
23
- "react": "19.2.3",
24
- "react-dom": "19.2.3",
25
- "react-hook-form": "^7.71.1",
26
- "react-redux": "^9.2.0",
27
- "sonner": "^2.0.7",
28
- "tailwind-merge": "^3.4.0",
29
- "tailwindcss-animate": "^1.0.7",
30
- "zod": "^4.3.6"
31
- },
32
- "overrides": {
33
- "minimatch": ">=10.2.1"
34
- },
35
- "devDependencies": {
36
- "@tailwindcss/postcss": "^4",
37
- "@types/node": "^20",
38
- "@types/react": "^19",
39
- "@types/react-dom": "^19",
40
- "eslint": "^9",
41
- "eslint-config-next": "16.1.6",
42
- "shadcn": "^3.8.4",
43
- "tailwindcss": "^4",
44
- "tw-animate-css": "^1.4.0",
45
- "typescript": "^5"
46
- }
47
- }
1
+ {
2
+ "name": "{{PROJECT_NAME}}-client",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint src/",
10
+ "generate:env": "node -e \"import('fs').then(f=>{if(f.existsSync('.env'))console.log('.env already exists, skipping');else{f.copyFileSync('.env.example','.env');console.log('.env created from .env.example')}})\""
11
+ },
12
+ "dependencies": {
13
+ "@hookform/resolvers": "^5.2.2",
14
+ "@reduxjs/toolkit": "^2.11.2",
15
+ "@tanstack/react-query": "^5.90.21",
16
+ "axios": "^1.13.5",
17
+ "class-variance-authority": "^0.7.1",
18
+ "clsx": "^2.1.1",
19
+ "lucide-react": "^0.563.0",
20
+ "next": "16.1.6",
21
+ "next-themes": "^0.4.6",
22
+ "radix-ui": "^1.4.3",
23
+ "react": "19.2.3",
24
+ "react-dom": "19.2.3",
25
+ "react-hook-form": "^7.71.1",
26
+ "react-redux": "^9.2.0",
27
+ "sonner": "^2.0.7",
28
+ "tailwind-merge": "^3.4.0",
29
+ "tailwindcss-animate": "^1.0.7",
30
+ "zod": "^4.3.6"
31
+ },
32
+ "overrides": {
33
+ "minimatch": ">=10.2.1"
34
+ },
35
+ "devDependencies": {
36
+ "@tailwindcss/postcss": "^4",
37
+ "@types/node": "^20",
38
+ "@types/react": "^19",
39
+ "@types/react-dom": "^19",
40
+ "eslint": "^9",
41
+ "eslint-config-next": "16.1.6",
42
+ "shadcn": "^3.8.4",
43
+ "tailwindcss": "^4",
44
+ "tw-animate-css": "^1.4.0",
45
+ "typescript": "^5"
46
+ }
47
+ }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import type React from 'react';
4
- import { useCallback } from 'react';
4
+ import { Suspense, useCallback } from 'react';
5
5
 
6
6
  import { useRouter, useSearchParams, usePathname } from 'next/navigation';
7
7
  import { ChevronLeft, ChevronRight } from 'lucide-react';
@@ -13,7 +13,7 @@ interface PaginationProps {
13
13
  totalPages: number;
14
14
  }
15
15
 
16
- export const Pagination = ({ page, totalPages }: PaginationProps): React.ReactElement => {
16
+ const PaginationInner = ({ page, totalPages }: PaginationProps): React.ReactElement => {
17
17
  const router = useRouter();
18
18
  const pathname = usePathname();
19
19
  const searchParams = useSearchParams();
@@ -53,3 +53,11 @@ export const Pagination = ({ page, totalPages }: PaginationProps): React.ReactEl
53
53
  </div>
54
54
  );
55
55
  };
56
+
57
+ export const Pagination = ({ page, totalPages }: PaginationProps): React.ReactElement => {
58
+ return (
59
+ <Suspense>
60
+ <PaginationInner page={page} totalPages={totalPages} />
61
+ </Suspense>
62
+ );
63
+ };
@@ -1,8 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import type React from 'react';
4
+ import { Suspense } from 'react';
4
5
 
5
6
  import Link from 'next/link';
7
+ import { useSearchParams } from 'next/navigation';
6
8
  import { useForm } from 'react-hook-form';
7
9
  import { zodResolver } from '@hookform/resolvers/zod';
8
10
  import { z } from 'zod';
@@ -22,8 +24,11 @@ const loginSchema = z.object({
22
24
 
23
25
  type LoginFormData = z.infer<typeof loginSchema>;
24
26
 
25
- export const LoginForm = (): React.ReactElement => {
27
+ const LoginFormInner = (): React.ReactElement => {
26
28
  const { login, isLoggingIn } = useAuth();
29
+ const searchParams = useSearchParams();
30
+ const from = searchParams.get('from');
31
+ const redirectTo = from && from.startsWith('/') && !from.startsWith('//') ? from : undefined;
27
32
 
28
33
  const {
29
34
  register,
@@ -34,7 +39,7 @@ export const LoginForm = (): React.ReactElement => {
34
39
  });
35
40
 
36
41
  const onSubmit = (data: LoginFormData): void => {
37
- login(data);
42
+ login(data, redirectTo);
38
43
  };
39
44
 
40
45
  return (
@@ -105,3 +110,11 @@ export const LoginForm = (): React.ReactElement => {
105
110
  </div>
106
111
  );
107
112
  };
113
+
114
+ export const LoginForm = (): React.ReactElement => {
115
+ return (
116
+ <Suspense>
117
+ <LoginFormInner />
118
+ </Suspense>
119
+ );
120
+ };
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
- import { useCallback } from 'react';
3
+ import { useCallback, useRef } from 'react';
4
4
 
5
5
  import { useMutation } from '@tanstack/react-query';
6
- import { useRouter, useSearchParams } from 'next/navigation';
6
+ import { useRouter } from 'next/navigation';
7
7
  import { toast } from 'sonner';
8
8
 
9
9
  import { useAppDispatch, useAppSelector } from '@/store/hooks';
@@ -18,7 +18,7 @@ interface UseAuthReturn {
18
18
  user: IUser | null;
19
19
  isAuthenticated: boolean;
20
20
  isInitializing: boolean;
21
- login: (data: ILoginRequest) => void;
21
+ login: (data: ILoginRequest, redirectTo?: string) => void;
22
22
  register: (data: IRegisterRequest) => void;
23
23
  logout: () => Promise<void>;
24
24
  isLoggingIn: boolean;
@@ -29,17 +29,15 @@ interface UseAuthReturn {
29
29
  export const useAuth = (): UseAuthReturn => {
30
30
  const dispatch = useAppDispatch();
31
31
  const router = useRouter();
32
- const searchParams = useSearchParams();
33
32
  const { user, isAuthenticated, isInitializing, isLoggingOut } = useAppSelector((state) => state.auth);
33
+ const pendingRedirectRef = useRef<string>(ROUTES.DASHBOARD);
34
34
 
35
35
  const loginMutation = useMutation({
36
36
  mutationFn: (data: ILoginRequest) => authService.login(data),
37
37
  onSuccess: (data) => {
38
38
  dispatch(setUser(data.user));
39
39
  toast.success('Signed in successfully');
40
- const from = searchParams.get('from');
41
- const redirectTo = from && from.startsWith('/') && !from.startsWith('//') ? from : ROUTES.DASHBOARD;
42
- router.push(redirectTo);
40
+ router.push(pendingRedirectRef.current);
43
41
  },
44
42
  onError: (error) => {
45
43
  if (isErrorCode(error, ERROR_CODES.ACCOUNT_NOT_ACTIVE)) {
@@ -83,7 +81,12 @@ export const useAuth = (): UseAuthReturn => {
83
81
  user,
84
82
  isAuthenticated,
85
83
  isInitializing,
86
- login: loginMutation.mutate,
84
+ login: (data: ILoginRequest, redirectTo?: string) => {
85
+ if (redirectTo) {
86
+ pendingRedirectRef.current = redirectTo;
87
+ }
88
+ loginMutation.mutate(data);
89
+ },
87
90
  register: registerMutation.mutate,
88
91
  logout,
89
92
  isLoggingIn: loginMutation.isPending,
@@ -1,92 +1,92 @@
1
- /*
2
- * Default Theme — Claude-inspired warm palette
3
- * Accent: Terracotta orange
4
- * Vibe: Earthy, warm, approachable
5
- *
6
- * This is the single theme file. Colors use HEX values.
7
- * Light mode: :root selectors. Dark mode: .dark selectors.
8
- * To customize: edit the HEX values below.
9
- */
10
-
11
- :root {
12
- --radius: 0.625rem;
13
- /* Warm cream background with terracotta orange accent */
14
- --background: #f4f3ee;
15
- --foreground: #1a170f;
16
- --card: #ffffff;
17
- --card-foreground: #1a170f;
18
- --popover: #ffffff;
19
- --popover-foreground: #1a170f;
20
- --primary: #c15f3c;
21
- --primary-foreground: #ffffff;
22
- --secondary: #ebeae3;
23
- --secondary-foreground: #302e25;
24
- --muted: #ebeae3;
25
- --muted-foreground: #b1ada1;
26
- --accent: #ebeae3;
27
- --accent-foreground: #302e25;
28
- --destructive: #e7000b;
29
- --border: #deddd4;
30
- --input: #deddd4;
31
- --ring: #c15f3c;
32
- --success: #008339;
33
- --success-foreground: #ffffff;
34
- --warning: #e99b2a;
35
- --warning-foreground: #242119;
36
- --info: #0079bf;
37
- --info-foreground: #ffffff;
38
- --chart-1: #c15f3c;
39
- --chart-2: #009689;
40
- --chart-3: #104e64;
41
- --chart-4: #ebc065;
42
- --chart-5: #b1ada1;
43
- --sidebar: #f0efe9;
44
- --sidebar-foreground: #1a170f;
45
- --sidebar-primary: #302e25;
46
- --sidebar-primary-foreground: #f4f3ee;
47
- --sidebar-accent: #ebeae3;
48
- --sidebar-accent-foreground: #302e25;
49
- --sidebar-border: #deddd4;
50
- --sidebar-ring: #c15f3c;
51
- }
52
-
53
- .dark {
54
- /* Dark mode: inverted with same warm undertone */
55
- --background: #15130d;
56
- --foreground: #e9e8e3;
57
- --card: #201e18;
58
- --card-foreground: #e9e8e3;
59
- --popover: #201e18;
60
- --popover-foreground: #e9e8e3;
61
- --primary: #d6724f;
62
- --primary-foreground: #15130d;
63
- --secondary: #2b2922;
64
- --secondary-foreground: #e9e8e3;
65
- --muted: #2b2922;
66
- --muted-foreground: #928f85;
67
- --accent: #2b2922;
68
- --accent-foreground: #e9e8e3;
69
- --destructive: #ff6467;
70
- --border: rgba(255, 255, 250, 0.12);
71
- --input: rgba(255, 255, 250, 0.15);
72
- --ring: #d6724f;
73
- --success: #009c50;
74
- --success-foreground: #ffffff;
75
- --warning: #faab3f;
76
- --warning-foreground: #242119;
77
- --info: #0099e0;
78
- --info-foreground: #ffffff;
79
- --chart-1: #d6724f;
80
- --chart-2: #00bc7d;
81
- --chart-3: #e5a658;
82
- --chart-4: #a066df;
83
- --chart-5: #e25969;
84
- --sidebar: #201e18;
85
- --sidebar-foreground: #e9e8e3;
86
- --sidebar-primary: #d6724f;
87
- --sidebar-primary-foreground: #e9e8e3;
88
- --sidebar-accent: #2b2922;
89
- --sidebar-accent-foreground: #e9e8e3;
90
- --sidebar-border: rgba(255, 255, 250, 0.12);
91
- --sidebar-ring: #d6724f;
92
- }
1
+ /*
2
+ * Default Theme — Claude-inspired warm palette
3
+ * Accent: Terracotta orange
4
+ * Vibe: Earthy, warm, approachable
5
+ *
6
+ * This is the single theme file. Colors use HEX values.
7
+ * Light mode: :root selectors. Dark mode: .dark selectors.
8
+ * To customize: edit the HEX values below.
9
+ */
10
+
11
+ :root {
12
+ --radius: 0.625rem;
13
+ /* Warm cream background with terracotta orange accent */
14
+ --background: #f4f3ee;
15
+ --foreground: #1a170f;
16
+ --card: #ffffff;
17
+ --card-foreground: #1a170f;
18
+ --popover: #ffffff;
19
+ --popover-foreground: #1a170f;
20
+ --primary: #c15f3c;
21
+ --primary-foreground: #ffffff;
22
+ --secondary: #ebeae3;
23
+ --secondary-foreground: #302e25;
24
+ --muted: #ebeae3;
25
+ --muted-foreground: #b1ada1;
26
+ --accent: #ebeae3;
27
+ --accent-foreground: #302e25;
28
+ --destructive: #e7000b;
29
+ --border: #deddd4;
30
+ --input: #deddd4;
31
+ --ring: #c15f3c;
32
+ --success: #008339;
33
+ --success-foreground: #ffffff;
34
+ --warning: #e99b2a;
35
+ --warning-foreground: #242119;
36
+ --info: #0079bf;
37
+ --info-foreground: #ffffff;
38
+ --chart-1: #c15f3c;
39
+ --chart-2: #009689;
40
+ --chart-3: #104e64;
41
+ --chart-4: #ebc065;
42
+ --chart-5: #b1ada1;
43
+ --sidebar: #f0efe9;
44
+ --sidebar-foreground: #1a170f;
45
+ --sidebar-primary: #302e25;
46
+ --sidebar-primary-foreground: #f4f3ee;
47
+ --sidebar-accent: #ebeae3;
48
+ --sidebar-accent-foreground: #302e25;
49
+ --sidebar-border: #deddd4;
50
+ --sidebar-ring: #c15f3c;
51
+ }
52
+
53
+ .dark {
54
+ /* Dark mode: inverted with same warm undertone */
55
+ --background: #15130d;
56
+ --foreground: #e9e8e3;
57
+ --card: #201e18;
58
+ --card-foreground: #e9e8e3;
59
+ --popover: #201e18;
60
+ --popover-foreground: #e9e8e3;
61
+ --primary: #d6724f;
62
+ --primary-foreground: #15130d;
63
+ --secondary: #2b2922;
64
+ --secondary-foreground: #e9e8e3;
65
+ --muted: #2b2922;
66
+ --muted-foreground: #928f85;
67
+ --accent: #2b2922;
68
+ --accent-foreground: #e9e8e3;
69
+ --destructive: #ff6467;
70
+ --border: rgba(255, 255, 250, 0.12);
71
+ --input: rgba(255, 255, 250, 0.15);
72
+ --ring: #d6724f;
73
+ --success: #009c50;
74
+ --success-foreground: #ffffff;
75
+ --warning: #faab3f;
76
+ --warning-foreground: #242119;
77
+ --info: #0099e0;
78
+ --info-foreground: #ffffff;
79
+ --chart-1: #d6724f;
80
+ --chart-2: #00bc7d;
81
+ --chart-3: #e5a658;
82
+ --chart-4: #a066df;
83
+ --chart-5: #e25969;
84
+ --sidebar: #201e18;
85
+ --sidebar-foreground: #e9e8e3;
86
+ --sidebar-primary: #d6724f;
87
+ --sidebar-primary-foreground: #e9e8e3;
88
+ --sidebar-accent: #2b2922;
89
+ --sidebar-accent-foreground: #e9e8e3;
90
+ --sidebar-border: rgba(255, 255, 250, 0.12);
91
+ --sidebar-ring: #d6724f;
92
+ }
@@ -93,10 +93,16 @@ MAX_FILE_SIZE_MB=10
93
93
  # Without this, all uploaded files are lost on every redeployment.
94
94
 
95
95
  # ===================================================================
96
- # DOCKER PORTS (auto-generated, unique per project)
96
+ # DOCKER PORTS LOCAL DEVELOPMENT ONLY
97
97
  # ===================================================================
98
+ #
99
+ # These ports are used by docker-compose to expose MySQL, Redis, and
100
+ # their admin UIs on your local machine. They are NOT needed in
101
+ # production — production connects via DATABASE_URL and REDIS_URL
102
+ # (typically over a private network), not through exposed ports.
103
+ #
104
+ # Change these if they conflict with other services on your machine.
98
105
 
99
- # Change these if they conflict with other services on your machine
100
106
  MYSQL_PORT={{MYSQL_PORT}}
101
107
  PHPMYADMIN_PORT={{PHPMYADMIN_PORT}}
102
108
  REDIS_PORT={{REDIS_PORT}}
@@ -19,6 +19,7 @@
19
19
  "prisma:seed": "prisma db seed",
20
20
  "prisma:studio": "prisma studio",
21
21
  "lint": "eslint src/",
22
+ "typecheck": "tsc --noEmit",
22
23
  "redis:flush": "tsx scripts/flush-redis.ts",
23
24
  "docker:up": "docker compose up -d",
24
25
  "docker:down": "docker compose down",
@@ -61,6 +61,7 @@ export async function buildApp() {
61
61
  await app.register(cors, {
62
62
  origin: corsOrigin,
63
63
  credentials: true,
64
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
64
65
  });
65
66
 
66
67
  // Enhanced security headers for production