proper user/session creation and auth integration into UI
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return NextResponse.json(
|
||||
{ error: "Authentication required" },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
const { prompt } = await request.json();
|
||||
|
||||
const systemPrompt = `
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
import { signIn, signOut } from "@/auth"
|
||||
import { auth } from "@/auth"
|
||||
import { Button } from "@/components/ui/button"
|
||||
"use client"
|
||||
|
||||
export default async function SignIn() {
|
||||
const session = await auth()
|
||||
import { signOut, useSession } from "next-auth/react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
export default function SignIn() {
|
||||
const { data: session, status } = useSession()
|
||||
const router = useRouter()
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await signOut({ redirect: false })
|
||||
router.push("/")
|
||||
router.refresh()
|
||||
}
|
||||
|
||||
if (status === "loading") {
|
||||
return <div className="h-8 w-16 bg-muted animate-pulse rounded"></div>
|
||||
}
|
||||
|
||||
if (session?.user) {
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<form
|
||||
action={async () => {
|
||||
"use server"
|
||||
await signOut()
|
||||
}}
|
||||
>
|
||||
<Button type="submit" variant="secondary" >
|
||||
Sign Out
|
||||
</Button>
|
||||
</form>
|
||||
<span className="text-sm text-muted-foreground hidden sm:inline">
|
||||
{session.user.name || session.user.email}
|
||||
</span>
|
||||
<Button onClick={handleSignOut} variant="ghost" size="sm">
|
||||
Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
action={async () => {
|
||||
"use server"
|
||||
await signIn("authentik")
|
||||
}}
|
||||
<Button
|
||||
onClick={() => router.push("/signin")}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<Button type="submit" variant="default" >
|
||||
Sign In
|
||||
</Button>
|
||||
</form>
|
||||
Sign In
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -143,6 +143,13 @@ export default function HomePage() {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ prompt: aiPrompt })
|
||||
})
|
||||
|
||||
if (res.status === 401) {
|
||||
alert('Please sign in to use AI features.')
|
||||
setAiLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
@@ -225,25 +232,32 @@ export default function HomePage() {
|
||||
<div className='max-w-fit m-auto'> Drag & Drop *.ics here</div>
|
||||
</div>
|
||||
{/* AI Toolbar */}
|
||||
<div className="flex flex-row gap-4 mb-4 items-start">
|
||||
{session?.user && (
|
||||
<>
|
||||
<div className='w-full'>
|
||||
<Textarea
|
||||
className="wrap-anywhere min-h-12"
|
||||
placeholder='Describe event for AI to create'
|
||||
value={aiPrompt}
|
||||
onChange={e => setAiPrompt(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row gap-2 pt-1.5'>
|
||||
<Button onClick={handleAiCreate} disabled={aiLoading}>
|
||||
{aiLoading ? 'Thinking...' : 'AI Create'}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{session?.user ? (
|
||||
<div className="flex flex-row gap-4 mb-4 items-start">
|
||||
<div className='w-full'>
|
||||
<Textarea
|
||||
className="wrap-anywhere min-h-12"
|
||||
placeholder='Describe event for AI to create'
|
||||
value={aiPrompt}
|
||||
onChange={e => setAiPrompt(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row gap-2 pt-1.5'>
|
||||
<Button onClick={handleAiCreate} disabled={aiLoading}>
|
||||
{aiLoading ? 'Thinking...' : 'AI Create'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-4 p-4 border border-dashed rounded-lg text-center">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
Sign in to unlock AI-powered calendar features
|
||||
</div>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href="/signin">Sign In</a>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Summary Panel */}
|
||||
{
|
||||
|
||||
45
src/app/signin/page.tsx
Normal file
45
src/app/signin/page.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { signIn, auth } from "@/auth"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { redirect } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
|
||||
export default async function SignInPage() {
|
||||
const session = await auth()
|
||||
|
||||
// If already signed in, redirect to home
|
||||
if (session?.user) {
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="text-2xl font-bold">Welcome</CardTitle>
|
||||
<CardDescription>
|
||||
Sign in to access AI-powered calendar features
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<form
|
||||
action={async () => {
|
||||
"use server"
|
||||
await signIn("authentik", { redirectTo: "/" })
|
||||
}}
|
||||
>
|
||||
<Button type="submit" className="w-full" size="lg">
|
||||
Continue with Authentik
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="text-center">
|
||||
<Link href="/" className="text-sm text-muted-foreground hover:underline">
|
||||
Continue without signing in
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
49
src/app/signout/page.tsx
Normal file
49
src/app/signout/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { signOut, auth } from "@/auth"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { redirect } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
|
||||
export default async function SignOutPage() {
|
||||
const session = await auth()
|
||||
|
||||
if (!session) {
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="text-2xl font-bold">Sign Out</CardTitle>
|
||||
<CardDescription>
|
||||
Are you sure you want to sign out?
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="text-center p-3 bg-muted rounded-lg">
|
||||
<div className="text-sm text-muted-foreground">Currently signed in as</div>
|
||||
<div className="font-medium">{session.user?.name || session.user?.email}</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<form
|
||||
action={async () => {
|
||||
"use server"
|
||||
await signOut({ redirectTo: "/" })
|
||||
}}
|
||||
>
|
||||
<Button type="submit" variant="destructive" className="w-full">
|
||||
Sign Out
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/">Cancel</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,36 +1,55 @@
|
||||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
||||
import { pgTable, text, timestamp, integer, boolean, primaryKey } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
email: text('email').notNull().unique(),
|
||||
export const users = pgTable('user', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name'),
|
||||
email: text('email').notNull(),
|
||||
emailVerified: timestamp('emailVerified', { mode: 'string' }),
|
||||
image: text('image'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const accounts = pgTable('accounts', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
export const accounts = pgTable('account', {
|
||||
userId: text('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
type: text('type').notNull(),
|
||||
provider: text('provider').notNull(),
|
||||
providerAccountId: text('provider_account_id').notNull(),
|
||||
refreshToken: text('refresh_token'),
|
||||
accessToken: text('access_token'),
|
||||
expiresAt: timestamp('expires_at'),
|
||||
tokenType: text('token_type'),
|
||||
providerAccountId: text('providerAccountId').notNull(),
|
||||
refresh_token: text('refresh_token'),
|
||||
access_token: text('access_token'),
|
||||
expires_at: text('expires_at'),
|
||||
token_type: text('token_type'),
|
||||
scope: text('scope'),
|
||||
idToken: text('id_token'),
|
||||
sessionState: text('session_state'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
id_token: text('id_token'),
|
||||
session_state: text('session_state'),
|
||||
}, (account) => ({
|
||||
compoundKey: primaryKey({ columns: [account.provider, account.providerAccountId] })
|
||||
}));
|
||||
|
||||
export const sessions = pgTable('session', {
|
||||
sessionToken: text().primaryKey().notNull(),
|
||||
userId: text().notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
expires: timestamp({ mode: 'string' }).notNull(),
|
||||
});
|
||||
|
||||
export const sessions = pgTable('sessions', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
sessionToken: text('session_token').notNull().unique(),
|
||||
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
expires: timestamp('expires').notNull(),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
export const verificationTokens = pgTable('verificationToken', {
|
||||
identifier: text('identifier').notNull(),
|
||||
token: text('token').notNull(),
|
||||
expires: timestamp('expires', { mode: 'string' }).notNull(),
|
||||
}, (vt) => ({
|
||||
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] })
|
||||
}));
|
||||
|
||||
export const authenticators = pgTable('authenticator', {
|
||||
credentialID: text('credentialID').notNull().unique(),
|
||||
userId: text('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
providerAccountId: text('providerAccountId').notNull(),
|
||||
credentialPublicKey: text('credentialPublicKey').notNull(),
|
||||
counter: integer('counter').notNull(),
|
||||
credentialDeviceType: text('credentialDeviceType').notNull(),
|
||||
credentialBackedUp: boolean('credentialBackedUp').notNull(),
|
||||
transports: text('transports'),
|
||||
}, (authenticator) => ({
|
||||
compositePK: primaryKey({
|
||||
columns: [authenticator.credentialID, authenticator.userId],
|
||||
name: "authenticator_userId_credentialID_pk"
|
||||
})
|
||||
}));
|
||||
@@ -1,25 +0,0 @@
|
||||
import { auth } from "@/auth"
|
||||
|
||||
export default auth((req) => {
|
||||
const { nextUrl } = req
|
||||
const isLoggedIn = !!req.auth
|
||||
|
||||
// Protect dashboard routes
|
||||
// if (nextUrl.pathname.startsWith('/api') && !isLoggedIn) {
|
||||
// return Response.redirect(new URL('/signin', nextUrl))
|
||||
// }
|
||||
|
||||
// Redirect logged-in users from sign-in page
|
||||
if (nextUrl.pathname.startsWith('/signin') && isLoggedIn) {
|
||||
return Response.redirect(new URL('/', nextUrl))
|
||||
}
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Skip Next.js internals and all static files
|
||||
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
||||
// Always run for API routes
|
||||
'/(api|trpc)(.*)',
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user