├── .env.example ├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── src ├── app │ ├── (authenticated) │ │ ├── dashboard │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── (guest) │ │ ├── forgot-password │ │ │ └── page.tsx │ │ ├── login │ │ │ └── page.tsx │ │ ├── password-reset │ │ │ └── [token] │ │ │ │ └── page.tsx │ │ ├── register │ │ │ └── page.tsx │ │ └── verify-email │ │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── not-found.tsx │ └── page.tsx ├── components │ ├── ApplicationLogo.tsx │ ├── AuthCard.tsx │ ├── AuthSessionStatus.tsx │ ├── Dropdown.tsx │ ├── DropdownLink.tsx │ ├── Layouts │ │ └── Navigation.tsx │ ├── NavLink.tsx │ └── ResponsiveNavLink.tsx ├── hooks │ └── auth.ts ├── lib │ └── axios.ts └── types │ └── User.ts ├── tailwind.config.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_BACKEND_URL= 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "jsxBracketSameLine": true, 4 | "semi": false, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Breeze - Next.js v14 Edition with TypeScript 🏝️ 2 | ## Introduction 3 | 4 | --- 5 | **This repository is a refactor of [breeze-next](https://github.com/laravel/breeze-next) by changing programing language from [JavaScript](https://www.javascript.com/) to [TypeScript](https://www.typescriptlang.org/)** 6 | 7 | **This used NextJS Version 14** 8 | 9 | ### Little extras 10 | * Add Formik Validation 11 | * Next Features inside app folder 12 | * Add Route Group 13 | 14 | --- 15 | 16 | This repository is an implementation of the [Laravel Breeze](https://laravel.com/docs/starter-kits) application / authentication starter kit frontend in [Next.js](https://nextjs.org). All of the authentication boilerplate is already written for you - powered by [Laravel Sanctum](https://laravel.com/docs/sanctum), allowing you to quickly begin pairing your beautiful Next.js frontend with a powerful Laravel backend. 17 | 18 | ## Official Documentation 19 | 20 | ### Installation 21 | 22 | First, create a Next.js compatible Laravel backend by installing Laravel Breeze into a [fresh Laravel application](https://laravel.com/docs/installation) and installing Breeze's API scaffolding: 23 | 24 | ```bash 25 | # Create the Laravel application... 26 | laravel new next-backend 27 | 28 | cd next-backend 29 | 30 | # Install Breeze and dependencies... 31 | composer require laravel/breeze --dev 32 | 33 | php artisan breeze:install api 34 | ``` 35 | 36 | Next, ensure that your application's `APP_URL` and `FRONTEND_URL` environment variables are set to `http://localhost:8000` and `http://localhost:3000`, respectively. 37 | 38 | After defining the appropriate environment variables, you may serve the Laravel application using the `serve` Artisan command: 39 | 40 | ```bash 41 | # Serve the application... 42 | php artisan serve 43 | ``` 44 | 45 | Next, clone this repository and install its dependencies with `yarn install` or `npm install`. Then, copy the `.env.example` file to `.env.local` and supply the URL of your backend: 46 | 47 | ``` 48 | NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 49 | ``` 50 | 51 | Finally, run the application via `npm run dev`. The application will be available at `http://localhost:3000`: 52 | 53 | ``` 54 | npm run dev 55 | ``` 56 | 57 | ## License 58 | 59 | Laravel Breeze Next.js v14 is open-sourced software licensed under the [MIT license](LICENSE.md). 60 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-breeze-next-typescript", 3 | "version": "0.1.0", 4 | "private": true, 5 | "keywords": [ 6 | "laravel", 7 | "breeze", 8 | "laravel-breeze", 9 | "next", 10 | "typescript", 11 | "next-v14" 12 | ], 13 | "scripts": { 14 | "dev": "next dev", 15 | "build": "next build", 16 | "start": "next start", 17 | "lint": "next lint" 18 | }, 19 | "dependencies": { 20 | "@headlessui/react": "^1.7.17", 21 | "@tailwindcss/forms": "^0.5.7", 22 | "axios": "^1.6.2", 23 | "formik": "^2.4.5", 24 | "next": "14.0.3", 25 | "react": "^18", 26 | "react-dom": "^18", 27 | "swr": "^2.2.4", 28 | "yup": "^1.3.2" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20", 32 | "@types/react": "^18", 33 | "@types/react-dom": "^18", 34 | "autoprefixer": "^10.0.1", 35 | "eslint": "^8", 36 | "eslint-config-next": "14.0.3", 37 | "postcss": "^8", 38 | "tailwindcss": "^3.3.0", 39 | "typescript": "^5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/(authenticated)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const DashboardPage = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 | {`You're logged in!`} 10 |
11 |
12 |
13 |
14 | ) 15 | } 16 | 17 | export default DashboardPage 18 | -------------------------------------------------------------------------------- /src/app/(authenticated)/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { ReactNode } from 'react' 3 | import { useAuth } from '@/hooks/auth' 4 | import Navigation from '@/components/Layouts/Navigation' 5 | 6 | const AppLayout = ({ children }: { children: ReactNode }) => { 7 | const { user } = useAuth({ middleware: 'auth' }) 8 | 9 | return ( 10 |
11 | 12 | 13 | {/* Page Content */} 14 |
{children}
15 |
16 | ) 17 | } 18 | 19 | export default AppLayout 20 | -------------------------------------------------------------------------------- /src/app/(guest)/forgot-password/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { useState } from 'react' 3 | import * as Yup from 'yup' 4 | import Link from 'next/link' 5 | import axios, { AxiosError } from 'axios' 6 | import { ErrorMessage, Field, Form, Formik, FormikHelpers } from 'formik' 7 | 8 | import { useAuth } from '@/hooks/auth' 9 | import AuthCard from '@/components/AuthCard' 10 | import ApplicationLogo from '@/components/ApplicationLogo' 11 | import AuthSessionStatus from '@/components/AuthSessionStatus' 12 | 13 | interface FormValues { 14 | email: string 15 | } 16 | 17 | const ForgotPasswordPage = () => { 18 | const [status, setStatus] = useState('') 19 | 20 | const { forgotPassword } = useAuth({ 21 | middleware: 'guest', 22 | redirectIfAuthenticated: '/dashboard', 23 | }) 24 | 25 | const ForgotPasswordSchema = Yup.object().shape({ 26 | email: Yup.string() 27 | .email('Invalid email') 28 | .required('The email field is required.'), 29 | }) 30 | 31 | const submitForm = async ( 32 | values: FormValues, 33 | { setSubmitting, setErrors }: FormikHelpers, 34 | ): Promise => { 35 | try { 36 | const response = await forgotPassword(values) 37 | 38 | setStatus(response.data.status) 39 | } catch (error: Error | AxiosError | any) { 40 | setStatus('') 41 | if (axios.isAxiosError(error) && error.response?.status === 422) { 42 | setErrors(error.response?.data?.errors) 43 | } 44 | } finally { 45 | setSubmitting(false) 46 | } 47 | } 48 | 49 | return ( 50 | 53 | 54 | 55 | }> 56 |
57 | Forgot your password? No problem. Just let us know your email address 58 | and we will email you a password reset link that will allow you to 59 | choose a new one. 60 |
61 | 62 | 63 | 64 | 68 |
69 |
70 | 75 | 76 | 82 | 83 | 88 |
89 | 90 |
91 | 96 |
97 |
98 |
99 |
100 | ) 101 | } 102 | 103 | export default ForgotPasswordPage 104 | -------------------------------------------------------------------------------- /src/app/(guest)/login/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import Link from 'next/link' 3 | import * as Yup from 'yup' 4 | import { useSearchParams } from 'next/navigation' 5 | import axios, { AxiosError } from 'axios' 6 | import { ErrorMessage, Field, Form, Formik, FormikHelpers } from 'formik' 7 | 8 | import { useAuth } from '@/hooks/auth' 9 | import ApplicationLogo from '@/components/ApplicationLogo' 10 | import AuthCard from '@/components/AuthCard' 11 | import { useEffect, useState } from 'react' 12 | import AuthSessionStatus from '@/components/AuthSessionStatus' 13 | 14 | interface Values { 15 | email: string 16 | password: string 17 | remember: boolean 18 | } 19 | 20 | const LoginPage = () => { 21 | const searchParams = useSearchParams() 22 | const [status, setStatus] = useState('') 23 | 24 | const { login } = useAuth({ 25 | middleware: 'guest', 26 | redirectIfAuthenticated: '/dashboard', 27 | }) 28 | 29 | useEffect(() => { 30 | const resetToken = searchParams.get('reset') 31 | setStatus(resetToken ? atob(resetToken) : '') 32 | }, [searchParams]) 33 | 34 | const submitForm = async ( 35 | values: Values, 36 | { setSubmitting, setErrors }: FormikHelpers, 37 | ): Promise => { 38 | try { 39 | await login(values) 40 | } catch (error: Error | AxiosError | any) { 41 | if (axios.isAxiosError(error) && error.response?.status === 422) { 42 | setErrors(error.response?.data?.errors) 43 | } 44 | } finally { 45 | setSubmitting(false) 46 | setStatus('') 47 | } 48 | } 49 | 50 | const LoginSchema = Yup.object().shape({ 51 | email: Yup.string() 52 | .email('Invalid email') 53 | .required('The email field is required.'), 54 | password: Yup.string().required('The password field is required.'), 55 | }) 56 | 57 | return ( 58 | 61 | 62 | 63 | }> 64 | 65 | 66 | 70 |
71 |
72 | 77 | 78 | 84 | 85 | 90 |
91 | 92 |
93 | 98 | 99 | 105 | 106 | 111 |
112 | 113 |
114 | 125 |
126 | 127 |
128 | 131 | Forgot your password? 132 | 133 | 134 | 139 |
140 |
141 |
142 |
143 | ) 144 | } 145 | 146 | export default LoginPage 147 | -------------------------------------------------------------------------------- /src/app/(guest)/password-reset/[token]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import Link from 'next/link' 3 | import * as Yup from 'yup' 4 | import axios, { AxiosError } from 'axios' 5 | import { useSearchParams } from 'next/navigation' 6 | import { ErrorMessage, Field, Form, Formik, FormikHelpers } from 'formik' 7 | 8 | import { useAuth } from '@/hooks/auth' 9 | import AuthCard from '@/components/AuthCard' 10 | import ApplicationLogo from '@/components/ApplicationLogo' 11 | 12 | interface Values { 13 | email: string 14 | password: string 15 | password_confirmation: string 16 | } 17 | 18 | const PasswordResetPage = () => { 19 | const query = useSearchParams() 20 | const { resetPassword } = useAuth({ middleware: 'guest' }) 21 | 22 | const submitForm = async ( 23 | values: Values, 24 | { setSubmitting, setErrors }: FormikHelpers, 25 | ): Promise => { 26 | try { 27 | await resetPassword(values) 28 | } catch (error: Error | AxiosError | any) { 29 | if (axios.isAxiosError(error) && error.response?.status === 422) { 30 | setErrors(error.response?.data?.errors) 31 | } 32 | } finally { 33 | setSubmitting(false) 34 | } 35 | } 36 | 37 | const ForgotPasswordSchema = Yup.object().shape({ 38 | email: Yup.string() 39 | .email('Invalid email') 40 | .required('The email field is required.'), 41 | password: Yup.string().required('The password field is required.'), 42 | password_confirmation: Yup.string() 43 | .required('Please confirm password.') 44 | .oneOf([Yup.ref('password')], 'Your passwords do not match.'), 45 | }) 46 | 47 | return ( 48 | 51 | 52 | 53 | }> 54 | 62 |
63 |
64 | 69 | 70 | 77 | 78 | 83 |
84 | 85 |
86 | 91 | 92 | 98 | 99 | 104 |
105 | 106 |
107 | 112 | 113 | 119 | 120 | 125 |
126 | 127 |
128 | 133 |
134 |
135 |
136 |
137 | ) 138 | } 139 | 140 | export default PasswordResetPage 141 | -------------------------------------------------------------------------------- /src/app/(guest)/register/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import Link from 'next/link' 3 | import * as Yup from 'yup' 4 | import axios, { AxiosError } from 'axios' 5 | import { ErrorMessage, Field, Form, Formik, FormikHelpers } from 'formik' 6 | 7 | import { useAuth } from '@/hooks/auth' 8 | import ApplicationLogo from '@/components/ApplicationLogo' 9 | import AuthCard from '@/components/AuthCard' 10 | 11 | interface Values { 12 | name: string 13 | email: string 14 | password: string 15 | password_confirmation: string 16 | } 17 | 18 | const RegisterPage = () => { 19 | const { register } = useAuth({ 20 | middleware: 'guest', 21 | redirectIfAuthenticated: '/dashboard', 22 | }) 23 | 24 | const submitForm = async ( 25 | values: Values, 26 | { setSubmitting, setErrors }: FormikHelpers, 27 | ): Promise => { 28 | try { 29 | await register(values) 30 | } catch (error: Error | AxiosError | any) { 31 | if (axios.isAxiosError(error) && error.response?.status === 422) { 32 | setErrors(error.response?.data?.errors) 33 | } 34 | } finally { 35 | setSubmitting(false) 36 | } 37 | } 38 | 39 | const RegisterSchema = Yup.object().shape({ 40 | name: Yup.string().required('The name field is required.'), 41 | email: Yup.string() 42 | .email('Invalid email') 43 | .required('The email field is required.'), 44 | password: Yup.string().required('The password field is required.'), 45 | password_confirmation: Yup.string() 46 | .required('Please confirm password.') 47 | .oneOf([Yup.ref('password')], 'Your passwords do not match.'), 48 | }) 49 | 50 | return ( 51 | 54 | 55 | 56 | }> 57 | 66 |
67 |
68 | 73 | 74 | 79 | 80 | 85 |
86 | 87 |
88 | 93 | 94 | 100 | 101 | 106 |
107 | 108 |
109 | 114 | 115 | 121 | 122 | 127 |
128 | 129 |
130 | 135 | 136 | 142 | 143 | 148 |
149 | 150 |
151 | 154 | Already registered? 155 | 156 | 157 | 162 |
163 |
164 |
165 |
166 | ) 167 | } 168 | 169 | export default RegisterPage 170 | -------------------------------------------------------------------------------- /src/app/(guest)/verify-email/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import Link from 'next/link' 3 | import React, { useState } from 'react' 4 | 5 | import { useAuth } from '@/hooks/auth' 6 | import AuthCard from '@/components/AuthCard' 7 | import ApplicationLogo from '@/components/ApplicationLogo' 8 | 9 | const VerifyEmailPage = () => { 10 | const [status, setStatus] = useState('') 11 | 12 | const { logout, resendEmailVerification } = useAuth({ 13 | middleware: 'auth', 14 | redirectIfAuthenticated: '/dashboard', 15 | }) 16 | 17 | const onClickResend = () => { 18 | resendEmailVerification().then(response => setStatus(response.data.status)) 19 | } 20 | 21 | return ( 22 | 25 | 26 | 27 | }> 28 |
29 | Thanks for signing up! Before getting started, could you verify your 30 | email address by clicking on the link we just emailed to you? If you 31 | didn't receive the email, we will gladly send you another. 32 |
33 | 34 | {status === 'verification-link-sent' && ( 35 |
36 | A new verification link has been sent to the email address you 37 | provided during registration. 38 |
39 | )} 40 | 41 |
42 | 47 | 48 | 54 |
55 |
56 | ) 57 | } 58 | 59 | export default VerifyEmailPage 60 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Byandev/laravel-breeze-next-typescript/b4315642ddc83d66890e0ee8535efd5acbf15ab4/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Nunito } from 'next/font/google' 3 | import './globals.css' 4 | 5 | const nunito = Nunito({ subsets: ['latin'] }) 6 | 7 | export const metadata: Metadata = { 8 | title: 'Create Next App', 9 | description: 'Generated by create next app', 10 | } 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: { 15 | children: React.ReactNode 16 | }) { 17 | return ( 18 | 19 | 20 | {children} 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return ( 3 |
4 |
5 |
6 |
7 | 404 8 |
9 | 10 |
11 | Not Found 12 |
13 |
14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import Head from 'next/head' 4 | import Link from 'next/link' 5 | import { useAuth } from '@/hooks/auth' 6 | 7 | export default function Home() { 8 | const { user } = useAuth({ middleware: 'guest' }) 9 | 10 | return ( 11 | <> 12 | 13 | Laravel 14 | 15 | 16 |
17 |
18 | {user ? ( 19 | 22 | Dashboard 23 | 24 | ) : ( 25 | <> 26 | 27 | Login 28 | 29 | 30 | 33 | Register 34 | 35 | 36 | )} 37 |
38 | 39 |
40 |
41 | 46 | 47 | 48 | 49 | 50 |
51 | 52 |
53 |
54 |
55 |
56 | 64 | 65 | 66 | 67 | 74 |
75 | 76 |
77 |
78 | Laravel has wonderful, thorough documentation covering every 79 | aspect of the framework. Whether you are new to the 80 | framework or have previous experience with Laravel, we 81 | recommend reading all of the documentation from beginning to 82 | end. 83 |
84 |
85 |
86 | 87 |
88 |
89 | 97 | 98 | 99 | 100 | 101 | 108 |
109 | 110 |
111 |
112 | Laracasts offers thousands of video tutorials on Laravel, 113 | PHP, and JavaScript development. Check them out, see for 114 | yourself, and massively level up your development skills in 115 | the process. 116 |
117 |
118 |
119 | 120 |
121 |
122 | 130 | 131 | 132 | 133 | 140 |
141 | 142 |
143 |
144 | Laravel News is a community driven portal and newsletter 145 | aggregating all of the latest and most important news in the 146 | Laravel ecosystem, including new package releases and 147 | tutorials. 148 |
149 |
150 |
151 | 152 |
153 |
154 | 162 | 163 | 164 | 165 |
166 | Vibrant Ecosystem 167 |
168 |
169 | 170 |
171 |
172 | Laravel is robust library of first-party tools and 173 | libraries, such as{' '} 174 | 175 | Forge 176 | 177 | ,{' '} 178 | 179 | Vapor 180 | 181 | ,{' '} 182 | 183 | Nova 184 | 185 | , and{' '} 186 | 187 | Envoyer 188 | {' '} 189 | help you take your projects to the next level. Pair them 190 | with powerful open source libraries like{' '} 191 | 194 | Cashier 195 | 196 | ,{' '} 197 | 200 | Dusk 201 | 202 | ,{' '} 203 | 206 | Echo 207 | 208 | ,{' '} 209 | 212 | Horizon 213 | 214 | ,{' '} 215 | 218 | Sanctum 219 | 220 | ,{' '} 221 | 224 | Telescope 225 | 226 | , and more. 227 |
228 |
229 |
230 |
231 |
232 | 233 |
234 |
235 |
236 | 244 | 245 | 246 | 247 | 250 | Shop 251 | 252 | 253 | 261 | 262 | 263 | 264 | 267 | Sponsor 268 | 269 |
270 |
271 | 272 |
273 | Laravel Breeze + Next.js template 274 |
275 |
276 |
277 |
278 | 279 | ) 280 | } 281 | -------------------------------------------------------------------------------- /src/components/ApplicationLogo.tsx: -------------------------------------------------------------------------------- 1 | const ApplicationLogo = ({ ...props }) => ( 2 | 3 | 4 | 5 | ) 6 | 7 | export default ApplicationLogo 8 | -------------------------------------------------------------------------------- /src/components/AuthCard.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | 3 | type Props = { 4 | logo: ReactNode 5 | children: ReactNode 6 | } 7 | 8 | const AuthCard = ({ logo, children }: Props) => { 9 | return ( 10 |
11 |
{logo}
12 | 13 |
14 | {children} 15 |
16 |
17 | ) 18 | } 19 | 20 | export default AuthCard 21 | -------------------------------------------------------------------------------- /src/components/AuthSessionStatus.tsx: -------------------------------------------------------------------------------- 1 | import React, { ComponentProps } from 'react' 2 | 3 | interface AuthSessionStatusProps extends ComponentProps<'div'> { 4 | status: string 5 | } 6 | 7 | const AuthSessionStatus = ({ 8 | status, 9 | className, 10 | ...props 11 | }: AuthSessionStatusProps) => { 12 | return ( 13 | <> 14 | {status && ( 15 |
18 | {status} 19 |
20 | )} 21 | 22 | ) 23 | } 24 | 25 | export default AuthSessionStatus 26 | -------------------------------------------------------------------------------- /src/components/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { Menu, Transition } from '@headlessui/react' 3 | 4 | type DropdownProps = { 5 | width?: number 6 | trigger: ReactNode 7 | children: ReactNode 8 | contentClasses?: string 9 | align?: 'right' | 'left' | 'top' 10 | } 11 | 12 | const Dropdown = ({ 13 | align = 'right', 14 | width = 48, 15 | contentClasses = 'py-1 bg-white', 16 | trigger, 17 | children, 18 | }: DropdownProps) => { 19 | let alignmentClasses: string 20 | 21 | switch (align) { 22 | case 'left': 23 | alignmentClasses = 'origin-top-left left-0' 24 | break 25 | case 'top': 26 | alignmentClasses = 'origin-top' 27 | break 28 | case 'right': 29 | default: 30 | alignmentClasses = 'origin-top-right right-0' 31 | break 32 | } 33 | 34 | return ( 35 | 36 | {({ open }) => ( 37 | <> 38 | {trigger} 39 | 40 | 48 |
50 | 53 | {children} 54 | 55 |
56 |
57 | 58 | )} 59 |
60 | ) 61 | } 62 | 63 | export default Dropdown 64 | -------------------------------------------------------------------------------- /src/components/DropdownLink.tsx: -------------------------------------------------------------------------------- 1 | import { Menu } from '@headlessui/react' 2 | import Link, { LinkProps } from 'next/link' 3 | import { ComponentProps, ReactNode } from 'react' 4 | 5 | interface DropdownLinkProps extends LinkProps { 6 | children: ReactNode 7 | } 8 | 9 | interface DropdownButtonProps extends ComponentProps<'button'> { 10 | children: ReactNode 11 | } 12 | 13 | const DropdownLink = ({ children, ...props }: DropdownLinkProps) => ( 14 | 15 | {({ active }) => ( 16 | 21 | {children} 22 | 23 | )} 24 | 25 | ) 26 | 27 | export const DropdownButton = ({ children, ...props }: DropdownButtonProps) => ( 28 | 29 | {({ active }) => ( 30 | 37 | )} 38 | 39 | ) 40 | 41 | export default DropdownLink 42 | -------------------------------------------------------------------------------- /src/components/Layouts/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { useState } from 'react' 3 | import { usePathname } from 'next/navigation' 4 | 5 | import NavLink from '@/components/NavLink' 6 | import Dropdown from '@/components/Dropdown' 7 | import ResponsiveNavLink, { 8 | ResponsiveNavButton, 9 | } from '@/components/ResponsiveNavLink' 10 | import { DropdownButton } from '@/components/DropdownLink' 11 | import ApplicationLogo from '@/components/ApplicationLogo' 12 | 13 | import { UserType } from '@/types/User' 14 | import { useAuth } from '@/hooks/auth' 15 | 16 | const Navigation = ({ user }: { user: UserType }) => { 17 | const pathname = usePathname() 18 | 19 | const { logout } = useAuth({}) 20 | const [open, setOpen] = useState(false) 21 | 22 | return ( 23 | 152 | ) 153 | } 154 | 155 | export default Navigation 156 | -------------------------------------------------------------------------------- /src/components/NavLink.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import Link, { LinkProps } from 'next/link' 3 | 4 | interface NavLinkProps extends LinkProps { 5 | active?: boolean 6 | children: ReactNode 7 | } 8 | 9 | const NavLink = ({ 10 | active = false, 11 | href, 12 | children, 13 | ...props 14 | }: NavLinkProps) => ( 15 | 24 | {children} 25 | 26 | ) 27 | 28 | export default NavLink 29 | -------------------------------------------------------------------------------- /src/components/ResponsiveNavLink.tsx: -------------------------------------------------------------------------------- 1 | import Link, { LinkProps } from 'next/link' 2 | import { ComponentProps, ReactNode } from 'react' 3 | 4 | interface ResponsiveNavLinkProps extends LinkProps { 5 | active?: boolean 6 | children: ReactNode 7 | } 8 | 9 | const ResponsiveNavLink = ({ 10 | active = false, 11 | children, 12 | ...props 13 | }: ResponsiveNavLinkProps) => ( 14 | 21 | {children} 22 | 23 | ) 24 | 25 | export const ResponsiveNavButton = (props: ComponentProps<'button'>) => ( 26 |