nextjs-hackathon-stack 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/eslint.config.ts +1 -1
- package/template/next.config.ts +3 -5
- package/template/package.json.tmpl +6 -1
- package/template/playwright.config.ts +1 -1
- package/template/src/app/(protected)/layout.tsx +1 -0
- package/template/src/app/(protected)/page.tsx +1 -1
- package/template/src/app/api/auth/callback/route.ts +1 -0
- package/template/src/app/layout.tsx +1 -0
- package/template/src/features/auth/__tests__/login-form.test.tsx +1 -0
- package/template/src/features/auth/__tests__/use-auth.test.ts +5 -4
- package/template/src/features/auth/actions/login.action.ts +1 -0
- package/template/src/features/auth/actions/logout.action.ts +1 -0
- package/template/src/features/auth/components/login-form.tsx +5 -3
- package/template/src/features/auth/hooks/use-auth.ts +1 -0
- package/template/src/features/chat/__tests__/chat-ui.test.tsx +1 -0
- package/template/src/features/chat/api/route.ts +3 -2
- package/template/src/features/chat/components/chat-ui.tsx +2 -1
- package/template/src/features/tts/__tests__/tts-player.test.tsx +1 -0
- package/template/src/features/tts/components/tts-player.tsx +3 -3
- package/template/src/features/video/__tests__/video-generator.test.tsx +1 -0
- package/template/src/features/video/components/video-generator.tsx +3 -3
- package/template/src/shared/__tests__/ai.test.ts +1 -0
- package/template/src/shared/__tests__/minimax-media.test.ts +3 -2
- package/template/src/shared/__tests__/providers.test.tsx +1 -0
- package/template/src/shared/__tests__/schema.test.ts +1 -0
- package/template/src/shared/components/ui/button.tsx +59 -0
- package/template/src/shared/components/ui/card.tsx +92 -0
- package/template/src/shared/components/ui/input.tsx +19 -0
- package/template/src/shared/components/ui/label.tsx +22 -0
- package/template/src/shared/db/index.ts +2 -1
- package/template/src/shared/lib/ai.ts +2 -2
- package/template/src/shared/lib/minimax-media.ts +2 -2
- package/template/src/shared/lib/supabase/client.ts +2 -2
- package/template/src/shared/lib/supabase/middleware.ts +9 -8
- package/template/src/shared/lib/supabase/server.ts +6 -4
package/package.json
CHANGED
|
@@ -61,6 +61,6 @@ export default tseslint.config(
|
|
|
61
61
|
},
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
|
-
ignores: [".next/**", "node_modules/**", "dist/**"],
|
|
64
|
+
ignores: [".next/**", "node_modules/**", "dist/**", "eslint.config.ts", "next-env.d.ts", "next.config.ts", "playwright.config.ts", "drizzle.config.ts", "vitest.config.ts"],
|
|
65
65
|
}
|
|
66
66
|
);
|
package/template/next.config.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
|
|
3
3
|
const nextConfig: NextConfig = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
},
|
|
7
|
-
headers: async () => [
|
|
4
|
+
typedRoutes: true,
|
|
5
|
+
headers: () => Promise.resolve([
|
|
8
6
|
{
|
|
9
7
|
source: "/(.*)",
|
|
10
8
|
headers: [
|
|
@@ -25,7 +23,7 @@ const nextConfig: NextConfig = {
|
|
|
25
23
|
},
|
|
26
24
|
],
|
|
27
25
|
},
|
|
28
|
-
],
|
|
26
|
+
]),
|
|
29
27
|
};
|
|
30
28
|
|
|
31
29
|
export default nextConfig;
|
|
@@ -32,8 +32,13 @@
|
|
|
32
32
|
"zod": "^3",
|
|
33
33
|
"react": "^19",
|
|
34
34
|
"react-dom": "^19",
|
|
35
|
+
"postgres": "^3",
|
|
36
|
+
"@ai-sdk/openai": "^1",
|
|
35
37
|
"clsx": "^2",
|
|
36
|
-
"tailwind-merge": "^2"
|
|
38
|
+
"tailwind-merge": "^2",
|
|
39
|
+
"class-variance-authority": "^0.7",
|
|
40
|
+
"@radix-ui/react-slot": "^1",
|
|
41
|
+
"@radix-ui/react-label": "^2"
|
|
37
42
|
},
|
|
38
43
|
"devDependencies": {
|
|
39
44
|
"typescript": "^5",
|
|
@@ -5,7 +5,7 @@ export default defineConfig({
|
|
|
5
5
|
fullyParallel: true,
|
|
6
6
|
forbidOnly: !!process.env["CI"],
|
|
7
7
|
retries: process.env["CI"] ? 2 : 0,
|
|
8
|
-
|
|
8
|
+
...(process.env["CI"] ? { workers: 1 } : {}),
|
|
9
9
|
reporter: "html",
|
|
10
10
|
use: {
|
|
11
11
|
baseURL: "http://localhost:3000",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createClient } from "@/shared/lib/supabase/server";
|
|
2
1
|
import { ChatUi } from "@/features/chat/components/chat-ui";
|
|
2
|
+
import { createClient } from "@/shared/lib/supabase/server";
|
|
3
3
|
|
|
4
4
|
export default async function HomePage() {
|
|
5
5
|
const supabase = await createClient();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
4
|
+
|
|
4
5
|
import { LoginForm } from "../components/login-form";
|
|
5
6
|
|
|
6
7
|
vi.mock("../actions/login.action", () => ({
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { renderHook, waitFor } from "@testing-library/react";
|
|
2
|
-
import { describe, it, expect, vi } from "vitest";
|
|
3
1
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
4
3
|
import { createElement } from "react";
|
|
4
|
+
import { describe, it, expect, vi } from "vitest";
|
|
5
|
+
|
|
5
6
|
import { useAuth } from "../hooks/use-auth";
|
|
6
7
|
|
|
7
8
|
const mockGetSession = vi.fn();
|
|
@@ -29,7 +30,7 @@ describe("useAuth", () => {
|
|
|
29
30
|
const { result } = renderHook(() => useAuth(), { wrapper });
|
|
30
31
|
|
|
31
32
|
// Assert
|
|
32
|
-
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
33
|
+
await waitFor(() => { expect(result.current.isSuccess).toBe(true); });
|
|
33
34
|
expect(result.current.data).toEqual(mockSession);
|
|
34
35
|
});
|
|
35
36
|
|
|
@@ -41,6 +42,6 @@ describe("useAuth", () => {
|
|
|
41
42
|
const { result } = renderHook(() => useAuth(), { wrapper });
|
|
42
43
|
|
|
43
44
|
// Assert
|
|
44
|
-
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
45
|
+
await waitFor(() => { expect(result.current.isError).toBe(true); });
|
|
45
46
|
});
|
|
46
47
|
});
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
3
4
|
import { useActionState } from "react";
|
|
4
5
|
import { useForm } from "react-hook-form";
|
|
5
|
-
import { zodResolver } from "@hookform/resolvers/zod";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
|
|
7
8
|
import { loginAction, type LoginActionState } from "../actions/login.action";
|
|
9
|
+
|
|
8
10
|
import { Button } from "@/shared/components/ui/button";
|
|
11
|
+
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/shared/components/ui/card";
|
|
9
12
|
import { Input } from "@/shared/components/ui/input";
|
|
10
13
|
import { Label } from "@/shared/components/ui/label";
|
|
11
|
-
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/shared/components/ui/card";
|
|
12
14
|
|
|
13
15
|
const schema = z.object({
|
|
14
16
|
email: z.string().email("Invalid email"),
|
|
@@ -42,7 +44,7 @@ export function LoginForm() {
|
|
|
42
44
|
<CardDescription>Enter your email and password below</CardDescription>
|
|
43
45
|
</CardHeader>
|
|
44
46
|
<CardContent>
|
|
45
|
-
<form onSubmit={onSubmit} data-testid="login-form" className="space-y-4">
|
|
47
|
+
<form onSubmit={(e) => { void onSubmit(e); }} data-testid="login-form" className="space-y-4">
|
|
46
48
|
{state.status === "error" && (
|
|
47
49
|
<p role="alert" className="rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
|
48
50
|
{state.message}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { streamText } from "ai";
|
|
1
|
+
import { streamText, type CoreMessage } from "ai";
|
|
2
|
+
|
|
2
3
|
import { aiModel } from "@/shared/lib/ai";
|
|
3
4
|
|
|
4
5
|
export const runtime = "edge";
|
|
5
6
|
|
|
6
7
|
export async function POST(req: Request) {
|
|
7
|
-
const { messages } = (await req.json()) as { messages:
|
|
8
|
+
const { messages } = (await req.json()) as { messages: CoreMessage[] };
|
|
8
9
|
|
|
9
10
|
const result = streamText({
|
|
10
11
|
model: aiModel,
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import { useChat } from "@ai-sdk/react";
|
|
4
4
|
|
|
5
5
|
export function ChatUi() {
|
|
6
|
-
const { messages, input, handleInputChange, handleSubmit,
|
|
6
|
+
const { messages, input, handleInputChange, handleSubmit, status } = useChat({
|
|
7
7
|
api: "/features/chat/api",
|
|
8
8
|
});
|
|
9
|
+
const isLoading = status === "streaming" || status === "submitted";
|
|
9
10
|
|
|
10
11
|
return (
|
|
11
12
|
<div className="flex flex-col gap-4" data-testid="chat-ui">
|
|
@@ -8,7 +8,7 @@ export function TtsPlayer() {
|
|
|
8
8
|
const [isLoading, setIsLoading] = useState(false);
|
|
9
9
|
const [error, setError] = useState<string | null>(null);
|
|
10
10
|
|
|
11
|
-
const handleSubmit = async (e: React.
|
|
11
|
+
const handleSubmit = async (e: React.SyntheticEvent) => {
|
|
12
12
|
e.preventDefault();
|
|
13
13
|
setIsLoading(true);
|
|
14
14
|
setError(null);
|
|
@@ -32,10 +32,10 @@ export function TtsPlayer() {
|
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
34
|
<div className="space-y-4" data-testid="tts-player">
|
|
35
|
-
<form onSubmit={handleSubmit} className="flex gap-2">
|
|
35
|
+
<form onSubmit={(e) => { void handleSubmit(e); }} className="flex gap-2">
|
|
36
36
|
<textarea
|
|
37
37
|
value={text}
|
|
38
|
-
onChange={(e) => setText(e.target.value)}
|
|
38
|
+
onChange={(e) => { setText(e.target.value); }}
|
|
39
39
|
placeholder="Enter text to speak..."
|
|
40
40
|
className="flex-1 rounded border px-3 py-2"
|
|
41
41
|
rows={3}
|
|
@@ -8,7 +8,7 @@ export function VideoGenerator() {
|
|
|
8
8
|
const [isLoading, setIsLoading] = useState(false);
|
|
9
9
|
const [error, setError] = useState<string | null>(null);
|
|
10
10
|
|
|
11
|
-
const handleSubmit = async (e: React.
|
|
11
|
+
const handleSubmit = async (e: React.SyntheticEvent) => {
|
|
12
12
|
e.preventDefault();
|
|
13
13
|
setIsLoading(true);
|
|
14
14
|
setError(null);
|
|
@@ -32,10 +32,10 @@ export function VideoGenerator() {
|
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
34
|
<div className="space-y-4" data-testid="video-generator">
|
|
35
|
-
<form onSubmit={handleSubmit} className="flex gap-2">
|
|
35
|
+
<form onSubmit={(e) => { void handleSubmit(e); }} className="flex gap-2">
|
|
36
36
|
<input
|
|
37
37
|
value={prompt}
|
|
38
|
-
onChange={(e) => setPrompt(e.target.value)}
|
|
38
|
+
onChange={(e) => { setPrompt(e.target.value); }}
|
|
39
39
|
placeholder="Describe a video..."
|
|
40
40
|
className="flex-1 rounded border px-3 py-2"
|
|
41
41
|
/>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
|
|
2
3
|
import { generateVideo, synthesizeSpeech } from "../lib/minimax-media";
|
|
3
4
|
|
|
4
5
|
const mockFetch = vi.fn();
|
|
@@ -13,7 +14,7 @@ describe("generateVideo", () => {
|
|
|
13
14
|
// Arrange
|
|
14
15
|
mockFetch.mockResolvedValue({
|
|
15
16
|
ok: true,
|
|
16
|
-
json:
|
|
17
|
+
json: () => Promise.resolve({ task_id: "abc123" }),
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
// Act
|
|
@@ -37,7 +38,7 @@ describe("synthesizeSpeech", () => {
|
|
|
37
38
|
// Arrange
|
|
38
39
|
mockFetch.mockResolvedValue({
|
|
39
40
|
ok: true,
|
|
40
|
-
json:
|
|
41
|
+
json: () => Promise.resolve({ audio_file: "https://example.com/audio.mp3" }),
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
// Act
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/shared/lib/utils"
|
|
6
|
+
|
|
7
|
+
const buttonVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
16
|
+
outline:
|
|
17
|
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
18
|
+
secondary:
|
|
19
|
+
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
20
|
+
ghost:
|
|
21
|
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
26
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
27
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
28
|
+
icon: "size-9",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
defaultVariants: {
|
|
32
|
+
variant: "default",
|
|
33
|
+
size: "default",
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
function Button({
|
|
39
|
+
className,
|
|
40
|
+
variant,
|
|
41
|
+
size,
|
|
42
|
+
asChild = false,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<"button"> &
|
|
45
|
+
VariantProps<typeof buttonVariants> & {
|
|
46
|
+
asChild?: boolean
|
|
47
|
+
}) {
|
|
48
|
+
const Comp = asChild ? Slot : "button"
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Comp
|
|
52
|
+
data-slot="button"
|
|
53
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/shared/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card-header"
|
|
22
|
+
className={cn(
|
|
23
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-slot="card-title"
|
|
35
|
+
className={cn("leading-none font-semibold", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="card-description"
|
|
45
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="card-action"
|
|
55
|
+
className={cn(
|
|
56
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="card-content"
|
|
68
|
+
className={cn("px-6", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="card-footer"
|
|
78
|
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
Card,
|
|
86
|
+
CardHeader,
|
|
87
|
+
CardFooter,
|
|
88
|
+
CardTitle,
|
|
89
|
+
CardAction,
|
|
90
|
+
CardDescription,
|
|
91
|
+
CardContent,
|
|
92
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/shared/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot="input"
|
|
10
|
+
className={cn(
|
|
11
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Input }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/shared/lib/utils"
|
|
5
|
+
|
|
6
|
+
function Label({
|
|
7
|
+
className,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
10
|
+
return (
|
|
11
|
+
<LabelPrimitive.Root
|
|
12
|
+
data-slot="label"
|
|
13
|
+
className={cn(
|
|
14
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { Label }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
2
2
|
import postgres from "postgres";
|
|
3
|
+
|
|
3
4
|
import * as schema from "./schema";
|
|
4
5
|
|
|
5
|
-
const client = postgres(process.env
|
|
6
|
+
const client = postgres(process.env.DATABASE_URL ?? "");
|
|
6
7
|
|
|
7
8
|
export const db = drizzle(client, { schema });
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
2
|
|
|
3
3
|
const gateway = createOpenAI({
|
|
4
|
-
baseURL: process.env
|
|
5
|
-
apiKey: process.env
|
|
4
|
+
baseURL: process.env.AI_GATEWAY_URL ?? "https://api.minimaxi.chat/v1",
|
|
5
|
+
apiKey: process.env.MINIMAX_API_KEY ?? "",
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
export const aiModel = gateway("minimax/minimax-m2.7");
|
|
@@ -23,7 +23,7 @@ export async function generateVideo(prompt: string): Promise<string> {
|
|
|
23
23
|
const response = await fetch(`${MINIMAX_API_BASE}/video_generation`, {
|
|
24
24
|
method: "POST",
|
|
25
25
|
headers: {
|
|
26
|
-
Authorization: `Bearer ${process.env
|
|
26
|
+
Authorization: `Bearer ${process.env.MINIMAX_API_KEY ?? ""}`,
|
|
27
27
|
"Content-Type": "application/json",
|
|
28
28
|
},
|
|
29
29
|
body: JSON.stringify({
|
|
@@ -44,7 +44,7 @@ export async function synthesizeSpeech(text: string, voiceId = "female-shaonv"):
|
|
|
44
44
|
const response = await fetch(`${MINIMAX_API_BASE}/t2a_v2`, {
|
|
45
45
|
method: "POST",
|
|
46
46
|
headers: {
|
|
47
|
-
Authorization: `Bearer ${process.env
|
|
47
|
+
Authorization: `Bearer ${process.env.MINIMAX_API_KEY ?? ""}`,
|
|
48
48
|
"Content-Type": "application/json",
|
|
49
49
|
},
|
|
50
50
|
body: JSON.stringify({
|
|
@@ -2,7 +2,7 @@ import { createBrowserClient } from "@supabase/ssr";
|
|
|
2
2
|
|
|
3
3
|
export function createClient() {
|
|
4
4
|
return createBrowserClient(
|
|
5
|
-
process.env
|
|
6
|
-
process.env
|
|
5
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL ?? "",
|
|
6
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? ""
|
|
7
7
|
);
|
|
8
8
|
}
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { createServerClient } from "@supabase/ssr";
|
|
1
|
+
import { createServerClient, type CookieOptions } from "@supabase/ssr";
|
|
2
2
|
import { NextResponse, type NextRequest } from "next/server";
|
|
3
3
|
|
|
4
4
|
export async function updateSession(request: NextRequest) {
|
|
5
5
|
let supabaseResponse = NextResponse.next({ request });
|
|
6
6
|
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
7
8
|
const supabase = createServerClient(
|
|
8
|
-
process.env
|
|
9
|
-
process.env
|
|
9
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL ?? "",
|
|
10
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "",
|
|
10
11
|
{
|
|
11
12
|
cookies: {
|
|
12
13
|
getAll() {
|
|
13
14
|
return request.cookies.getAll();
|
|
14
15
|
},
|
|
15
|
-
setAll(cookiesToSet) {
|
|
16
|
-
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
|
|
16
|
+
setAll(cookiesToSet: { name: string; value: string; options: CookieOptions }[]) {
|
|
17
|
+
cookiesToSet.forEach(({ name, value }) => { request.cookies.set(name, value); });
|
|
17
18
|
supabaseResponse = NextResponse.next({ request });
|
|
18
|
-
cookiesToSet.forEach(({ name, value, options }) =>
|
|
19
|
-
supabaseResponse.cookies.set(name, value, options)
|
|
20
|
-
);
|
|
19
|
+
cookiesToSet.forEach(({ name, value, options }) => {
|
|
20
|
+
supabaseResponse.cookies.set(name, value, options);
|
|
21
|
+
});
|
|
21
22
|
},
|
|
22
23
|
},
|
|
23
24
|
}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import { createServerClient } from "@supabase/ssr";
|
|
1
|
+
import { createServerClient, type CookieOptions } from "@supabase/ssr";
|
|
2
2
|
import { cookies } from "next/headers";
|
|
3
3
|
|
|
4
4
|
export async function createClient() {
|
|
5
5
|
const cookieStore = await cookies();
|
|
6
6
|
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
7
8
|
return createServerClient(
|
|
8
|
-
process.env
|
|
9
|
-
process.env
|
|
9
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL ?? "",
|
|
10
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "",
|
|
10
11
|
{
|
|
11
12
|
cookies: {
|
|
12
13
|
getAll() {
|
|
13
14
|
return cookieStore.getAll();
|
|
14
15
|
},
|
|
15
|
-
setAll(cookiesToSet) {
|
|
16
|
+
setAll(cookiesToSet: { name: string; value: string; options: CookieOptions }[]) {
|
|
16
17
|
try {
|
|
17
18
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
18
19
|
cookieStore.set(name, value, options)
|
|
19
20
|
);
|
|
20
21
|
} catch {
|
|
22
|
+
// Server components cannot set cookies; middleware handles this
|
|
21
23
|
}
|
|
22
24
|
},
|
|
23
25
|
},
|