Skip to content

Commit

Permalink
Convert to app router (#39)
Browse files Browse the repository at this point in the history
* Move homepage to app

* Move dashboard to app

Adds LoginLinks component

* Update to nextjs 14.0.3

* switch to useParams

* Colocate components

* Move to (app) grouped folder

* Move to (auth) grouped folder

create not-found

* Fix import

* Use existing export syntax

* Fix syntax error

* Fix error with loading Nunito font

* Drop @Header layout file

* Move header layout back to main layout

* Update global.css

* Colocate DashboardLayout

* Add @Header back the correct way

* remove unnecessary DashboardLayout

* Add loading to avoid page flicker

* wip

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
allenjd3 and taylorotwell authored Dec 23, 2023
1 parent 81ce4ca commit 52128f8
Show file tree
Hide file tree
Showing 32 changed files with 2,500 additions and 1,499 deletions.
2,704 changes: 1,860 additions & 844 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@headlessui/react": "^1.4.2",
"axios": "^0.21.1",
"next": "^13.0.3",
"next": "^14.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"swr": "^1.3.0"
Expand All @@ -25,7 +25,7 @@
"autoprefixer": "^10.4.2",
"babel-eslint": "^10.1.0",
"eslint": "^8.27.0",
"eslint-config-next": "13.0.3",
"eslint-config-next": "^14.0.3",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-prettier": "^3.3.0",
"postcss": "^8.4.6",
Expand Down
9 changes: 9 additions & 0 deletions src/app/(app)/@header/dashboard/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Header = () => {
return (
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Dashboard
</h2>
)
}

export default Header
9 changes: 9 additions & 0 deletions src/app/(app)/@header/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Header = () => {
return (
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Dashboard
</h2>
)
}

export default Header
9 changes: 9 additions & 0 deletions src/app/(app)/Loading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const Loading = () => {
return (
<div className="flex min-h-screen w-full items-center justify-center bg-gray-100">
Loading...
</div>
)
}

export default Loading
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ResponsiveNavLink, {
} from '@/components/ResponsiveNavLink'
import { DropdownButton } from '@/components/DropdownLink'
import { useAuth } from '@/hooks/auth'
import { useRouter } from 'next/router'
import { useRouter } from 'next/navigation'
import { useState } from 'react'

const Navigation = ({ user }) => {
Expand Down
19 changes: 19 additions & 0 deletions src/app/(app)/dashboard/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const metadata = {
title: 'Laravel - Dashboard',
}

const Dashboard = () => {
return (
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div className="p-6 bg-white border-b border-gray-200">
You're logged in!
</div>
</div>
</div>
</div>
)
}

export default Dashboard
13 changes: 9 additions & 4 deletions src/components/Layouts/AppLayout.js → src/app/(app)/layout.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import Navigation from '@/components/Layouts/Navigation'
'use client'

import { useAuth } from '@/hooks/auth'
import Navigation from '@/app/(app)/Navigation'
import Loading from '@/app/(app)/Loading'

const AppLayout = ({ header, children }) => {
const AppLayout = ({ children, header }) => {
const { user } = useAuth({ middleware: 'auth' })

if (!user) {
return <Loading />
}

return (
<div className="min-h-screen bg-gray-100">
<Navigation user={user} />

{/* Page Heading */}
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{header}
</div>
</header>

{/* Page Content */}
<main>{children}</main>
</div>
)
Expand Down
File renamed without changes.
File renamed without changes.
64 changes: 64 additions & 0 deletions src/app/(auth)/forgot-password/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client'

import Button from '@/components/Button'
import Input from '@/components/Input'
import InputError from '@/components/InputError'
import Label from '@/components/Label'
import { useAuth } from '@/hooks/auth'
import { useState } from 'react'
import AuthSessionStatus from '@/app/(auth)/AuthSessionStatus'

const Page = () => {
const { forgotPassword } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/dashboard',
})

const [email, setEmail] = useState('')
const [errors, setErrors] = useState([])
const [status, setStatus] = useState(null)

const submitForm = event => {
event.preventDefault()

forgotPassword({ email, setErrors, setStatus })
}

return (
<>
<div className="mb-4 text-sm text-gray-600">
Forgot your password? No problem. Just let us know your email
email address and we will email you a password reset link that
that will allow you to choose a new one.
</div>

{/* Session Status */}
<AuthSessionStatus className="mb-4" status={status} />

<form onSubmit={submitForm}>
{/* Email Address */}
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
name="email"
value={email}
className="block mt-1 w-full"
onChange={event => setEmail(event.target.value)}
required
autoFocus
/>

<InputError messages={errors.email} className="mt-2" />
</div>

<div className="flex items-center justify-end mt-4">
<Button>Email Password Reset Link</Button>
</div>
</form>
</>
)
}

export default Page
26 changes: 26 additions & 0 deletions src/app/(auth)/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Link from 'next/link'
import AuthCard from '@/app/(auth)/AuthCard'
import ApplicationLogo from '@/components/ApplicationLogo'

export const metadata = {
title: 'Laravel',
}

const Layout = ({ children }) => {
return (
<div>
<div className="font-sans text-gray-900 antialiased">
<AuthCard
logo={
<Link href="/">
<ApplicationLogo className="w-20 h-20 fill-current text-gray-500" />
</Link>
}>
{children}
</AuthCard>
</div>
</div>
)
}

export default Layout
123 changes: 123 additions & 0 deletions src/app/(auth)/login/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use client'

import Button from '@/components/Button'
import Input from '@/components/Input'
import InputError from '@/components/InputError'
import Label from '@/components/Label'
import Link from 'next/link'
import { useAuth } from '@/hooks/auth'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import AuthSessionStatus from '@/app/(auth)/AuthSessionStatus'

const Login = () => {
const router = useRouter()

const { login } = useAuth({
middleware: 'guest',
redirectIfAuthenticated: '/dashboard',
})

const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [shouldRemember, setShouldRemember] = useState(false)
const [errors, setErrors] = useState([])
const [status, setStatus] = useState(null)

useEffect(() => {
if (router.reset?.length > 0 && errors.length === 0) {
setStatus(atob(router.reset))
} else {
setStatus(null)
}
})

const submitForm = async event => {
event.preventDefault()

login({
email,
password,
remember: shouldRemember,
setErrors,
setStatus,
})
}

return (
<>
<AuthSessionStatus className="mb-4" status={status} />
<form onSubmit={submitForm}>
{/* Email Address */}
<div>
<Label htmlFor="email">Email</Label>

<Input
id="email"
type="email"
value={email}
className="block mt-1 w-full"
onChange={event => setEmail(event.target.value)}
required
autoFocus
/>

<InputError messages={errors.email} className="mt-2" />
</div>

{/* Password */}
<div className="mt-4">
<Label htmlFor="password">Password</Label>

<Input
id="password"
type="password"
value={password}
className="block mt-1 w-full"
onChange={event => setPassword(event.target.value)}
required
autoComplete="current-password"
/>

<InputError
messages={errors.password}
className="mt-2"
/>
</div>

{/* Remember Me */}
<div className="block mt-4">
<label
htmlFor="remember_me"
className="inline-flex items-center">
<input
id="remember_me"
type="checkbox"
name="remember"
className="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
onChange={event =>
setShouldRemember(event.target.checked)
}
/>

<span className="ml-2 text-sm text-gray-600">
Remember me
</span>
</label>
</div>

<div className="flex items-center justify-end mt-4">
<Link
href="/forgot-password"
className="underline text-sm text-gray-600 hover:text-gray-900">
Forgot your password?
</Link>

<Button className="ml-3">Login</Button>
</div>
</form>
</>
)
}

export default Login
Loading

0 comments on commit 52128f8

Please sign in to comment.