Skip to content

Commit

Permalink
Merge pull request #49 from Mango-Entertainment/zod-integration
Browse files Browse the repository at this point in the history
Zod integration
  • Loading branch information
AlexVCS authored Jan 3, 2024
2 parents a855404 + 65589d3 commit d76a3cf
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 94 deletions.
46 changes: 46 additions & 0 deletions app/auth/confirm/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
const {searchParams} = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = searchParams.get('next') ?? '/'
const redirectTo = request.nextUrl.clone()
redirectTo.pathname = next

if (token_hash && type) {
const cookieStore = cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
set(name: string, value: string, options: CookieOptions) {
cookieStore.set({ name, value, ...options })
},
remove(name: string, options: CookieOptions) {
cookieStore.delete({ name, ...options })
},
},
}
)

const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return NextResponse.redirect(redirectTo)
}
}

// return the user to an error page with some instructions
redirectTo.pathname = '/auth/auth-code-error'
return NextResponse.redirect(redirectTo)
}
1 change: 1 addition & 0 deletions app/lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {z} from 'zod'
import { TrendingData, RegularData } from '@/app/lib/definitions'
import postgres from 'postgres'

Expand Down
100 changes: 57 additions & 43 deletions app/lib/definitions.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,61 @@
export type Selection = {
id: string
title: string
category: string
year: number
rating: string
isTrending: boolean
isBookmarked: boolean
}
import { z } from 'zod'

export type TrendingThumbs = {
id: string
selectionId: string
small: string
large: string
}
const User = z.object({
id: z.string(),
email: z.string().email({ message: "Invalid email address" }),
password: z.string().min(7, { message: 'Must be 7 or more characters long' }),
})

export type RegularThumbs = {
id: string
selectionId: string
small: string
medium: string
large: string
}
const Selection = z.object({
id: z.string(),
title: z.string(),
category: z.string(),
year: z.number(),
rating: z.string(),
isTrending: z.boolean(),
isBookmarked: z.boolean(),
})

export type TrendingData = {
id: string
title: string
rating: string
year: number
category: string
is_bookmarked: boolean
large: string
small: string
}
const TrendingThumbs = z.object({
id: z.string(),
selectionId: z.string(),
small: z.string(),
large: z.string(),
})

export type RegularData = {
id: string
title: string
rating: string
year: number
category: string
is_bookmarked: boolean
large: string
small: string
medium: string
}
const RegularThumbs = z.object({
id: z.string(),
selectionId: z.string(),
small: z.string(),
medium: z.string(),
large: z.string(),
})

const TrendingData = z.object({
id: z.string(),
title: z.string(),
rating: z.string(),
year: z.number(),
category: z.string(),
is_bookmarked: z.boolean(),
large: z.string(),
small: z.string(),
})

const RegularData = z.object({
id: z.string(),
title: z.string(),
rating: z.string(),
year: z.number(),
category: z.string(),
is_bookmarked: z.boolean(),
large: z.string(),
small: z.string(),
medium: z.string(),
})

export type Selection = z.infer<typeof Selection>
export type TrendingThumbs = z.infer<typeof TrendingThumbs>
export type RegularThumbs = z.infer<typeof RegularThumbs>
export type TrendingData = z.infer<typeof TrendingData>
export type RegularData = z.infer<typeof RegularData>
27 changes: 27 additions & 0 deletions app/signup/routeTWO.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use server'
import { redirect } from 'next/navigation'
import { headers, cookies } from 'next/headers'
import { createClient } from '@/utils/supabase/server'


