Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
36 changes: 4 additions & 32 deletions apps/blog-next/app/auth-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client'

import { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useAuth } from '@holo-js/auth/next/client'
import { logoutAction } from './logout/actions'

const linkStyle = {
color: '#cbd5e1',
Expand All @@ -25,37 +24,8 @@ const logoutFormStyle = {

export function AuthNav() {
const auth = useAuth()
const router = useRouter()
const [isLoggingOut, setIsLoggingOut] = useState(false)
const displayName = auth.user?.name ?? auth.user?.email ?? 'Account'

async function logout() {
if (isLoggingOut) {
return
}

setIsLoggingOut(true)
try {
const response = await fetch('/api/logout', { method: 'POST' })
if (!response.ok) {
console.warn('Logout failed.', { status: response.status })
return
}

try {
await auth.refreshUser()
} catch (error) {
console.warn('Auth refresh failed after logout.', error)
}

router.replace('/')
} catch (error) {
console.warn('Logout failed.', error)
} finally {
setIsLoggingOut(false)
}
}

if (!auth.authenticated) {
return (
<>
Expand All @@ -68,7 +38,9 @@ export function AuthNav() {
return (
<>
<span style={{ color: '#e5eef8' }}>{displayName}</span>
<button type="button" disabled={isLoggingOut} onClick={logout} style={logoutButtonStyle}>Logout</button>
<form action={logoutAction} style={logoutFormStyle}>
<button type="submit" style={logoutButtonStyle}>Logout</button>
</form>
{auth.provider === 'workos' && (
<form action="/api/auth/workos/logout" method="post" style={logoutFormStyle}>
<button type="submit" style={logoutButtonStyle}>Logout from WorkOS</button>
Expand Down
34 changes: 34 additions & 0 deletions apps/blog-next/app/login/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use server'

import { login } from '@holo-js/auth'
import { validate } from '@holo-js/forms'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

import { loginForm } from '@/lib/schemas/auth'

export async function loginAction(formData: FormData) {
const submission = await validate(formData, loginForm, {
csrf: true,
throttle: 'login',
})

if (!submission.valid) {
return submission.fail()
}

const { data: session, error } = await login(submission.data)
if (error) {
return submission.fail({
status: error.status,
errors: error.fields,
})
}

const redirectTo = session.emailVerificationRequired
? session.emailVerificationRoute ?? '/verify-email'
: '/admin'

revalidatePath('/', 'layout')
redirect(redirectTo)
}
26 changes: 3 additions & 23 deletions apps/blog-next/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
'use client'

import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useAuth } from '@holo-js/auth/next/client'
import { useForm } from '@holo-js/adapter-next/client'
import { loginForm } from '@/lib/schemas/auth'
import { loginAction } from './actions'

const panelStyle = {
display: 'grid',
Expand All @@ -17,24 +16,12 @@ const panelStyle = {
} satisfies React.CSSProperties

export default function LoginPage() {
const router = useRouter()
const auth = useAuth()
const form = useForm(loginForm, {
csrf: true,
validateOn: 'blur',
initialValues: { email: '', password: '', remember: false },
async submitter({ formData }) {
const response = await fetch('/api/login', { method: 'POST', body: formData })
const submission = await response.json()
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
try {
await auth.refreshUser()
} catch (error) {
console.warn('Auth refresh failed after login.', error)
}
router.replace(submission.data.redirectTo)
}
return submission
return await loginAction(formData)
},
})
const formError = form.errors.first('_root')
Expand Down Expand Up @@ -95,15 +82,8 @@ export default function LoginPage() {
</button>
</form>

{form.lastSubmission?.ok === true ? (
<div style={{ color: '#86efac' }}>
<p style={{ marginTop: 0 }}>Signed in successfully.</p>
<Link href="/admin" style={{ color: '#7dd3fc' }}>Continue to admin</Link>
</div>
) : null}

<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
<Link href="/register" style={{ color: '#7dd3fc' }}>Create account</Link>
<Link href="/register" style={{ color: '#7dd3fc' }}>Create account</Link>
<Link href="/forgot-password" style={{ color: '#7dd3fc' }}>Forgot password?</Link>
</div>
</section>
Expand Down
11 changes: 11 additions & 0 deletions apps/blog-next/app/logout/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use server'

import { logout } from '@holo-js/auth'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function logoutAction() {
await logout()
revalidatePath('/', 'layout')
redirect('/')
}
35 changes: 35 additions & 0 deletions apps/blog-next/app/register/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use server'

import { loginUsing, register } from '@holo-js/auth'
import { validate } from '@holo-js/forms'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

import { registerForm } from '@/lib/schemas/auth'

export async function registerAction(formData: FormData) {
const submission = await validate(formData, registerForm, {
csrf: true,
throttle: 'register',
})

if (!submission.valid) {
return submission.fail()
}

const { data: created, error } = await register(submission.data)
if (error) {
return submission.fail({
status: error.status,
errors: error.fields,
})
}

const session = await loginUsing(created)
const redirectTo = session.emailVerificationRequired
? session.emailVerificationRoute ?? '/verify-email'
: '/admin'

revalidatePath('/', 'layout')
redirect(redirectTo)
}
17 changes: 2 additions & 15 deletions apps/blog-next/app/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use client'

import Link from 'next/link'
import { useRouter } from 'next/navigation'

import { useForm } from '@holo-js/adapter-next/client'

import { registerForm } from '@/lib/schemas/auth'
import { registerAction } from './actions'

const panelStyle = {
display: 'grid',
Expand All @@ -18,18 +18,12 @@ const panelStyle = {
} satisfies React.CSSProperties

export default function RegisterPage() {
const router = useRouter()
const form = useForm(registerForm, {
csrf: true,
validateOn: 'blur',
initialValues: { name: '', email: '', password: '', passwordConfirmation: '' },
async submitter({ formData }) {
const response = await fetch('/api/register', { method: 'POST', body: formData })
const submission = await response.json()
if (submission?.ok === true) {
router.replace('/login')
}
return submission
return await registerAction(formData)
},
})

Expand Down Expand Up @@ -93,13 +87,6 @@ export default function RegisterPage() {
</button>
</form>

{form.lastSubmission?.ok === true ? (
<div style={{ color: '#86efac', display: 'grid', gap: '0.5rem' }}>
<p style={{ margin: 0 }}>Account created. Check your inbox to verify your email address.</p>
<Link href="/login" style={{ color: '#7dd3fc' }}>Return to sign in</Link>
</div>
) : null}

<Link href="/login" style={{ color: '#7dd3fc' }}>Already have an account?</Link>
<a href="/api/auth/workos/register" style={{ color: '#7dd3fc' }}>Register with WorkOS</a>
<a href="/api/auth/clerk/register" style={{ color: '#7dd3fc' }}>Register with Clerk</a>
Expand Down
33 changes: 33 additions & 0 deletions apps/blog-next/app/super-admin/login/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use server'

import auth from '@holo-js/auth'
import { validate } from '@holo-js/forms'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

import { loginForm } from '@/lib/schemas/auth'

export async function superAdminLoginAction(formData: FormData) {
const submission = await validate(formData, loginForm, {
throttle: 'login',
})

if (!submission.valid) {
return submission.fail()
}

const { data: session, error } = await auth.guard('admin').login(submission.data)
if (error) {
return submission.fail({
status: error.status,
errors: error.fields,
})
}

const redirectTo = session.emailVerificationRequired
? session.emailVerificationRoute ?? '/verify-email'
: '/super-admin'

revalidatePath('/', 'layout')
redirect(redirectTo)
}
22 changes: 2 additions & 20 deletions apps/blog-next/app/super-admin/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client'

import { useRouter } from 'next/navigation'
import { useAuth } from '@holo-js/auth/next/client'
import { useForm } from '@holo-js/adapter-next/client'
import { loginForm } from '@/lib/schemas/auth'
import { superAdminLoginAction } from './actions'

const panelStyle = {
display: 'grid',
Expand All @@ -16,23 +15,11 @@ const panelStyle = {
} satisfies React.CSSProperties

export default function SuperAdminLoginPage() {
const router = useRouter()
const auth = useAuth({ guard: 'admin' })
const form = useForm(loginForm, {
validateOn: 'blur',
initialValues: { email: '', password: '', remember: false },
async submitter({ formData }) {
const response = await fetch('/api/super-admin/login', { method: 'POST', body: formData })
const submission = await response.json()
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
try {
await auth.refreshUser()
} catch (error) {
console.warn('Super admin auth refresh failed after login.', error)
}
router.replace(submission.data.redirectTo)
}
return submission
return await superAdminLoginAction(formData)
},
})
const formError = form.errors.first('_root')
Expand Down Expand Up @@ -86,11 +73,6 @@ export default function SuperAdminLoginPage() {
</button>
</form>

{form.lastSubmission?.ok === true ? (
<div style={{ color: '#86efac' }}>
<p style={{ margin: 0 }}>Signed in as super admin.</p>
</div>
) : null}
</section>
)
}
43 changes: 4 additions & 39 deletions apps/blog-next/app/super-admin/logout-button.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
'use client'

import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { useAuth } from '@holo-js/auth/next/client'
import { superAdminLogoutAction } from './logout/actions'

export function SuperAdminLogoutButton() {
const router = useRouter()
const auth = useAuth({ guard: 'admin' })
const [isLoggingOut, setIsLoggingOut] = useState(false)

async function logout() {
if (isLoggingOut) {
return
}

setIsLoggingOut(true)
try {
const response = await fetch('/api/super-admin/logout', { method: 'POST' })
if (!response.ok) {
console.warn('Super admin logout failed.', { status: response.status })
return
}

try {
await auth.refreshUser()
} catch (error) {
console.warn('Super admin auth refresh failed after logout.', error)
}

router.replace('/super-admin/login')
} catch (error) {
console.warn('Super admin logout failed.', error)
} finally {
setIsLoggingOut(false)
}
}

return (
<button type="button" onClick={logout} disabled={isLoggingOut}>
{isLoggingOut ? 'Signing out...' : 'Sign out of super admin'}
</button>
<form action={superAdminLogoutAction}>
<button type="submit">Sign out of super admin</button>
</form>
)
}
11 changes: 11 additions & 0 deletions apps/blog-next/app/super-admin/logout/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use server'

import auth from '@holo-js/auth'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'

export async function superAdminLogoutAction() {
await auth.guard('admin').logout()
revalidatePath('/', 'layout')
redirect('/super-admin/login')
}
Loading