-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
115 changed files
with
19,320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Get your OpenAI API Key here: https://platform.openai.com/account/api-keys | ||
OPENAI_API_KEY=**** | ||
|
||
# Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32` | ||
AUTH_SECRET=**** | ||
|
||
# The following keys below are automatically created and | ||
# added to your environment when you deploy on vercel | ||
|
||
# Instructions to create a Vercel Blob Store here: https://vercel.com/docs/storage/vercel-blob | ||
BLOB_READ_WRITE_TOKEN=**** | ||
|
||
# Instructions to create a database here: https://vercel.com/docs/storage/vercel-postgres/quickstart | ||
POSTGRES_URL=**** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"extends": [ | ||
"next/core-web-vitals", | ||
"plugin:import/recommended", | ||
"plugin:import/typescript", | ||
"prettier", | ||
"plugin:tailwindcss/recommended" | ||
], | ||
"plugins": ["tailwindcss"], | ||
"rules": { | ||
"tailwindcss/no-custom-classname": "off", | ||
"tailwindcss/classnames-order": "off" | ||
}, | ||
"settings": { | ||
"import/resolver": { | ||
"typescript": { | ||
"alwaysTryTypes": true | ||
} | ||
} | ||
}, | ||
"ignorePatterns": ["**/components/ui/**"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: Lint | ||
on: | ||
push: | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-22.04 | ||
strategy: | ||
matrix: | ||
node-version: [20] | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Install pnpm | ||
uses: pnpm/action-setup@v4 | ||
with: | ||
version: 9 | ||
- name: Use Node.js ${{ matrix.node-version }} | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: ${{ matrix.node-version }} | ||
cache: 'pnpm' | ||
- name: Install dependencies | ||
run: pnpm install | ||
- name: Run lint | ||
run: pnpm lint |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
node_modules | ||
.pnp | ||
.pnp.js | ||
|
||
# testing | ||
coverage | ||
|
||
# next.js | ||
.next/ | ||
out/ | ||
build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.pnpm-debug.log* | ||
|
||
# local env files | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
# turbo | ||
.turbo | ||
|
||
.env | ||
.vercel | ||
.vscode | ||
.env*.local |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright 2024 Vercel, Inc. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
'use server'; | ||
|
||
import { z } from 'zod'; | ||
|
||
import { createUser, getUser } from '@/lib/db/queries'; | ||
|
||
import { signIn } from './auth'; | ||
|
||
const authFormSchema = z.object({ | ||
email: z.string().email(), | ||
password: z.string().min(6), | ||
}); | ||
|
||
export interface LoginActionState { | ||
status: 'idle' | 'in_progress' | 'success' | 'failed' | 'invalid_data'; | ||
} | ||
|
||
export const login = async ( | ||
_: LoginActionState, | ||
formData: FormData, | ||
): Promise<LoginActionState> => { | ||
try { | ||
const validatedData = authFormSchema.parse({ | ||
email: formData.get('email'), | ||
password: formData.get('password'), | ||
}); | ||
|
||
await signIn('credentials', { | ||
email: validatedData.email, | ||
password: validatedData.password, | ||
redirect: false, | ||
}); | ||
|
||
return { status: 'success' }; | ||
} catch (error) { | ||
if (error instanceof z.ZodError) { | ||
return { status: 'invalid_data' }; | ||
} | ||
|
||
return { status: 'failed' }; | ||
} | ||
}; | ||
|
||
export interface RegisterActionState { | ||
status: | ||
| 'idle' | ||
| 'in_progress' | ||
| 'success' | ||
| 'failed' | ||
| 'user_exists' | ||
| 'invalid_data'; | ||
} | ||
|
||
export const register = async ( | ||
_: RegisterActionState, | ||
formData: FormData, | ||
): Promise<RegisterActionState> => { | ||
try { | ||
const validatedData = authFormSchema.parse({ | ||
email: formData.get('email'), | ||
password: formData.get('password'), | ||
}); | ||
|
||
const [user] = await getUser(validatedData.email); | ||
|
||
if (user) { | ||
return { status: 'user_exists' } as RegisterActionState; | ||
} | ||
await createUser(validatedData.email, validatedData.password); | ||
await signIn('credentials', { | ||
email: validatedData.email, | ||
password: validatedData.password, | ||
redirect: false, | ||
}); | ||
|
||
return { status: 'success' }; | ||
} catch (error) { | ||
if (error instanceof z.ZodError) { | ||
return { status: 'invalid_data' }; | ||
} | ||
|
||
return { status: 'failed' }; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { GET, POST } from '@/app/(auth)/auth'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import type { NextAuthConfig } from 'next-auth'; | ||
|
||
export const authConfig = { | ||
pages: { | ||
signIn: '/login', | ||
newUser: '/', | ||
}, | ||
providers: [ | ||
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js | ||
// while this file is also used in non-Node.js environments | ||
], | ||
callbacks: { | ||
authorized({ auth, request: { nextUrl } }) { | ||
const isLoggedIn = !!auth?.user; | ||
const isOnChat = nextUrl.pathname.startsWith('/'); | ||
const isOnRegister = nextUrl.pathname.startsWith('/register'); | ||
const isOnLogin = nextUrl.pathname.startsWith('/login'); | ||
|
||
if (isLoggedIn && (isOnLogin || isOnRegister)) { | ||
return Response.redirect(new URL('/', nextUrl as unknown as URL)); | ||
} | ||
|
||
if (isOnRegister || isOnLogin) { | ||
return true; // Always allow access to register and login pages | ||
} | ||
|
||
if (isOnChat) { | ||
if (isLoggedIn) return true; | ||
return false; // Redirect unauthenticated users to login page | ||
} | ||
|
||
if (isLoggedIn) { | ||
return Response.redirect(new URL('/', nextUrl as unknown as URL)); | ||
} | ||
|
||
return true; | ||
}, | ||
}, | ||
} satisfies NextAuthConfig; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { compare } from 'bcrypt-ts'; | ||
import NextAuth, { type User, type Session } from 'next-auth'; | ||
import Credentials from 'next-auth/providers/credentials'; | ||
|
||
import { getUser } from '@/lib/db/queries'; | ||
|
||
import { authConfig } from './auth.config'; | ||
|
||
interface ExtendedSession extends Session { | ||
user: User; | ||
} | ||
|
||
export const { | ||
handlers: { GET, POST }, | ||
auth, | ||
signIn, | ||
signOut, | ||
} = NextAuth({ | ||
...authConfig, | ||
providers: [ | ||
Credentials({ | ||
credentials: {}, | ||
async authorize({ email, password }: any) { | ||
const users = await getUser(email); | ||
if (users.length === 0) return null; | ||
// biome-ignore lint: Forbidden non-null assertion. | ||
const passwordsMatch = await compare(password, users[0].password!); | ||
if (!passwordsMatch) return null; | ||
return users[0] as any; | ||
}, | ||
}), | ||
], | ||
callbacks: { | ||
async jwt({ token, user }) { | ||
if (user) { | ||
token.id = user.id; | ||
} | ||
|
||
return token; | ||
}, | ||
async session({ | ||
session, | ||
token, | ||
}: { | ||
session: ExtendedSession; | ||
token: any; | ||
}) { | ||
if (session.user) { | ||
session.user.id = token.id as string; | ||
} | ||
|
||
return session; | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
'use client'; | ||
|
||
import Link from 'next/link'; | ||
import { useRouter } from 'next/navigation'; | ||
import { useActionState, useEffect, useState } from 'react'; | ||
import { toast } from 'sonner'; | ||
|
||
import { AuthForm } from '@/components/auth-form'; | ||
import { SubmitButton } from '@/components/submit-button'; | ||
|
||
import { login, type LoginActionState } from '../actions'; | ||
|
||
export default function Page() { | ||
const router = useRouter(); | ||
|
||
const [email, setEmail] = useState(''); | ||
const [isSuccessful, setIsSuccessful] = useState(false); | ||
|
||
const [state, formAction] = useActionState<LoginActionState, FormData>( | ||
login, | ||
{ | ||
status: 'idle', | ||
}, | ||
); | ||
|
||
useEffect(() => { | ||
if (state.status === 'failed') { | ||
toast.error('Invalid credentials!'); | ||
} else if (state.status === 'invalid_data') { | ||
toast.error('Failed validating your submission!'); | ||
} else if (state.status === 'success') { | ||
setIsSuccessful(true); | ||
router.refresh(); | ||
} | ||
}, [state.status, router]); | ||
|
||
const handleSubmit = (formData: FormData) => { | ||
setEmail(formData.get('email') as string); | ||
formAction(formData); | ||
}; | ||
|
||
return ( | ||
<div className="flex h-dvh w-screen items-start pt-12 md:pt-0 md:items-center justify-center bg-background"> | ||
<div className="w-full max-w-md overflow-hidden rounded-2xl flex flex-col gap-12"> | ||
<div className="flex flex-col items-center justify-center gap-2 px-4 text-center sm:px-16"> | ||
<h3 className="text-xl font-semibold dark:text-zinc-50">Sign In</h3> | ||
<p className="text-sm text-gray-500 dark:text-zinc-400"> | ||
Use your email and password to sign in | ||
</p> | ||
</div> | ||
<AuthForm action={handleSubmit} defaultEmail={email}> | ||
<SubmitButton isSuccessful={isSuccessful}>Sign in</SubmitButton> | ||
<p className="text-center text-sm text-gray-600 mt-4 dark:text-zinc-400"> | ||
{"Don't have an account? "} | ||
<Link | ||
href="/register" | ||
className="font-semibold text-gray-800 hover:underline dark:text-zinc-200" | ||
> | ||
Sign up | ||
</Link> | ||
{' for free.'} | ||
</p> | ||
</AuthForm> | ||
</div> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.