create-nextjs-stack 0.1.0

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/bin/cli.js +187 -0
  4. package/package.json +48 -0
  5. package/templates/admin/.env.example +11 -0
  6. package/templates/admin/README.md +82 -0
  7. package/templates/admin/app/(auth)/login/page.tsx +84 -0
  8. package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +45 -0
  9. package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +32 -0
  10. package/templates/admin/app/(dashboard)/[resource]/page.tsx +131 -0
  11. package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +22 -0
  12. package/templates/admin/app/(dashboard)/categories/new/page.tsx +5 -0
  13. package/templates/admin/app/(dashboard)/categories/page.tsx +33 -0
  14. package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +22 -0
  15. package/templates/admin/app/(dashboard)/clients/new/page.tsx +5 -0
  16. package/templates/admin/app/(dashboard)/clients/page.tsx +33 -0
  17. package/templates/admin/app/(dashboard)/dashboard/page.tsx +45 -0
  18. package/templates/admin/app/(dashboard)/layout.tsx +13 -0
  19. package/templates/admin/app/(dashboard)/products/[id]/page.tsx +22 -0
  20. package/templates/admin/app/(dashboard)/products/new/page.tsx +5 -0
  21. package/templates/admin/app/(dashboard)/products/page.tsx +33 -0
  22. package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +22 -0
  23. package/templates/admin/app/(dashboard)/projects/new/page.tsx +5 -0
  24. package/templates/admin/app/(dashboard)/projects/page.tsx +33 -0
  25. package/templates/admin/app/(dashboard)/users/[id]/page.tsx +22 -0
  26. package/templates/admin/app/(dashboard)/users/new/page.tsx +5 -0
  27. package/templates/admin/app/(dashboard)/users/page.tsx +33 -0
  28. package/templates/admin/app/actions/resources.ts +46 -0
  29. package/templates/admin/app/actions/upload.ts +58 -0
  30. package/templates/admin/app/favicon.ico +0 -0
  31. package/templates/admin/app/globals.css +23 -0
  32. package/templates/admin/app/layout.tsx +23 -0
  33. package/templates/admin/app/page.tsx +5 -0
  34. package/templates/admin/components/admin/AdminLayoutClient.tsx +22 -0
  35. package/templates/admin/components/admin/DeleteModal.tsx +90 -0
  36. package/templates/admin/components/admin/FormLayout.tsx +113 -0
  37. package/templates/admin/components/admin/ImageUpload.tsx +137 -0
  38. package/templates/admin/components/admin/ResourceFormClient.tsx +62 -0
  39. package/templates/admin/components/admin/Sidebar.tsx +74 -0
  40. package/templates/admin/components/admin/SubmitButton.tsx +34 -0
  41. package/templates/admin/components/admin/ToastProvider.tsx +8 -0
  42. package/templates/admin/components/categories/CategoryForm.tsx +24 -0
  43. package/templates/admin/components/categories/CategoryList.tsx +113 -0
  44. package/templates/admin/components/clients/ClientForm.tsx +24 -0
  45. package/templates/admin/components/clients/ClientList.tsx +113 -0
  46. package/templates/admin/components/products/ProductForm.tsx +24 -0
  47. package/templates/admin/components/products/ProductList.tsx +117 -0
  48. package/templates/admin/components/projects/ProjectForm.tsx +24 -0
  49. package/templates/admin/components/projects/ProjectList.tsx +121 -0
  50. package/templates/admin/components/users/UserForm.tsx +39 -0
  51. package/templates/admin/components/users/UserList.tsx +101 -0
  52. package/templates/admin/config/resources.ts +123 -0
  53. package/templates/admin/eslint.config.mjs +18 -0
  54. package/templates/admin/hooks/useResource.ts +86 -0
  55. package/templates/admin/lib/services/base.service.ts +106 -0
  56. package/templates/admin/lib/services/categories.service.ts +7 -0
  57. package/templates/admin/lib/services/clients.service.ts +7 -0
  58. package/templates/admin/lib/services/index.ts +27 -0
  59. package/templates/admin/lib/services/products.service.ts +9 -0
  60. package/templates/admin/lib/services/projects.service.ts +22 -0
  61. package/templates/admin/lib/services/resource.service.ts +26 -0
  62. package/templates/admin/lib/services/users.service.ts +9 -0
  63. package/templates/admin/lib/supabase/client.ts +9 -0
  64. package/templates/admin/lib/supabase/middleware.ts +57 -0
  65. package/templates/admin/lib/supabase/server.ts +29 -0
  66. package/templates/admin/middleware.ts +15 -0
  67. package/templates/admin/next.config.ts +10 -0
  68. package/templates/admin/package-lock.json +6768 -0
  69. package/templates/admin/package.json +33 -0
  70. package/templates/admin/postcss.config.mjs +7 -0
  71. package/templates/admin/public/file.svg +1 -0
  72. package/templates/admin/public/globe.svg +1 -0
  73. package/templates/admin/public/next.svg +1 -0
  74. package/templates/admin/public/vercel.svg +1 -0
  75. package/templates/admin/public/window.svg +1 -0
  76. package/templates/admin/supabase_mock_data.sql +57 -0
  77. package/templates/admin/supabase_schema.sql +93 -0
  78. package/templates/admin/tsconfig.json +34 -0
  79. package/templates/web/.env.example +21 -0
  80. package/templates/web/README.md +129 -0
  81. package/templates/web/components.json +22 -0
  82. package/templates/web/eslint.config.mjs +25 -0
  83. package/templates/web/next.config.ts +25 -0
  84. package/templates/web/package-lock.json +6778 -0
  85. package/templates/web/package.json +45 -0
  86. package/templates/web/postcss.config.mjs +5 -0
  87. package/templates/web/src/app/api/contact/route.ts +181 -0
  88. package/templates/web/src/app/api/revalidate/route.ts +95 -0
  89. package/templates/web/src/app/error.tsx +28 -0
  90. package/templates/web/src/app/globals.css +838 -0
  91. package/templates/web/src/app/layout.tsx +126 -0
  92. package/templates/web/src/app/loading.tsx +60 -0
  93. package/templates/web/src/app/not-found.tsx +68 -0
  94. package/templates/web/src/app/page.tsx +106 -0
  95. package/templates/web/src/app/robots.ts +12 -0
  96. package/templates/web/src/app/sitemap.ts +66 -0
  97. package/templates/web/src/components/home/StatsGrid.tsx +89 -0
  98. package/templates/web/src/hooks/useIntersectionObserver.ts +39 -0
  99. package/templates/web/src/lib/providers/StoreProvider.tsx +12 -0
  100. package/templates/web/src/lib/seo/index.ts +4 -0
  101. package/templates/web/src/lib/seo/metadata.ts +103 -0
  102. package/templates/web/src/lib/seo/seo.config.ts +161 -0
  103. package/templates/web/src/lib/seo/seo.types.ts +76 -0
  104. package/templates/web/src/lib/services/categories.service.ts +38 -0
  105. package/templates/web/src/lib/services/categoryService.ts +251 -0
  106. package/templates/web/src/lib/services/clientService.ts +132 -0
  107. package/templates/web/src/lib/services/clients.service.ts +20 -0
  108. package/templates/web/src/lib/services/productService.ts +261 -0
  109. package/templates/web/src/lib/services/products.service.ts +38 -0
  110. package/templates/web/src/lib/services/projectService.ts +234 -0
  111. package/templates/web/src/lib/services/projects.service.ts +38 -0
  112. package/templates/web/src/lib/services/users.service.ts +20 -0
  113. package/templates/web/src/lib/supabase/client.ts +42 -0
  114. package/templates/web/src/lib/supabase/constants.ts +25 -0
  115. package/templates/web/src/lib/supabase/server.ts +29 -0
  116. package/templates/web/src/lib/supabase/types.ts +112 -0
  117. package/templates/web/src/lib/utils/cache.ts +98 -0
  118. package/templates/web/src/lib/utils/rate-limiter.ts +102 -0
  119. package/templates/web/src/store/actions/index.ts +2 -0
  120. package/templates/web/src/store/index.ts +13 -0
  121. package/templates/web/src/store/reducers/index.ts +13 -0
  122. package/templates/web/src/store/types/index.ts +2 -0
  123. package/templates/web/tsconfig.json +41 -0
@@ -0,0 +1,38 @@
1
+ import { getServerClient } from "@/lib/supabase/server";
2
+
3
+ export class ProjectService {
4
+ private static table = "projects";
5
+
6
+ static async getAll() {
7
+ const supabase = await getServerClient();
8
+ const { data, error } = await supabase
9
+ .from(this.table)
10
+ .select("*, clients(name, logo_url)")
11
+ .eq("published", true)
12
+ .order("created_at", { ascending: false });
13
+
14
+ if (error) {
15
+ console.error(`Error fetching ${this.table}:`, error);
16
+ return [];
17
+ }
18
+
19
+ return data;
20
+ }
21
+
22
+ static async getBySlug(slug: string) {
23
+ const supabase = await getServerClient();
24
+ const { data, error } = await supabase
25
+ .from(this.table)
26
+ .select("*, clients(name, logo_url)")
27
+ .eq("slug", slug)
28
+ .eq("published", true)
29
+ .single();
30
+
31
+ if (error) {
32
+ console.error(`Error fetching ${this.table} ${slug}:`, error);
33
+ return null;
34
+ }
35
+
36
+ return data;
37
+ }
38
+ }
@@ -0,0 +1,20 @@
1
+ import { getServerClient } from "@/lib/supabase/server";
2
+
3
+ export class UserService {
4
+ private static table = "users";
5
+
6
+ static async getAll() {
7
+ const supabase = await getServerClient();
8
+ const { data, error } = await supabase
9
+ .from(this.table)
10
+ .select("*")
11
+ .order("created_at", { ascending: false });
12
+
13
+ if (error) {
14
+ console.error(`Error fetching ${this.table}:`, error);
15
+ return [];
16
+ }
17
+
18
+ return data;
19
+ }
20
+ }
@@ -0,0 +1,42 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+ import { createBrowserClient } from '@supabase/ssr';
3
+ import type { Database } from './types';
4
+
5
+ /**
6
+ * Get Supabase client for server-side operations
7
+ * Uses SERVICE_ROLE_KEY for full database access
8
+ */
9
+ export function getServerClient() {
10
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
11
+ const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
12
+
13
+ if (!supabaseUrl || !supabaseKey) {
14
+ throw new Error(
15
+ 'Missing Supabase credentials. Please check NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in .env'
16
+ );
17
+ }
18
+
19
+ return createClient<Database>(supabaseUrl, supabaseKey, {
20
+ auth: {
21
+ persistSession: false,
22
+ autoRefreshToken: false,
23
+ },
24
+ });
25
+ }
26
+
27
+ /**
28
+ * Get Supabase client for browser-side operations
29
+ * Uses createBrowserClient from @supabase/ssr to properly handle Cookies
30
+ */
31
+ export function getBrowserClient() {
32
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
33
+ const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
34
+
35
+ if (!supabaseUrl || !supabaseKey) {
36
+ throw new Error(
37
+ 'Missing Supabase credentials. Please check NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY in .env'
38
+ );
39
+ }
40
+
41
+ return createBrowserClient<Database>(supabaseUrl, supabaseKey);
42
+ }
@@ -0,0 +1,25 @@
1
+ export const TABLE_NAMES = {
2
+ CATEGORIES: 'categories',
3
+ PRODUCTS: 'products',
4
+ CLIENTS: 'clients',
5
+ PROJECTS: 'projects',
6
+ } as const;
7
+
8
+ export const CACHE_TIMES = {
9
+ CATEGORIES: 3600, // 1 hour
10
+ PRODUCTS: 1800, // 30 minutes
11
+ CLIENTS: 3600, // 1 hour
12
+ PROJECTS: 1800, // 30 minutes
13
+ } as const;
14
+
15
+ export const CACHE_TAGS = {
16
+ CATEGORIES: 'categories',
17
+ PRODUCTS: 'products',
18
+ CLIENTS: 'clients',
19
+ PROJECTS: 'projects',
20
+ } as const;
21
+
22
+ export const RATE_LIMITS = {
23
+ REQUESTS_PER_MINUTE: 60,
24
+ REQUESTS_PER_HOUR: 1000,
25
+ } as const;
@@ -0,0 +1,29 @@
1
+ import { createServerClient } from "@supabase/ssr";
2
+ import { cookies } from "next/headers";
3
+
4
+ export async function getServerClient() {
5
+ const cookieStore = await cookies();
6
+
7
+ return createServerClient(
8
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
9
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10
+ {
11
+ cookies: {
12
+ getAll() {
13
+ return cookieStore.getAll();
14
+ },
15
+ setAll(cookiesToSet) {
16
+ try {
17
+ cookiesToSet.forEach(({ name, value, options }) =>
18
+ cookieStore.set(name, value, options)
19
+ );
20
+ } catch {
21
+ // The `setAll` method was called from a Server Component.
22
+ // This can be ignored if you have middleware refreshing
23
+ // user sessions.
24
+ }
25
+ },
26
+ },
27
+ }
28
+ );
29
+ }
@@ -0,0 +1,112 @@
1
+ // Database Row Types
2
+ export interface Category {
3
+ id: string;
4
+ title: string;
5
+ slug: string;
6
+ description: string | null;
7
+ parent_id: string | null;
8
+ image_url: string | null;
9
+ published: boolean;
10
+ featured: boolean;
11
+ meta_description: string | null;
12
+ created_at: string;
13
+ updated_at: string;
14
+ }
15
+
16
+ export interface Product {
17
+ id: string;
18
+ title: string;
19
+ slug: string;
20
+ short_description: string | null;
21
+ description: string | null;
22
+ category_id: string;
23
+ featured_image_url: string | null;
24
+ gallery_images: string[];
25
+ width: number | null;
26
+ height: number | null;
27
+ thickness: number | null;
28
+ material: string | null;
29
+ surface_finish: string | null;
30
+ fire_rating: string | null;
31
+ weight_per_sqm: number | null;
32
+ available_colors: string | null;
33
+ installation_method: string | null;
34
+ certifications: string | null;
35
+ additional_features: string | null;
36
+ meta_description: string | null;
37
+ published: boolean;
38
+ featured: boolean;
39
+ created_at: string;
40
+ updated_at: string;
41
+ }
42
+
43
+ export interface Client {
44
+ id: string;
45
+ name: string;
46
+ website: string | null;
47
+ logo_url: string | null;
48
+ sort_order: number;
49
+ published: boolean;
50
+ created_at: string;
51
+ updated_at: string;
52
+ }
53
+
54
+ export interface Project {
55
+ id: string;
56
+ title: string;
57
+ slug: string;
58
+ short_description: string | null;
59
+ description: string | null;
60
+ location: string | null;
61
+ year: number;
62
+ client_name: string | null;
63
+ featured_image_url: string | null;
64
+ gallery_images: string[];
65
+ product_ids: string[];
66
+ meta_description: string | null;
67
+ published: boolean;
68
+ featured: boolean;
69
+ created_at: string;
70
+ updated_at: string;
71
+ }
72
+
73
+ // Extended Types with Relations
74
+ export interface CategoryWithChildren extends Category {
75
+ children?: CategoryWithChildren[];
76
+ }
77
+
78
+ export interface ProductWithCategory extends Product {
79
+ category: Category | null;
80
+ }
81
+
82
+ export interface ProjectWithProducts extends Project {
83
+ products: Product[];
84
+ }
85
+
86
+ // Database Type (for Supabase client)
87
+ export interface Database {
88
+ public: {
89
+ Tables: {
90
+ categories: {
91
+ Row: Category;
92
+ Insert: Omit<Category, 'id' | 'created_at' | 'updated_at'>;
93
+ Update: Partial<Omit<Category, 'id' | 'created_at' | 'updated_at'>>;
94
+ };
95
+ products: {
96
+ Row: Product;
97
+ Insert: Omit<Product, 'id' | 'created_at' | 'updated_at'>;
98
+ Update: Partial<Omit<Product, 'id' | 'created_at' | 'updated_at'>>;
99
+ };
100
+ clients: {
101
+ Row: Client;
102
+ Insert: Omit<Client, 'id' | 'created_at' | 'updated_at'>;
103
+ Update: Partial<Omit<Client, 'id' | 'created_at' | 'updated_at'>>;
104
+ };
105
+ projects: {
106
+ Row: Project;
107
+ Insert: Omit<Project, 'id' | 'created_at' | 'updated_at'>;
108
+ Update: Partial<Omit<Project, 'id' | 'created_at' | 'updated_at'>>;
109
+ };
110
+ };
111
+ };
112
+ }
@@ -0,0 +1,98 @@
1
+ interface CacheEntry<T> {
2
+ data: T;
3
+ timestamp: number;
4
+ ttl: number;
5
+ }
6
+
7
+ /**
8
+ * In-memory cache with TTL (Time To Live) support
9
+ * Provides fast access to frequently requested data
10
+ */
11
+ class MemoryCache {
12
+ private cache: Map<string, CacheEntry<unknown>> = new Map();
13
+
14
+ /**
15
+ * Store data in cache with TTL
16
+ * @param key - Cache key
17
+ * @param data - Data to cache
18
+ * @param ttl - Time to live in seconds
19
+ */
20
+ set<T>(key: string, data: T, ttl: number): void {
21
+ this.cache.set(key, {
22
+ data,
23
+ timestamp: Date.now(),
24
+ ttl,
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Retrieve data from cache
30
+ * @param key - Cache key
31
+ * @returns Cached data or null if expired/not found
32
+ */
33
+ get<T>(key: string): T | null {
34
+ const entry = this.cache.get(key) as CacheEntry<T> | undefined;
35
+
36
+ if (!entry) {
37
+ return null;
38
+ }
39
+
40
+ // Check if entry has expired
41
+ const age = Date.now() - entry.timestamp;
42
+ if (age > entry.ttl * 1000) {
43
+ this.cache.delete(key);
44
+ return null;
45
+ }
46
+
47
+ return entry.data;
48
+ }
49
+
50
+ /**
51
+ * Check if key exists and is not expired
52
+ * @param key - Cache key
53
+ */
54
+ has(key: string): boolean {
55
+ return this.get(key) !== null;
56
+ }
57
+
58
+ /**
59
+ * Invalidate cache entries matching a pattern
60
+ * @param pattern - String pattern to match against keys
61
+ */
62
+ invalidate(pattern: string): void {
63
+ const keys = Array.from(this.cache.keys());
64
+ keys.forEach((key) => {
65
+ if (key.includes(pattern)) {
66
+ this.cache.delete(key);
67
+ }
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Invalidate specific cache key
73
+ * @param key - Cache key to invalidate
74
+ */
75
+ invalidateKey(key: string): void {
76
+ this.cache.delete(key);
77
+ }
78
+
79
+ /**
80
+ * Clear all cache entries
81
+ */
82
+ clear(): void {
83
+ this.cache.clear();
84
+ }
85
+
86
+ /**
87
+ * Get cache statistics
88
+ */
89
+ getStats() {
90
+ return {
91
+ size: this.cache.size,
92
+ keys: Array.from(this.cache.keys()),
93
+ };
94
+ }
95
+ }
96
+
97
+ // Export singleton instance
98
+ export const memoryCache = new MemoryCache();
@@ -0,0 +1,102 @@
1
+ import { RATE_LIMITS } from '@/lib/supabase/constants';
2
+
3
+ interface RateLimitEntry {
4
+ tokens: number;
5
+ lastRefill: number;
6
+ }
7
+
8
+ /**
9
+ * Token Bucket Rate Limiter
10
+ * Implements the token bucket algorithm for rate limiting
11
+ */
12
+ class RateLimiter {
13
+ private buckets: Map<string, RateLimitEntry> = new Map();
14
+ private maxTokens: number;
15
+ private refillRate: number; // tokens per second
16
+
17
+ /**
18
+ * @param maxTokens - Maximum number of tokens in the bucket
19
+ * @param refillRate - Number of tokens added per second
20
+ */
21
+ constructor(maxTokens: number, refillRate: number) {
22
+ this.maxTokens = maxTokens;
23
+ this.refillRate = refillRate;
24
+ }
25
+
26
+ /**
27
+ * Check if request is allowed under rate limit
28
+ * @param key - Unique identifier for the rate limit bucket (e.g., user ID, IP, endpoint)
29
+ * @returns true if request is allowed, false if rate limited
30
+ */
31
+ async checkLimit(key: string): Promise<boolean> {
32
+ const now = Date.now();
33
+ let bucket = this.buckets.get(key);
34
+
35
+ // Initialize bucket if it doesn't exist
36
+ if (!bucket) {
37
+ bucket = {
38
+ tokens: this.maxTokens - 1,
39
+ lastRefill: now,
40
+ };
41
+ this.buckets.set(key, bucket);
42
+ return true;
43
+ }
44
+
45
+ // Refill tokens based on time passed
46
+ const timePassed = (now - bucket.lastRefill) / 1000; // in seconds
47
+ const tokensToAdd = timePassed * this.refillRate;
48
+ bucket.tokens = Math.min(this.maxTokens, bucket.tokens + tokensToAdd);
49
+ bucket.lastRefill = now;
50
+
51
+ // Check if we have tokens available
52
+ if (bucket.tokens >= 1) {
53
+ bucket.tokens -= 1;
54
+ return true;
55
+ }
56
+
57
+ // Rate limit exceeded
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * Get remaining tokens for a key
63
+ * @param key - Rate limit bucket key
64
+ */
65
+ getRemainingTokens(key: string): number {
66
+ const bucket = this.buckets.get(key);
67
+ if (!bucket) {
68
+ return this.maxTokens;
69
+ }
70
+
71
+ const now = Date.now();
72
+ const timePassed = (now - bucket.lastRefill) / 1000;
73
+ const tokensToAdd = timePassed * this.refillRate;
74
+ return Math.min(this.maxTokens, bucket.tokens + tokensToAdd);
75
+ }
76
+
77
+ /**
78
+ * Reset rate limit for a specific key
79
+ * @param key - Rate limit bucket key
80
+ */
81
+ reset(key: string): void {
82
+ this.buckets.delete(key);
83
+ }
84
+
85
+ /**
86
+ * Clear all rate limit buckets
87
+ */
88
+ clearAll(): void {
89
+ this.buckets.clear();
90
+ }
91
+ }
92
+
93
+ // Global rate limiters
94
+ export const perMinuteLimiter = new RateLimiter(
95
+ RATE_LIMITS.REQUESTS_PER_MINUTE,
96
+ RATE_LIMITS.REQUESTS_PER_MINUTE / 60 // tokens per second
97
+ );
98
+
99
+ export const perHourLimiter = new RateLimiter(
100
+ RATE_LIMITS.REQUESTS_PER_HOUR,
101
+ RATE_LIMITS.REQUESTS_PER_HOUR / 3600 // tokens per second
102
+ );
@@ -0,0 +1,2 @@
1
+ // Export actions here
2
+ // export * from './exampleActions';
@@ -0,0 +1,13 @@
1
+ import { createStore, applyMiddleware } from 'redux';
2
+ import { thunk } from 'redux-thunk';
3
+ import logger from 'redux-logger';
4
+ import rootReducer from './reducers';
5
+
6
+ const middleware = [thunk, logger];
7
+
8
+ const store = createStore(
9
+ rootReducer,
10
+ applyMiddleware(...middleware)
11
+ );
12
+
13
+ export default store;
@@ -0,0 +1,13 @@
1
+ import { combineReducers } from 'redux';
2
+ // Import your reducers here
3
+ // import exampleReducer from './exampleReducer';
4
+
5
+ const rootReducer = combineReducers({
6
+ // example: exampleReducer,
7
+ // Add a placeholder to prevent error if no reducers yet
8
+ _placeholder: (state = {}) => state
9
+ });
10
+
11
+ export type RootState = ReturnType<typeof rootReducer>;
12
+
13
+ export default rootReducer;
@@ -0,0 +1,2 @@
1
+ // Define global types for specific actions if needed
2
+ // export const EXAMPLE_ACTION = 'EXAMPLE_ACTION';
@@ -0,0 +1,41 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "bundler",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "jsx": "react-jsx",
19
+ "incremental": true,
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ],
25
+ "paths": {
26
+ "@/*": [
27
+ "./src/*"
28
+ ]
29
+ }
30
+ },
31
+ "include": [
32
+ "next-env.d.ts",
33
+ "**/*.ts",
34
+ "**/*.tsx",
35
+ ".next/types/**/*.ts",
36
+ ".next/dev/types/**/*.ts"
37
+ ],
38
+ "exclude": [
39
+ "node_modules"
40
+ ]
41
+ }