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,123 @@
1
+ export interface ResourceConfig {
2
+ name: string;
3
+ singular: string;
4
+ plural: string;
5
+ icon: string;
6
+ path: string;
7
+ table: string;
8
+ fields: ResourceField[];
9
+ }
10
+
11
+ export interface ResourceField {
12
+ name: string;
13
+ type:
14
+ | "text"
15
+ | "textarea"
16
+ | "number"
17
+ | "boolean"
18
+ | "select"
19
+ | "image"
20
+ | "date";
21
+ label: string;
22
+ required?: boolean;
23
+ options?: { value: string; label: string }[];
24
+ relation?: {
25
+ table: string;
26
+ display: string;
27
+ };
28
+ }
29
+
30
+ export const resources: ResourceConfig[] = [
31
+ {
32
+ name: "categories",
33
+ singular: "Category",
34
+ plural: "Categories",
35
+ icon: "Layers",
36
+ path: "/categories",
37
+ table: "categories",
38
+ fields: [
39
+ { name: "title", type: "text", label: "Title", required: true },
40
+ { name: "slug", type: "text", label: "Slug", required: true },
41
+ { name: "description", type: "textarea", label: "Description" },
42
+ { name: "featured", type: "boolean", label: "Featured" },
43
+ { name: "published", type: "boolean", label: "Published" },
44
+ ],
45
+ },
46
+ {
47
+ name: "clients",
48
+ singular: "Client",
49
+ plural: "Clients",
50
+ icon: "Users",
51
+ path: "/clients",
52
+ table: "clients",
53
+ fields: [
54
+ { name: "name", type: "text", label: "Name", required: true },
55
+ { name: "logo_url", type: "image", label: "Logo" },
56
+ { name: "website", type: "text", label: "Website" },
57
+ ],
58
+ },
59
+ {
60
+ name: "products",
61
+ singular: "Product",
62
+ plural: "Products",
63
+ icon: "Package",
64
+ path: "/products",
65
+ table: "products",
66
+ fields: [
67
+ { name: "title", type: "text", label: "Title", required: true },
68
+ { name: "slug", type: "text", label: "Slug", required: true },
69
+ { name: "description", type: "textarea", label: "Description" },
70
+ { name: "featured_image_url", type: "image", label: "Featured Image" },
71
+ {
72
+ name: "category_id",
73
+ type: "select",
74
+ label: "Category",
75
+ relation: { table: "categories", display: "title" },
76
+ },
77
+ { name: "featured", type: "boolean", label: "Featured" },
78
+ { name: "published", type: "boolean", label: "Published" },
79
+ ],
80
+ },
81
+ {
82
+ name: "projects",
83
+ singular: "Project",
84
+ plural: "Projects",
85
+ icon: "Briefcase",
86
+ path: "/projects",
87
+ table: "projects",
88
+ fields: [
89
+ { name: "title", type: "text", label: "Title", required: true },
90
+ { name: "slug", type: "text", label: "Slug", required: true },
91
+ { name: "description", type: "textarea", label: "Description" },
92
+ {
93
+ name: "client_id",
94
+ type: "select",
95
+ label: "Client",
96
+ relation: { table: "clients", display: "name" },
97
+ },
98
+ { name: "featured_image_url", type: "image", label: "Featured Image" },
99
+ { name: "published", type: "boolean", label: "Published" },
100
+ ],
101
+ },
102
+ {
103
+ name: "users",
104
+ singular: "User",
105
+ plural: "Users",
106
+ icon: "Settings",
107
+ path: "/users",
108
+ table: "users",
109
+ fields: [
110
+ { name: "email", type: "text", label: "Email", required: true },
111
+ { name: "full_name", type: "text", label: "Full Name" },
112
+ {
113
+ name: "role",
114
+ type: "select",
115
+ label: "Role",
116
+ options: [
117
+ { value: "admin", label: "Admin" },
118
+ { value: "editor", label: "Editor" },
119
+ ],
120
+ },
121
+ ],
122
+ },
123
+ ];
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
@@ -0,0 +1,86 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { toast } from "react-toastify";
6
+ import { createResource, updateResource, deleteResource } from "@/app/actions/resources";
7
+ import { ResourceConfig } from "@/config/resources";
8
+
9
+ export function useResource(config: ResourceConfig) {
10
+ const router = useRouter();
11
+ const [isSubmitting, setIsSubmitting] = useState(false);
12
+ const [isDeleting, setIsDeleting] = useState(false);
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const create = async (data: any) => {
16
+ setIsSubmitting(true);
17
+ try {
18
+ const result = await createResource(config.table, data);
19
+ if (result.error) {
20
+ toast.error(result.error);
21
+ return false;
22
+ }
23
+ toast.success(`${config.singular} created successfully`);
24
+ router.push(`/${config.name}`);
25
+ router.refresh();
26
+ return true;
27
+ } catch (error) {
28
+ console.error(error);
29
+ toast.error("Something went wrong");
30
+ return false;
31
+ } finally {
32
+ setIsSubmitting(false);
33
+ }
34
+ };
35
+
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ const update = async (id: string, data: any) => {
38
+ setIsSubmitting(true);
39
+ try {
40
+ const result = await updateResource(config.table, id, data);
41
+ if (result.error) {
42
+ toast.error(result.error);
43
+ return false;
44
+ }
45
+ toast.success(`${config.singular} updated successfully`);
46
+ router.push(`/${config.name}`);
47
+ router.refresh();
48
+ return true;
49
+ } catch (error) {
50
+ console.error(error);
51
+ toast.error("Something went wrong");
52
+ return false;
53
+ } finally {
54
+ setIsSubmitting(false);
55
+ }
56
+ };
57
+
58
+ const remove = async (id: string) => {
59
+ setIsDeleting(true);
60
+ try {
61
+ const result = await deleteResource(config.table, id);
62
+ if (result.error) {
63
+ toast.error(result.error);
64
+ return false;
65
+ }
66
+ toast.success("Record deleted");
67
+ router.push(`/${config.name}`);
68
+ router.refresh();
69
+ return true;
70
+ } catch (error) {
71
+ console.error(error);
72
+ toast.error("Failed to delete");
73
+ return false;
74
+ } finally {
75
+ setIsDeleting(false);
76
+ }
77
+ };
78
+
79
+ return {
80
+ create,
81
+ update,
82
+ remove,
83
+ isSubmitting,
84
+ isDeleting
85
+ };
86
+ }
@@ -0,0 +1,106 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { getServerClient } from "@/lib/supabase/server";
3
+
4
+ export class BaseService<T = any> {
5
+ protected table: string;
6
+
7
+ constructor(table: string) {
8
+ this.table = table;
9
+ }
10
+
11
+ protected async getClient(): Promise<SupabaseClient> {
12
+ return getServerClient();
13
+ }
14
+
15
+ async getAll() {
16
+ const supabase = await this.getClient();
17
+ const { data, error } = await supabase
18
+ .from(this.table)
19
+ .select("*")
20
+ .order("created_at", { ascending: false });
21
+
22
+ if (error) {
23
+ console.error(`Error fetching ${this.table}:`, error);
24
+ throw error;
25
+ }
26
+
27
+ return data as T[];
28
+ }
29
+
30
+ async getById(id: string) {
31
+ const supabase = await this.getClient();
32
+ const { data, error } = await supabase
33
+ .from(this.table)
34
+ .select("*")
35
+ .eq("id", id)
36
+ .single();
37
+
38
+ if (error) {
39
+ console.error(`Error fetching ${this.table} ${id}:`, error);
40
+ throw error;
41
+ }
42
+
43
+ return data as T;
44
+ }
45
+
46
+ async create(payload: Partial<T>) {
47
+ const supabase = await this.getClient();
48
+ const { data, error } = await supabase
49
+ .from(this.table)
50
+ .insert(payload)
51
+ .select()
52
+ .single();
53
+
54
+ if (error) {
55
+ console.error(`Error creating ${this.table}:`, error);
56
+ throw error;
57
+ }
58
+
59
+ return data as T;
60
+ }
61
+
62
+ async update(id: string, payload: Partial<T>) {
63
+ const supabase = await this.getClient();
64
+ const { data, error } = await supabase
65
+ .from(this.table)
66
+ .update(payload)
67
+ .eq("id", id)
68
+ .select()
69
+ .single();
70
+
71
+ if (error) {
72
+ console.error(`Error updating ${this.table} ${id}:`, error);
73
+ throw error;
74
+ }
75
+
76
+ return data as T;
77
+ }
78
+
79
+ async delete(id: string) {
80
+ const supabase = await this.getClient();
81
+ const { error } = await supabase.from(this.table).delete().eq("id", id);
82
+
83
+ if (error) {
84
+ console.error(`Error deleting ${this.table} ${id}:`, error);
85
+ throw error;
86
+ }
87
+
88
+ return true;
89
+ }
90
+
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ async getOptions(displayField: string): Promise<any[]> {
93
+ const supabase = await this.getClient();
94
+ const { data, error } = await supabase
95
+ .from(this.table)
96
+ .select(`id, ${displayField}`)
97
+ .order(displayField);
98
+
99
+ if (error) {
100
+ console.error(`Error fetching options for ${this.table}:`, error);
101
+ return [];
102
+ }
103
+
104
+ return data || [];
105
+ }
106
+ }
@@ -0,0 +1,7 @@
1
+ import { BaseService } from "./base.service";
2
+
3
+ export class CategoryService extends BaseService {
4
+ constructor() {
5
+ super("categories");
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ import { BaseService } from "./base.service";
2
+
3
+ export class ClientService extends BaseService {
4
+ constructor() {
5
+ super("clients");
6
+ }
7
+ }
@@ -0,0 +1,27 @@
1
+ import { BaseService } from "./base.service";
2
+ import { ProductService } from "./products.service";
3
+ import { UserService } from "./users.service";
4
+ import { ClientService } from "./clients.service";
5
+ import { ProjectService } from "./projects.service";
6
+ import { CategoryService } from "./categories.service";
7
+
8
+ // Registry mapping resource names to Service instances
9
+ const services: Record<string, BaseService> = {
10
+ products: new ProductService(),
11
+ users: new UserService(),
12
+ clients: new ClientService(),
13
+ projects: new ProjectService(),
14
+ categories: new CategoryService(),
15
+ };
16
+
17
+ export const getService = (resourceName: string): BaseService => {
18
+ const service = services[resourceName];
19
+ if (!service) {
20
+ // Fallback or throw error. For template flexibility, we can return a generic BaseService
21
+ // or strictly throw if the resource isn't configured.
22
+ // For now, let's return a generic BaseService to support new tables added to config/resources.ts
23
+ // without needing a dedicated file immediately.
24
+ return new BaseService(resourceName);
25
+ }
26
+ return service;
27
+ };
@@ -0,0 +1,9 @@
1
+ import { BaseService } from "./base.service";
2
+
3
+ export class ProductService extends BaseService {
4
+ constructor() {
5
+ super("products");
6
+ }
7
+
8
+ // Add custom methods here, e.g. getBySlug(slug: string)
9
+ }
@@ -0,0 +1,22 @@
1
+ import { BaseService } from "./base.service";
2
+
3
+ export class ProjectService extends BaseService {
4
+ constructor() {
5
+ super("projects");
6
+ }
7
+
8
+ async getAll() {
9
+ const supabase = await this.getClient();
10
+ const { data, error } = await supabase
11
+ .from(this.table)
12
+ .select("*, clients(name)")
13
+ .order("created_at", { ascending: false });
14
+
15
+ if (error) {
16
+ console.error(`Error fetching ${this.table}:`, error);
17
+ throw error;
18
+ }
19
+
20
+ return data;
21
+ }
22
+ }
@@ -0,0 +1,26 @@
1
+ import { BaseService } from './base.service';
2
+ import { getServerClient } from "@/lib/supabase/server";
3
+
4
+ export class ResourceService extends BaseService {
5
+ constructor(table: string) {
6
+ super(table);
7
+ }
8
+
9
+ // Fetch options for relation fields (e.g. categories for products)
10
+ async getRelationOptions(table: string, displayField: string) {
11
+ const supabase = await getServerClient();
12
+ const { data, error } = await supabase
13
+ .from(table)
14
+ .select(`id, ${displayField}`)
15
+ .order(displayField);
16
+
17
+ if (error) throw error;
18
+ return data;
19
+ }
20
+ }
21
+
22
+ export const resourceService = new ResourceService(''); // Placeholder, instantiated per use really, strictly for static methods if needed or we change design.
23
+
24
+ // Improved design: We don't export a single instance but rather helper functions or the class itself.
25
+ // But for FormLayout client-side fetching, we need a server action or API route.
26
+ // Let's create a server action for this instead.
@@ -0,0 +1,9 @@
1
+ import { BaseService } from "./base.service";
2
+
3
+ export class UserService extends BaseService {
4
+ constructor() {
5
+ super("users");
6
+ }
7
+
8
+ // Future: override create() to handle Auth user creation if using Admin API
9
+ }
@@ -0,0 +1,9 @@
1
+ import { createBrowserClient } from "@supabase/ssr";
2
+
3
+ export function getBrowserClient() {
4
+ return createBrowserClient(
5
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
6
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
7
+ );
8
+ }
9
+
@@ -0,0 +1,57 @@
1
+ import { createServerClient } from '@supabase/ssr'
2
+ import { NextResponse, type NextRequest } from 'next/server'
3
+
4
+ export async function updateSession(request: NextRequest) {
5
+ let supabaseResponse = NextResponse.next({
6
+ request,
7
+ })
8
+
9
+ const supabase = createServerClient(
10
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
11
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
12
+ {
13
+ cookies: {
14
+ getAll() {
15
+ return request.cookies.getAll()
16
+ },
17
+ setAll(cookiesToSet) {
18
+ cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
19
+ supabaseResponse = NextResponse.next({
20
+ request,
21
+ })
22
+ cookiesToSet.forEach(({ name, value, options }) =>
23
+ supabaseResponse.cookies.set(name, value, options)
24
+ )
25
+ },
26
+ },
27
+ }
28
+ )
29
+
30
+ // IMPORTANT: Avoid writing any logic between createServerClient and
31
+ // supabase.auth.getUser(). A simple mistake could make it very hard to debug
32
+ // issues with users being randomly logged out.
33
+
34
+ const {
35
+ data: { user },
36
+ } = await supabase.auth.getUser()
37
+
38
+ if (
39
+ !user &&
40
+ !request.nextUrl.pathname.startsWith('/login') &&
41
+ !request.nextUrl.pathname.startsWith('/auth')
42
+ ) {
43
+ // no user, potentially respond by redirecting the user to the login page
44
+ const url = request.nextUrl.clone()
45
+ url.pathname = '/login'
46
+ return NextResponse.redirect(url)
47
+ }
48
+
49
+ // Redirect to dashboard if authenticated and on login page
50
+ if (user && request.nextUrl.pathname === '/login') {
51
+ const url = request.nextUrl.clone()
52
+ url.pathname = '/dashboard'
53
+ return NextResponse.redirect(url)
54
+ }
55
+
56
+ return supabaseResponse
57
+ }
@@ -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,15 @@
1
+ import { type NextRequest } from "next/server";
2
+ import { updateSession } from "@/lib/supabase/middleware";
3
+
4
+ export async function middleware(request: NextRequest) {
5
+ console.log("Middleware running");
6
+ console.log("URL:", process.env.NEXT_PUBLIC_SUPABASE_URL ? "Defined" : "Undefined");
7
+ console.log("Anon Key:", process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ? "Defined" : "Undefined");
8
+ return await updateSession(request);
9
+ }
10
+
11
+ export const config = {
12
+ matcher: [
13
+ "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
14
+ ],
15
+ };
@@ -0,0 +1,10 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ images: {
6
+ domains: ["res.cloudinary.com","picsum.photos"],
7
+ },
8
+ };
9
+
10
+ export default nextConfig;