const signUpWithEmail = async ({ email, password }: {email: string, password: string}) => {
const origin = headers().get('origin')
const cookieStore = cookies()
const supabase = createClient(cookieStore)

const { error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${origin}/auth/callback`,
},
})

if (error) {
return redirect('/login?message=Could not authenticate user')
}

return redirect('/login?message=Check email to continue sign in process')
}

export { signUpWithEmail }
68 changes: 68 additions & 0 deletions app/ui/components/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client'
import Link from "next/link"
import Image from "next/image"
import { createClient } from '@/utils/supabase/client'
import { useState } from "react"

const Auth = () => {
const [user, setUser] = useState('')
const supabase = createClient()

const subscription = supabase.auth.onAuthStateChange(async (event, session) => {
console.log(event, session)

if (event === 'INITIAL_SESSION') {
// handle initial session
const { data, error } = await supabase.auth.getSession()
} else if (event === 'SIGNED_IN') {
// handle sign in event
// set user
const { data, error } = await supabase.auth.getUser()
if(!error?.status) {
setUser(data.user?.email || '')
}
} else if (event === 'SIGNED_OUT') {
// handle sign out event
// unset user
setUser('')
} else if (event === 'PASSWORD_RECOVERY') {
// handle password recovery event
// supabase.auth.resetPasswordForEmail()
} else if (event === 'TOKEN_REFRESHED') {
// handle token refreshed event
} else if (event === 'USER_UPDATED') {
// handle user updated event
}
})

// call unsubscribe to remove the callback
// subscription.unsubscribe()

if(user === ''){
return (
<div className="bg-entertainment-red text-entertainment-pure-white text-center w-14 h-7 mr-4 rounded-md md:h-8 md:mr-6 lg:mr-0 lg:mb-8 flex align-center justify-center"
>
<Link
href="/login"
className="self-center"
>
Login
</Link>
</div>
)
}
return (
<div>
<Image
className="w-6 h-6 mr-4 border-2 rounded-full md:w-8 md:h-8 md:mr-6 lg:mr-0 lg:w-10 lg:h-10 lg:mb-8 border-entertainment-pure-white"
src="/image-avatar.png"
onClick={() => supabase.auth.signOut()}
alt="icon"
width={80}
height={80}
/>
</div>
)
}

export default Auth
18 changes: 9 additions & 9 deletions app/ui/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import HomeIcon from "@/app/ui/components/HomeIcon";
import TvIcon from "@/app/ui/components/TvIcon";
import BookmarkIcon from "@/app/ui/components/BookmarkIcon";
import MovieIcon from "@/app/ui/components/MovieIcon";
import Auth from "@/app/ui/components/Auth";


const Navbar = () => {


return (
<div className="grid grid-cols-3 lg:grid-cols-1 lg:h-screen lg:grid-rows-[2fr_3fr_9fr] lg:place-items-start bg-entertainment-semi-dark-blue items-center md:mx-6 md:w-auto md:mt-6 md:rounded-xl lg:max-h-[960px] h-auto w-full lg:mx-0 lg:justify-self-center lg:w-24">
<div className="w-6 h-5 my-4 ml-4 lg:ml-0 md:ml-6 md:my-6 md:w-8 md:h-6 lg:mt-8 lg:justify-self-center">
<Image src="/logo.svg" alt="icon" width={32} height={25} />
<Link href="/" className="w-4 md:w-5">
<Image src="/logo.svg" alt="icon" width={32} height={25} />
</Link>
</div>
<div className="flex justify-between lg:flex-col lg:justify-self-center lg:h-full lg:w-5">
<Link href="/" className="w-4 md:w-5">
Expand All @@ -26,16 +32,10 @@ const Navbar = () => {
</Link>
</div>
<div className="flex justify-end lg:justify-self-center lg:self-end">
<Image
className="w-6 h-6 mr-4 border-2 rounded-full md:w-8 md:h-8 md:mr-6 lg:mr-0 lg:w-10 lg:h-10 lg:mb-8 border-entertainment-pure-white"
src="/image-avatar.png"
alt="icon"
width={80}
height={80}
/>
<Auth />
</div>
</div>
);
)
};

export default Navbar;
42 changes: 31 additions & 11 deletions app/ui/login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,36 @@
import Image from "next/image";
import Link from "next/link";
import {useRouter} from "next/navigation";
import {useState, FormEvent, ChangeEvent} from "react";
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from "react-hook-form";
import { createClient } from '@/utils/supabase/client'


const FormFieldsSchema = z
.object({
email: z.string().email(),
password: z
.string()
.min(7, { message: 'Must be at least 7 characters long' }),
})

type FormFields = z.infer<typeof FormFieldsSchema>

const Login = () => {
const [email, setEmail] = useState<string>("");
const [passwordOne, setPasswordOne] = useState<string>("");
const router = useRouter();
const [error, setError] = useState<string | null>(null);
const { register, handleSubmit, formState: { errors },
} = useForm<FormFields>({resolver: zodResolver(FormFieldsSchema)})

const supabase = createClient()

const loginWithEmail = async ({email, password}: {email: string, password: string}) => {
const res = await supabase.auth.signInWithPassword({
email,
password
})
if (res.data.user?.email) router.push('/')
}

return (
<div className="justify-center mt-12 grid justify-items-center md:mt-20">
Expand All @@ -26,25 +48,23 @@ const Login = () => {
<h1 className="mb-6 text-3xl font-light text-entertainment-pure-white">
Login
</h1>
<form>
<form onSubmit={handleSubmit((d)=> loginWithEmail({email: d.email, password: d.password}))}>
<input
className="block w-full pb-4 pl-4 mb-3 text-sm font-light bg-transparent border-0 border-b-2 h-37 border-entertainment-greyish-blue text-entertainment-pure-white caret-entertainment-red focus:border-entertainment-pure-white"
type="email"
name="email"
value={email}
{...register('email')}
placeholder="Email address"
onChange={(event) => setEmail(event.target.value)}
required
/>
<span>{errors.email?.message}</span>
<input
className="block w-full pb-4 pl-4 mb-3 text-sm font-light bg-transparent border-0 border-b-2 h-37 border-entertainment-greyish-blue text-entertainment-pure-white caret-entertainment-red focus:border-entertainment-pure-white"
type="password"
placeholder="Password"
name="passwordOne"
value={passwordOne}
onChange={(event) => setPasswordOne(event.target.value)}
{...register('password')}
required
/>
<span>{errors.password?.message}</span>
<button
className="w-full h-12 mb-6 text-sm font-light text-entertainment-pure-white hover:text-entertainment-dark-blue hover:bg-entertainment-pure-white bg-entertainment-red rounded-md"
type="submit"
Expand Down
Loading

0 comments on commit d76a3cf

Please sign in to comment.