luxlabs 1.0.1 → 1.0.3
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/templates/interface-boilerplate/app/api/auth/[...all]/route.ts +52 -0
- package/templates/interface-boilerplate/app/auth/callback/page.tsx +22 -31
- package/templates/interface-boilerplate/app/dashboard/page.tsx +41 -214
- package/templates/interface-boilerplate/app/favicon.ico +0 -0
- package/templates/interface-boilerplate/app/globals.css +18 -113
- package/templates/interface-boilerplate/app/layout.tsx +21 -33
- package/templates/interface-boilerplate/app/page.tsx +37 -116
- package/templates/interface-boilerplate/app/settings/page.tsx +71 -0
- package/templates/interface-boilerplate/app/sign-in/page.tsx +19 -0
- package/templates/interface-boilerplate/app/sign-up/page.tsx +19 -0
- package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +144 -0
- package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +236 -0
- package/templates/interface-boilerplate/components/ui/badge.tsx +18 -14
- package/templates/interface-boilerplate/components/ui/button.tsx +29 -24
- package/templates/interface-boilerplate/components/ui/card.tsx +76 -57
- package/templates/interface-boilerplate/components/ui/input.tsx +8 -9
- package/templates/interface-boilerplate/eslint.config.mjs +18 -0
- package/templates/interface-boilerplate/lib/auth-client.ts +18 -0
- package/templates/interface-boilerplate/lib/auth.config.ts +111 -0
- package/templates/interface-boilerplate/lib/lux.ts +8 -0
- package/templates/interface-boilerplate/lib/utils.ts +3 -3
- package/templates/interface-boilerplate/middleware.ts +60 -0
- package/templates/interface-boilerplate/next.config.ts +7 -0
- package/templates/interface-boilerplate/package-lock.json +6855 -0
- package/templates/interface-boilerplate/package.json +18 -37
- package/templates/interface-boilerplate/postcss.config.mjs +7 -0
- package/templates/interface-boilerplate/tsconfig.json +18 -4
- package/templates/interface-boilerplate/.env.example +0 -2
- package/templates/interface-boilerplate/.eslintrc.json +0 -4
- package/templates/interface-boilerplate/app/login/page.tsx +0 -178
- package/templates/interface-boilerplate/app/signup/page.tsx +0 -199
- package/templates/interface-boilerplate/components/AnalyticsProvider.tsx +0 -142
- package/templates/interface-boilerplate/components/AuthGuard.tsx +0 -76
- package/templates/interface-boilerplate/components/ErrorBoundary.tsx +0 -106
- package/templates/interface-boilerplate/components/theme-provider.tsx +0 -9
- package/templates/interface-boilerplate/components/theme-toggle.tsx +0 -39
- package/templates/interface-boilerplate/components/ui/avatar.tsx +0 -46
- package/templates/interface-boilerplate/components/ui/checkbox.tsx +0 -27
- package/templates/interface-boilerplate/components/ui/dialog.tsx +0 -100
- package/templates/interface-boilerplate/components/ui/dropdown-menu.tsx +0 -173
- package/templates/interface-boilerplate/components/ui/index.ts +0 -53
- package/templates/interface-boilerplate/components/ui/label.tsx +0 -20
- package/templates/interface-boilerplate/components/ui/progress.tsx +0 -24
- package/templates/interface-boilerplate/components/ui/select.tsx +0 -149
- package/templates/interface-boilerplate/components/ui/separator.tsx +0 -25
- package/templates/interface-boilerplate/components/ui/skeleton.tsx +0 -12
- package/templates/interface-boilerplate/components/ui/switch.tsx +0 -28
- package/templates/interface-boilerplate/components/ui/tabs.tsx +0 -54
- package/templates/interface-boilerplate/components/ui/textarea.tsx +0 -22
- package/templates/interface-boilerplate/components/ui/tooltip.tsx +0 -29
- package/templates/interface-boilerplate/lib/analytics.ts +0 -182
- package/templates/interface-boilerplate/lib/auth-context.tsx +0 -83
- package/templates/interface-boilerplate/lib/auth.ts +0 -199
- package/templates/interface-boilerplate/lib/callFlow.ts +0 -234
- package/templates/interface-boilerplate/lib/flowTracer.ts +0 -195
- package/templates/interface-boilerplate/lib/hooks/.gitkeep +0 -0
- package/templates/interface-boilerplate/lib/stores/.gitkeep +0 -0
- package/templates/interface-boilerplate/next.config.js +0 -6
- package/templates/interface-boilerplate/postcss.config.js +0 -6
- package/templates/interface-boilerplate/tailwind.config.js +0 -103
|
@@ -1,32 +1,36 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { cva, type VariantProps } from
|
|
3
|
-
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
4
5
|
|
|
5
6
|
const badgeVariants = cva(
|
|
6
|
-
|
|
7
|
+
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2",
|
|
7
8
|
{
|
|
8
9
|
variants: {
|
|
9
10
|
variant: {
|
|
10
|
-
default:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
default:
|
|
12
|
+
"border-transparent bg-gray-900 text-white shadow hover:bg-gray-800",
|
|
13
|
+
secondary:
|
|
14
|
+
"border-transparent bg-gray-100 text-gray-900 hover:bg-gray-200",
|
|
15
|
+
destructive:
|
|
16
|
+
"border-transparent bg-red-600 text-white shadow hover:bg-red-700",
|
|
17
|
+
outline: "text-gray-900 border-gray-300",
|
|
16
18
|
},
|
|
17
19
|
},
|
|
18
20
|
defaultVariants: {
|
|
19
|
-
variant:
|
|
21
|
+
variant: "default",
|
|
20
22
|
},
|
|
21
23
|
}
|
|
22
|
-
)
|
|
24
|
+
)
|
|
23
25
|
|
|
24
26
|
export interface BadgeProps
|
|
25
27
|
extends React.HTMLAttributes<HTMLDivElement>,
|
|
26
28
|
VariantProps<typeof badgeVariants> {}
|
|
27
29
|
|
|
28
30
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
29
|
-
return
|
|
31
|
+
return (
|
|
32
|
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
|
33
|
+
)
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
export { Badge, badgeVariants }
|
|
36
|
+
export { Badge, badgeVariants }
|
|
@@ -1,52 +1,57 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { Slot } from
|
|
3
|
-
import { cva, type VariantProps } from
|
|
4
|
-
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
5
6
|
|
|
6
7
|
const buttonVariants = cva(
|
|
7
|
-
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
8
9
|
{
|
|
9
10
|
variants: {
|
|
10
11
|
variant: {
|
|
11
|
-
default:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
default:
|
|
13
|
+
"bg-gray-900 text-white shadow hover:bg-gray-800",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-red-600 text-white shadow-sm hover:bg-red-700",
|
|
16
|
+
outline:
|
|
17
|
+
"border border-gray-300 bg-white shadow-sm hover:bg-gray-50",
|
|
18
|
+
secondary:
|
|
19
|
+
"bg-gray-100 text-gray-900 shadow-sm hover:bg-gray-200",
|
|
20
|
+
ghost: "hover:bg-gray-100",
|
|
21
|
+
link: "text-gray-900 underline-offset-4 hover:underline",
|
|
17
22
|
},
|
|
18
23
|
size: {
|
|
19
|
-
default:
|
|
20
|
-
sm:
|
|
21
|
-
lg:
|
|
22
|
-
icon:
|
|
24
|
+
default: "h-9 px-4 py-2",
|
|
25
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
26
|
+
lg: "h-10 rounded-md px-8",
|
|
27
|
+
icon: "h-9 w-9",
|
|
23
28
|
},
|
|
24
29
|
},
|
|
25
30
|
defaultVariants: {
|
|
26
|
-
variant:
|
|
27
|
-
size:
|
|
31
|
+
variant: "default",
|
|
32
|
+
size: "default",
|
|
28
33
|
},
|
|
29
34
|
}
|
|
30
|
-
)
|
|
35
|
+
)
|
|
31
36
|
|
|
32
37
|
export interface ButtonProps
|
|
33
38
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
34
39
|
VariantProps<typeof buttonVariants> {
|
|
35
|
-
asChild?: boolean
|
|
40
|
+
asChild?: boolean
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
39
44
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
40
|
-
const Comp = asChild ? Slot :
|
|
45
|
+
const Comp = asChild ? Slot : "button"
|
|
41
46
|
return (
|
|
42
47
|
<Comp
|
|
43
48
|
className={cn(buttonVariants({ variant, size, className }))}
|
|
44
49
|
ref={ref}
|
|
45
50
|
{...props}
|
|
46
51
|
/>
|
|
47
|
-
)
|
|
52
|
+
)
|
|
48
53
|
}
|
|
49
|
-
)
|
|
50
|
-
Button.displayName =
|
|
54
|
+
)
|
|
55
|
+
Button.displayName = "Button"
|
|
51
56
|
|
|
52
|
-
export { Button, buttonVariants }
|
|
57
|
+
export { Button, buttonVariants }
|
|
@@ -1,57 +1,76 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const Card = React.forwardRef<
|
|
6
|
+
HTMLDivElement,
|
|
7
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<div
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn(
|
|
12
|
+
"rounded-xl border border-gray-200 bg-white shadow",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
))
|
|
18
|
+
Card.displayName = "Card"
|
|
19
|
+
|
|
20
|
+
const CardHeader = React.forwardRef<
|
|
21
|
+
HTMLDivElement,
|
|
22
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
23
|
+
>(({ className, ...props }, ref) => (
|
|
24
|
+
<div
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
))
|
|
30
|
+
CardHeader.displayName = "CardHeader"
|
|
31
|
+
|
|
32
|
+
const CardTitle = React.forwardRef<
|
|
33
|
+
HTMLDivElement,
|
|
34
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
35
|
+
>(({ className, ...props }, ref) => (
|
|
36
|
+
<div
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
))
|
|
42
|
+
CardTitle.displayName = "CardTitle"
|
|
43
|
+
|
|
44
|
+
const CardDescription = React.forwardRef<
|
|
45
|
+
HTMLDivElement,
|
|
46
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
47
|
+
>(({ className, ...props }, ref) => (
|
|
48
|
+
<div
|
|
49
|
+
ref={ref}
|
|
50
|
+
className={cn("text-sm text-gray-500", className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
))
|
|
54
|
+
CardDescription.displayName = "CardDescription"
|
|
55
|
+
|
|
56
|
+
const CardContent = React.forwardRef<
|
|
57
|
+
HTMLDivElement,
|
|
58
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
59
|
+
>(({ className, ...props }, ref) => (
|
|
60
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
61
|
+
))
|
|
62
|
+
CardContent.displayName = "CardContent"
|
|
63
|
+
|
|
64
|
+
const CardFooter = React.forwardRef<
|
|
65
|
+
HTMLDivElement,
|
|
66
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
67
|
+
>(({ className, ...props }, ref) => (
|
|
68
|
+
<div
|
|
69
|
+
ref={ref}
|
|
70
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
))
|
|
74
|
+
CardFooter.displayName = "CardFooter"
|
|
75
|
+
|
|
76
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { cn } from '@/lib/utils';
|
|
1
|
+
import * as React from "react"
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
5
4
|
|
|
6
|
-
const Input = React.forwardRef<HTMLInputElement,
|
|
5
|
+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
7
6
|
({ className, type, ...props }, ref) => {
|
|
8
7
|
return (
|
|
9
8
|
<input
|
|
10
9
|
type={type}
|
|
11
10
|
className={cn(
|
|
12
|
-
|
|
11
|
+
"flex h-9 w-full rounded-md border border-gray-300 bg-white px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-400 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
13
12
|
className
|
|
14
13
|
)}
|
|
15
14
|
ref={ref}
|
|
16
15
|
{...props}
|
|
17
16
|
/>
|
|
18
|
-
)
|
|
17
|
+
)
|
|
19
18
|
}
|
|
20
|
-
)
|
|
21
|
-
Input.displayName =
|
|
19
|
+
)
|
|
20
|
+
Input.displayName = "Input"
|
|
22
21
|
|
|
23
|
-
export { Input }
|
|
22
|
+
export { Input }
|
|
@@ -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,18 @@
|
|
|
1
|
+
import { createAuthClient } from "better-auth/react";
|
|
2
|
+
|
|
3
|
+
// Auth client configured to use the Lux Studio API
|
|
4
|
+
// The API URL and Org ID are injected at build/runtime
|
|
5
|
+
const baseURL = process.env.NEXT_PUBLIC_LUX_API_URL || "https://v2.uselux.ai";
|
|
6
|
+
const orgId = process.env.NEXT_PUBLIC_LUX_ORG_ID || "";
|
|
7
|
+
|
|
8
|
+
export const authClient = createAuthClient({
|
|
9
|
+
baseURL: `${baseURL}/api/auth`,
|
|
10
|
+
fetchOptions: {
|
|
11
|
+
headers: {
|
|
12
|
+
"X-Org-Id": orgId,
|
|
13
|
+
},
|
|
14
|
+
credentials: "include",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const { signIn, signUp, signOut, useSession } = authClient;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Configuration
|
|
3
|
+
*
|
|
4
|
+
* This file defines which routes require authentication and which are public.
|
|
5
|
+
* Update this file to control access to different parts of your application.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const authConfig = {
|
|
9
|
+
/**
|
|
10
|
+
* Routes that require authentication
|
|
11
|
+
* Users will be redirected to sign-in if not authenticated
|
|
12
|
+
*/
|
|
13
|
+
protectedRoutes: [
|
|
14
|
+
"/dashboard",
|
|
15
|
+
"/profile",
|
|
16
|
+
"/settings",
|
|
17
|
+
"/account",
|
|
18
|
+
"/auth/callback", // Organization setup after OAuth
|
|
19
|
+
// Add more protected routes here
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Auth pages (sign-in, sign-up, etc.)
|
|
24
|
+
* Authenticated users will be redirected away from these pages
|
|
25
|
+
*/
|
|
26
|
+
authRoutes: [
|
|
27
|
+
"/sign-in",
|
|
28
|
+
"/sign-up",
|
|
29
|
+
"/verify-email",
|
|
30
|
+
],
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Public routes that anyone can access
|
|
34
|
+
* These routes are accessible whether authenticated or not
|
|
35
|
+
*/
|
|
36
|
+
publicRoutes: [
|
|
37
|
+
"/",
|
|
38
|
+
"/about",
|
|
39
|
+
"/pricing",
|
|
40
|
+
"/contact",
|
|
41
|
+
"/blog",
|
|
42
|
+
"/docs",
|
|
43
|
+
"/accept-invite",
|
|
44
|
+
// Add more public routes here
|
|
45
|
+
],
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default redirect destinations
|
|
49
|
+
*/
|
|
50
|
+
redirects: {
|
|
51
|
+
// Where to redirect after successful sign-in
|
|
52
|
+
afterSignIn: "/dashboard",
|
|
53
|
+
|
|
54
|
+
// Where to redirect authenticated users who try to access auth pages
|
|
55
|
+
afterAuth: "/dashboard",
|
|
56
|
+
|
|
57
|
+
// Where to redirect unauthenticated users
|
|
58
|
+
toSignIn: "/sign-in",
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Route matching options
|
|
63
|
+
*/
|
|
64
|
+
options: {
|
|
65
|
+
// If true, "/dashboard" will also match "/dashboard/settings"
|
|
66
|
+
matchPrefixes: true,
|
|
67
|
+
|
|
68
|
+
// If true, "/" is treated as a public route even if not in publicRoutes
|
|
69
|
+
rootIsPublic: true,
|
|
70
|
+
},
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Helper function to check if a route matches a pattern
|
|
75
|
+
*/
|
|
76
|
+
export function isRouteMatch(pathname: string, pattern: string, matchPrefix = true): boolean {
|
|
77
|
+
if (matchPrefix) {
|
|
78
|
+
return pathname === pattern || pathname.startsWith(pattern + "/");
|
|
79
|
+
}
|
|
80
|
+
return pathname === pattern;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Helper function to determine route type
|
|
85
|
+
*/
|
|
86
|
+
export function getRouteType(pathname: string): "protected" | "auth" | "public" {
|
|
87
|
+
const { protectedRoutes, authRoutes, publicRoutes, options } = authConfig;
|
|
88
|
+
|
|
89
|
+
// Check if it's a protected route
|
|
90
|
+
if (protectedRoutes.some(route => isRouteMatch(pathname, route, options.matchPrefixes))) {
|
|
91
|
+
return "protected";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check if it's an auth route
|
|
95
|
+
if (authRoutes.some(route => isRouteMatch(pathname, route, options.matchPrefixes))) {
|
|
96
|
+
return "auth";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if it's explicitly public
|
|
100
|
+
if (publicRoutes.some(route => isRouteMatch(pathname, route, options.matchPrefixes))) {
|
|
101
|
+
return "public";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Special case for root
|
|
105
|
+
if (pathname === "/" && options.rootIsPublic) {
|
|
106
|
+
return "public";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Default: treat as public (change to "protected" for whitelist approach)
|
|
110
|
+
return "public";
|
|
111
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a lux helper for generating data-lux attribute values
|
|
3
|
+
* @param componentName - The name of the component
|
|
4
|
+
* @returns A function that generates prefixed element IDs
|
|
5
|
+
*/
|
|
6
|
+
export function createLux(componentName: string) {
|
|
7
|
+
return (elementId: string) => `${componentName}__${elementId}`;
|
|
8
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type ClassValue, clsx } from
|
|
2
|
-
import { twMerge } from
|
|
1
|
+
import { type ClassValue, clsx } from "clsx"
|
|
2
|
+
import { twMerge } from "tailwind-merge"
|
|
3
3
|
|
|
4
4
|
export function cn(...inputs: ClassValue[]) {
|
|
5
|
-
return twMerge(clsx(inputs))
|
|
5
|
+
return twMerge(clsx(inputs))
|
|
6
6
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import type { NextRequest } from "next/server";
|
|
3
|
+
import { authConfig, getRouteType } from "@/lib/auth.config";
|
|
4
|
+
|
|
5
|
+
export async function middleware(request: NextRequest) {
|
|
6
|
+
const { pathname } = request.nextUrl;
|
|
7
|
+
|
|
8
|
+
// Check if user has a session cookie
|
|
9
|
+
const sessionToken = request.cookies.get("better-auth.session_token");
|
|
10
|
+
const isAuthenticated = !!sessionToken;
|
|
11
|
+
|
|
12
|
+
// Special handling for API routes
|
|
13
|
+
if (pathname.startsWith("/api/")) {
|
|
14
|
+
// API auth routes are always allowed
|
|
15
|
+
if (pathname.startsWith("/api/auth/")) {
|
|
16
|
+
return NextResponse.next();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// All other API routes require authentication
|
|
20
|
+
if (!isAuthenticated) {
|
|
21
|
+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return NextResponse.next();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Page route handling (non-API)
|
|
28
|
+
// Determine route type from config
|
|
29
|
+
const routeType = getRouteType(pathname);
|
|
30
|
+
|
|
31
|
+
// Handle auth routes (signin, signup, etc.)
|
|
32
|
+
// Redirect authenticated users away from auth pages
|
|
33
|
+
if (routeType === "auth" && isAuthenticated) {
|
|
34
|
+
return NextResponse.redirect(new URL(authConfig.redirects.afterAuth, request.url));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Handle protected routes
|
|
38
|
+
// Redirect unauthenticated users to signin
|
|
39
|
+
if (routeType === "protected" && !isAuthenticated) {
|
|
40
|
+
const signInUrl = new URL(authConfig.redirects.toSignIn, request.url);
|
|
41
|
+
// Preserve the original destination for redirect after sign-in
|
|
42
|
+
signInUrl.searchParams.set("callbackUrl", pathname);
|
|
43
|
+
return NextResponse.redirect(signInUrl);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Allow access to public routes
|
|
47
|
+
return NextResponse.next();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const config = {
|
|
51
|
+
matcher: [
|
|
52
|
+
/*
|
|
53
|
+
* Match all request paths except:
|
|
54
|
+
* - _next/static (static files)
|
|
55
|
+
* - _next/image (image optimization)
|
|
56
|
+
* - favicon.ico (favicon file)
|
|
57
|
+
*/
|
|
58
|
+
"/((?!_next/static|_next/image|favicon.ico).*)",
|
|
59
|
+
],
|
|
60
|
+
};
|