Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions app/auth/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type NextRequest, NextResponse } from 'next/server'

import { createClient } from '@/libs/server'

export async function GET(request: NextRequest) {
const reqUrl = new URL(request.url)
const { searchParams } = reqUrl
const code = searchParams.get('code')
const next = reqUrl.origin

const redirectTo = request.nextUrl.clone()
redirectTo.pathname = next
redirectTo.searchParams.delete('code')

if (code) {
const supabase = createClient()

const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
redirectTo.searchParams.delete('code')

return NextResponse.redirect(reqUrl.origin)
}
}

// return the user to an error page with some instructions
redirectTo.pathname = '/error'

return NextResponse.redirect(reqUrl.origin)
}
3 changes: 3 additions & 0 deletions app/error/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ErrorPage() {
return <p>Sorry, something went wrong</p>
}
38 changes: 18 additions & 20 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Menu from '@/components/menu'
import { Metadata } from 'next'
import { Session } from 'next-auth'
import { headers } from 'next/dist/client/components/headers'
import ClientSessionProvider from '../components/auth/session-provider'
import { createClient } from '@/libs/server'
import '../global.css'

export const metadata: Metadata = {
Expand All @@ -15,31 +13,31 @@ export interface DashboardLayoutProps {
children: React.ReactNode
}

export async function getSession(cookie: string): Promise<Session | null> {
const response = await fetch(`${process.env.NEXT_PUBLIC_NEXTAUTH_URL}/api/auth/session`, {
headers: { cookie },
})
export default async function Layout({ children }: { children: React.ReactNode }) {
const supabase = createClient()
let _user
let _error

if (!response?.ok) {
return null
}
const { data: session, error: sessionError } = await supabase.auth.getSession()

const session = await response.json()
if (session.session) {
const { data: user, error } = await supabase.auth.getUser()
_user = user
_error = error
}

return Object.keys(session).length > 0 ? session : null
}
if (_error || sessionError) {
console.error(_error || sessionError)

export default async function Layout({ children }: { children: React.ReactNode; props?: any }) {
const session = await getSession(headers().get('cookie') ?? '')
return null
}

return (
<html>
<head />
<body className='bg-[#081113] text-gray-300'>
<ClientSessionProvider session={session}>
<Menu />
<div className='container grid mx-auto max-w-5xl h-screen p-4'>{children}</div>
</ClientSessionProvider>
<body className='bg-[#081113] text-gray-300' cz-shortcut-listen='true'>
<Menu user={_user} />
<div className='container grid mx-auto max-w-5xl h-screen p-4'>{children}</div>
</body>
</html>
)
Expand Down
13 changes: 0 additions & 13 deletions components/auth/auth-context.tsx

This file was deleted.

7 changes: 0 additions & 7 deletions components/auth/session-provider.tsx

This file was deleted.

14 changes: 11 additions & 3 deletions components/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import Link from 'next/link'
import User from './user'
import Login from './user'
import { Database } from '@/types/database.types'

export type User = Database['public']['Tables']['users']['Row']
interface UserProps {
user: User | null
}

export const Menu = (props: UserProps) => {
const { user } = props

export const Menu = () => {
return (
<div className='text-white p-4 border-b border-white border-opacity-10 border-dotted'>
<div className='container flex flex-row justify-between mx-auto max-w-5xl'>
<Link href='/'>
<span>🍃 Nextwind</span>
</Link>
<User />
<Login user={user} />
</div>
</div>
)
Expand Down
38 changes: 22 additions & 16 deletions components/user.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
/* eslint-disable @next/next/no-img-element */
'use client'
import { signIn, signOut, useSession } from 'next-auth/react'

const User = () => {
const { data, status } = useSession()
import Image from 'next/image'
import { DiscordLogin, handleLogout } from '@/libs/actions'

const Login = (props) => {
const { user: data } = props
const user = data?.user

return (
<div className='flex items-center gap-2'>
{status === 'loading' && <div className='w-8 h-8 rounded-full bg-purple-50 border-2' />}

{status === 'unauthenticated' && (
<>
<button onClick={() => signIn()}>Sign In</button>
<div className='w-8 h-8 rounded-full border-2' />
</>
{Boolean(!user) && (
<div className='flex gap-3'>
<button onClick={() => DiscordLogin()}>Sign In with Discord</button>
</div>
)}

{status === 'authenticated' && (
{Boolean(user) && (
<>
<p className='text-sm font-medium'>Welcome, {data?.user.name}</p>
{data?.user.image ? (
<img onClick={() => signOut()} className='w-8 h-8 rounded-full border-2' src={data.user?.image} alt='' />
<p className='text-sm font-medium'>Welcome, {user?.user_metadata?.name}</p>
{user?.user_metadata?.avatar_url ? (
<Image
width={32}
height={32}
onClick={() => handleLogout()}
className='rounded-full border-2 cursor-pointer'
src={user?.user_metadata?.avatar_url}
alt='avatar'
/>
) : (
<div className='w-8 h-8 rounded-full border-2' />
)}
Expand All @@ -30,4 +36,4 @@ const User = () => {
)
}

export default User
export default Login
6 changes: 3 additions & 3 deletions content/post.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ author: 'Codibrie'
</a>
</h1>

<p className="text-gray-200 leading-relaxed text-base mb-5">
<span className="text-gray-200 leading-relaxed text-base">
Discover a robust full-stack web development starter project built with TypeScript, Next.JS, Supabase, PostgreSQL, TailwindCSS, and Zustand! This project offers a powerful foundation for building modern web applications with ease, providing developers with optimized performance, easy-to-use APIs, customizable styling, and simplified state management.
</p>
</span>

<div className="flex justify-between flex-wrap mb-5">
<div className="flex justify-between flex-wrap my-5">
<a href="https://github.com/pmndrs/zustand" className="text-blue-500 mr-4 mb-2">Zustand</a>
<a href="https://tailwindcss.com/" className="text-blue-500 mr-4 mb-2">TailwindCSS</a>
<a href="https://next-auth.js.org/" className="text-blue-500 mr-4 mb-2">Next-Auth</a>
Expand Down
42 changes: 42 additions & 0 deletions libs/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use server'

import { redirect } from 'next/navigation'

import { createClient } from './server'

const getURL = () => {
let url = process?.env?.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000/'

url = url.includes('http:') ? url : `https://${url}`

url = url.charAt(url.length - 1) === '/' ? url : `${url}/`

return url
}

export async function DiscordLogin() {
const supabase = createClient()

const { error, data } = await supabase.auth.signInWithOAuth({
provider: 'discord',
options: {
redirectTo: `${getURL()}auth/callback`,
},
})

if (error) {
console.log(error)
redirect('/error')
} else {
redirect(data.url)
}
}

export const handleLogout = async () => {
const supabase = createClient()
const { error } = await supabase.auth.signOut()

if (!error) {
redirect('/')
}
}
5 changes: 5 additions & 0 deletions libs/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createBrowserClient } from '@supabase/ssr'

export function createClient() {
return createBrowserClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
}
60 changes: 60 additions & 0 deletions libs/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function updateSession(request: NextRequest) {
let response = NextResponse.next({
request: {
headers: request.headers,
},
})

const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value
},
set(name: string, value: string, options: CookieOptions) {
request.cookies.set({
name,
value,
...options,
})
response = NextResponse.next({
request: {
headers: request.headers,
},
})
response.cookies.set({
name,
value,
...options,
})
},
remove(name: string, options: CookieOptions) {
request.cookies.set({
name,
value: '',
...options,
})
response = NextResponse.next({
request: {
headers: request.headers,
},
})
response.cookies.set({
name,
value: '',
...options,
})
},
},
}
)

await supabase.auth.getUser()

return response
}
24 changes: 24 additions & 0 deletions libs/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers'

export function createClient() {
const cookieStore = cookies()

return 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) {
try {
cookieStore.set({ name, value, ...options })
} catch (error) {}
},
remove(name: string, options: CookieOptions) {
try {
cookieStore.set({ name, value: '', ...options })
} catch (error) {}
},
},
})
}
11 changes: 0 additions & 11 deletions libs/supabase.ts

This file was deleted.

10 changes: 10 additions & 0 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NextRequest } from 'next/server'
import { updateSession } from '@/libs/middleware'

export async function middleware(request: NextRequest) {
return await updateSession(request)
}

export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
}
1 change: 0 additions & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
6 changes: 4 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ const { withContentlayer } = require('next-contentlayer')

module.exports = withContentlayer({
reactStrictMode: true,
experimental: { serverActions: true },
// output: 'standalone', # Enable for Docker Deployments
images: {
domains: ['upload.wikimedia.org'],
remotePatterns: [
{ protocol: 'https', hostname: 'upload.wikimedia.org', port: '', pathname: '**' },
{ protocol: 'https', hostname: 'cdn.discordapp.com', port: '', pathname: '**' },
],
},
})
Loading