mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +02:00
minimal register and login
This commit is contained in:
parent
2c3da5425b
commit
b0e474677e
8 changed files with 187 additions and 70 deletions
|
@ -1,7 +1,6 @@
|
||||||
import NextAuth, { type SessionStrategy } from 'next-auth'
|
import NextAuth, { type SessionStrategy } from 'next-auth'
|
||||||
import CredentialsProvider from 'next-auth/providers/credentials'
|
import CredentialsProvider from 'next-auth/providers/credentials'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import type { SharedType } from '@maybe-finance/shared'
|
|
||||||
import { PrismaClient } from '@prisma/client'
|
import { PrismaClient } from '@prisma/client'
|
||||||
import { PrismaAdapter } from '@auth/prisma-adapter'
|
import { PrismaAdapter } from '@auth/prisma-adapter'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
@ -36,19 +35,20 @@ export const authOptions = {
|
||||||
password: { label: 'Password', type: 'password' },
|
password: { label: 'Password', type: 'password' },
|
||||||
},
|
},
|
||||||
async authorize(credentials) {
|
async authorize(credentials) {
|
||||||
console.log('inside the authorize method')
|
|
||||||
const parsedCredentials = z
|
const parsedCredentials = z
|
||||||
.object({
|
.object({
|
||||||
|
name: z.string().optional(),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
password: z.string().min(6),
|
password: z.string().min(6),
|
||||||
provider: z.string().optional(),
|
|
||||||
})
|
})
|
||||||
.safeParse(credentials)
|
.safeParse(credentials)
|
||||||
|
|
||||||
|
console.log("here's the credentials", parsedCredentials)
|
||||||
|
|
||||||
if (parsedCredentials.success) {
|
if (parsedCredentials.success) {
|
||||||
console.log("Credentials are valid, let's authorize")
|
const { name, email, password } = parsedCredentials.data
|
||||||
const { email, password } = parsedCredentials.data
|
|
||||||
console.log('Here are the params', email, password)
|
console.log('Hitting endpoint to get user', email)
|
||||||
const { data } = await axios.get(`/auth-users`, {
|
const { data } = await axios.get(`/auth-users`, {
|
||||||
params: { email: email },
|
params: { email: email },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
@ -56,19 +56,18 @@ export const authOptions = {
|
||||||
|
|
||||||
const user = data.data['json']
|
const user = data.data['json']
|
||||||
|
|
||||||
console.log('This is User', user)
|
console.log('here is the user', user)
|
||||||
|
|
||||||
if (!user.id) {
|
if (!user) {
|
||||||
console.log('User does not exist, creating new user')
|
console.log('User does not exist, creating user')
|
||||||
const hashedPassword = await bcrypt.hash(password, 10)
|
const hashedPassword = await bcrypt.hash(password, 10)
|
||||||
const { data: newUser } = await axios.post<SharedType.AuthUser>(
|
console.log('Hitting endpoint to create user', name, email, hashedPassword)
|
||||||
'/auth-users',
|
const { data } = await axios.post('/auth-users', {
|
||||||
{
|
name,
|
||||||
email,
|
email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
}
|
})
|
||||||
)
|
const newUser = data.data['json']
|
||||||
console.log('Created new user', newUser)
|
|
||||||
if (newUser) return newUser
|
if (newUser) return newUser
|
||||||
throw new Error('Could not create user')
|
throw new Error('Could not create user')
|
||||||
}
|
}
|
||||||
|
@ -77,7 +76,6 @@ export const authOptions = {
|
||||||
if (passwordsMatch) return user
|
if (passwordsMatch) return user
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Invalid credentials')
|
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import type { ReactElement } from 'react'
|
import { useState, type ReactElement } from 'react'
|
||||||
import { useState } from 'react'
|
|
||||||
import { FullPageLayout } from '@maybe-finance/client/features'
|
import { FullPageLayout } from '@maybe-finance/client/features'
|
||||||
import { Input, InputPassword, Button } from '@maybe-finance/design-system'
|
import { Input, InputPassword, Button } from '@maybe-finance/design-system'
|
||||||
import { AiOutlineLoading3Quarters as LoadingIcon } from 'react-icons/ai'
|
|
||||||
import { signIn, useSession } from 'next-auth/react'
|
import { signIn, useSession } from 'next-auth/react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import Script from 'next/script'
|
import Script from 'next/script'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [email, setEmail] = useState('')
|
const [email, setEmail] = useState('')
|
||||||
|
@ -39,43 +38,59 @@ export default function LoginPage() {
|
||||||
strategy="lazyOnload"
|
strategy="lazyOnload"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
<img
|
<div className="p-px w-96 bg-white bg-opacity-10 card-light rounded-3xl radial-gradient-background">
|
||||||
className="mb-8"
|
<div className="bg-black bg-opacity-75 p-8 rounded-3xl w-full h-full items-center flex flex-col radial-gradient-background-dark">
|
||||||
src="/assets/maybe.svg"
|
<img
|
||||||
alt="Maybe Finance Logo"
|
className="mb-8"
|
||||||
height={140}
|
src="/assets/maybe.svg"
|
||||||
width={140}
|
alt="Maybe Finance Logo"
|
||||||
/>
|
height={140}
|
||||||
<form className="space-y-4" onSubmit={onSubmit}>
|
width={140}
|
||||||
<Input
|
/>
|
||||||
type="text"
|
<form className="space-y-4 w-full px-4" onSubmit={onSubmit}>
|
||||||
label="Email"
|
<Input
|
||||||
value={email}
|
type="text"
|
||||||
onChange={(e) => setEmail(e.currentTarget.value)}
|
label="Email"
|
||||||
/>
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
<InputPassword
|
<InputPassword
|
||||||
autoComplete="password"
|
autoComplete="password"
|
||||||
label="Password"
|
label="Password"
|
||||||
value={password}
|
value={password}
|
||||||
showPasswordRequirements={!isValid}
|
showPasswordRequirements={!isValid}
|
||||||
onValidityChange={(checks) => {
|
onValidityChange={(checks) => {
|
||||||
const passwordValid = checks.filter((c) => !c.isValid).length === 0
|
const passwordValid =
|
||||||
setIsValid(passwordValid)
|
checks.filter((c) => !c.isValid).length === 0
|
||||||
}}
|
setIsValid(passwordValid)
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
}}
|
||||||
setPassword(e.target.value)
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
}
|
setPassword(e.target.value)
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!isValid}
|
disabled={!isValid}
|
||||||
variant={isValid ? 'primary' : 'secondary'}
|
variant={isValid ? 'primary' : 'secondary'}
|
||||||
>
|
>
|
||||||
Log in
|
Log in
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
<div className="text-sm text-gray-50 pt-2">
|
||||||
|
<div>
|
||||||
|
Don't have an account?{' '}
|
||||||
|
<Link
|
||||||
|
className="hover:text-cyan-400 underline font-medium"
|
||||||
|
href="/register"
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import type { ReactElement } from 'react'
|
import { useState, type ReactElement } from 'react'
|
||||||
import { Input, InputPassword } from '@maybe-finance/design-system'
|
import { Input, InputPassword, Button } from '@maybe-finance/design-system'
|
||||||
import { FullPageLayout } from '@maybe-finance/client/features'
|
import { FullPageLayout } from '@maybe-finance/client/features'
|
||||||
import { useSession } from 'next-auth/react'
|
import { signIn, useSession } from 'next-auth/react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import Script from 'next/script'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
|
const [name, setName] = useState('')
|
||||||
|
const [email, setEmail] = useState('')
|
||||||
|
const [password, setPassword] = useState('')
|
||||||
|
const [isValid, setIsValid] = useState(false)
|
||||||
|
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -13,13 +20,90 @@ export default function RegisterPage() {
|
||||||
if (session) router.push('/')
|
if (session) router.push('/')
|
||||||
}, [session, router])
|
}, [session, router])
|
||||||
|
|
||||||
|
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
setName('')
|
||||||
|
setEmail('')
|
||||||
|
setPassword('')
|
||||||
|
|
||||||
|
await signIn('credentials', {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
name,
|
||||||
|
redirect: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// _app.tsx will automatically redirect if not authenticated
|
// _app.tsx will automatically redirect if not authenticated
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<>
|
||||||
<div className="text-4xl font-bold text-white">THIS IS THE LOGIN PAGE</div>
|
<Script
|
||||||
<Input type="text" placeholder="Email" />
|
src="https://cdnjs.cloudflare.com/ajax/libs/zxcvbn/4.4.2/zxcvbn.js"
|
||||||
<InputPassword placeholder="Password" />
|
strategy="lazyOnload"
|
||||||
</div>
|
/>
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||||
|
<div className="p-px w-96 bg-white bg-opacity-10 card-light rounded-3xl radial-gradient-background">
|
||||||
|
<div className="bg-black bg-opacity-75 p-8 rounded-3xl w-full h-full items-center flex flex-col radial-gradient-background-dark">
|
||||||
|
<img
|
||||||
|
className="mb-8"
|
||||||
|
src="/assets/maybe.svg"
|
||||||
|
alt="Maybe Finance Logo"
|
||||||
|
height={140}
|
||||||
|
width={140}
|
||||||
|
/>
|
||||||
|
<form className="space-y-4 w-full px-4" onSubmit={onSubmit}>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
label="Name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
label="Email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<InputPassword
|
||||||
|
autoComplete="password"
|
||||||
|
label="Password"
|
||||||
|
value={password}
|
||||||
|
showPasswordRequirements={!isValid}
|
||||||
|
onValidityChange={(checks) => {
|
||||||
|
const passwordValid =
|
||||||
|
checks.filter((c) => !c.isValid).length === 0
|
||||||
|
setIsValid(passwordValid)
|
||||||
|
}}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setPassword(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={!isValid}
|
||||||
|
variant={isValid ? 'primary' : 'secondary'}
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</Button>
|
||||||
|
<div className="text-sm text-gray-50 pt-2">
|
||||||
|
<div>
|
||||||
|
Already have an account?{' '}
|
||||||
|
<Link
|
||||||
|
className="hover:text-cyan-400 underline font-medium"
|
||||||
|
href="/login"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,3 +145,20 @@
|
||||||
height: 0;
|
height: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.radial-gradient-background {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
60% 200% at 50% 50%,
|
||||||
|
rgba(67, 97, 238, 0.5) 0%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radial-gradient-background-dark {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
100% 100% at clamp(20%, calc(30% + var(--mx) * 0.05), 40%)
|
||||||
|
clamp(50%, calc(50% + var(--my) * 0.05), 60%),
|
||||||
|
#4361ee33 0%,
|
||||||
|
#16161af4 120%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,10 @@ module.exports = merge(designSystemConfig, {
|
||||||
wave: '3 wave 0.6s ease-in-out',
|
wave: '3 wave 0.6s ease-in-out',
|
||||||
float: 'float 4s infinite linear',
|
float: 'float 4s infinite linear',
|
||||||
},
|
},
|
||||||
|
backgroundImage: {
|
||||||
|
'login-background':
|
||||||
|
'radial-gradient(100% 100% at clamp(20%, calc(30% + var(--mx) * 0.05), 40%) clamp(50%, calc(50% + var(--my) * 0.05), 60%), #4361EE33 0%, #16161Af4 120%)',
|
||||||
|
},
|
||||||
typography: () => {
|
typography: () => {
|
||||||
const { white, gray, cyan } = designSystemConfig.theme.colors
|
const { white, gray, cyan } = designSystemConfig.theme.colors
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -9,12 +9,9 @@ router.get(
|
||||||
endpoint.create({
|
endpoint.create({
|
||||||
resolve: async ({ ctx, req }) => {
|
resolve: async ({ ctx, req }) => {
|
||||||
const email = req.query.email
|
const email = req.query.email
|
||||||
console.log('Going to get the user for email', email)
|
|
||||||
const user = await ctx.authUserService.getByEmail(email as string)
|
const user = await ctx.authUserService.getByEmail(email as string)
|
||||||
console.log('Got the user', user)
|
|
||||||
if (user) return user
|
if (user) return user
|
||||||
console.log('No user found')
|
return null
|
||||||
return { data: null }
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -23,11 +20,13 @@ router.post(
|
||||||
'/',
|
'/',
|
||||||
endpoint.create({
|
endpoint.create({
|
||||||
input: z.object({
|
input: z.object({
|
||||||
|
name: z.string(),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
password: z.string().min(6),
|
password: z.string().min(6),
|
||||||
}),
|
}),
|
||||||
resolve: async ({ input, ctx }) => {
|
resolve: async ({ input, ctx }) => {
|
||||||
return await ctx.authUserService.create({
|
return await ctx.authUserService.create({
|
||||||
|
name: input.name,
|
||||||
email: input.email,
|
email: input.email,
|
||||||
password: input.password,
|
password: input.password,
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class AuthUserService implements IAuthUserService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: Prisma.AuthUserCreateInput): Promise<AuthUser> {
|
async create(data: Prisma.AuthUserCreateInput) {
|
||||||
const user = await this.prisma.authUser.create({ data: { ...data } })
|
const user = await this.prisma.authUser.create({ data: { ...data } })
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
|
@ -565,7 +565,7 @@ model PlanMilestone {
|
||||||
@@map("plan_milestone")
|
@@map("plan_milestone")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next Auth Models
|
// NextAuth Models
|
||||||
model AuthAccount {
|
model AuthAccount {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String @map("user_id")
|
userId String @map("user_id")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue