├── .npmrc ├── .env.example ├── static └── favicon.png ├── src ├── routes │ ├── +layout.svelte │ ├── +layout.server.ts │ ├── (app) │ │ ├── dashboard │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ └── profile │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ ├── (auth) │ │ ├── logout │ │ │ └── +page.server.ts │ │ ├── password-reset │ │ │ ├── +page.server.ts │ │ │ ├── [token] │ │ │ │ ├── +page.server.ts │ │ │ │ └── +page.svelte │ │ │ └── +page.svelte │ │ ├── register │ │ │ ├── +page.server.ts │ │ │ └── +page.svelte │ │ └── login │ │ │ ├── +page.svelte │ │ │ └── +page.server.ts │ └── (guest) │ │ └── +page.svelte ├── lib │ ├── components │ │ ├── layouts │ │ │ ├── GuestLayout.svelte │ │ │ ├── AppLayout.svelte │ │ │ └── Navigation.svelte │ │ ├── AuthCard.svelte │ │ ├── AuthSessionStatus.svelte │ │ ├── Label.svelte │ │ ├── ResponsiveNavButton.svelte │ │ ├── Input.svelte │ │ ├── AuthValidationErrors.svelte │ │ ├── Button.svelte │ │ ├── NavLink.svelte │ │ ├── ResponsiveNavLink.svelte │ │ ├── Dropdown.svelte │ │ └── ApplicationLogo.svelte │ ├── axios.ts │ └── axios_backend.ts ├── app.postcss ├── app.html ├── app.d.ts └── hooks.server.ts ├── .gitignore ├── .vscode └── settings.json ├── tailwind.config.cjs ├── .eslintignore ├── .prettierignore ├── .prettierrc ├── vite.config.ts ├── postcss.config.cjs ├── svelte.config.js ├── .eslintrc.cjs ├── tsconfig.json ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | VITE_BACKEND_URL=http://localhost:8000 -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lindgr3n/breeze-sveltekit/HEAD/static/favicon.png -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | -------------------------------------------------------------------------------- /src/lib/components/layouts/GuestLayout.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app.postcss: -------------------------------------------------------------------------------- 1 | /* Write your global styles here, in PostCSS syntax */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /src/routes/+layout.server.ts: -------------------------------------------------------------------------------- 1 | import type { LayoutServerLoad } from './$types'; 2 | 3 | export const load: LayoutServerLoad = async function (event) { 4 | return { 5 | user: event.locals.user 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "spellright.language": [ 3 | "en" 4 | ], 5 | "spellright.documentTypes": [ 6 | "markdown", 7 | "latex", 8 | "plaintext" 9 | ] 10 | } -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | content: ['./src/**/*.{html,js,svelte,ts}'], 3 | 4 | theme: { 5 | extend: {} 6 | }, 7 | 8 | plugins: [require('@tailwindcss/forms')] 9 | }; 10 | 11 | module.exports = config; 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /src/lib/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const authClient = axios.create({ 4 | // baseURL: 'http://localhost:3000', 5 | headers: { 6 | 'X-Requested-With': 'XMLHttpRequest' 7 | }, 8 | withCredentials: true // required to handle the CSRF token 9 | }); 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import type { UserConfig } from 'vite'; 3 | 4 | const config: UserConfig = { 5 | plugins: [sveltekit()], 6 | ssr: { 7 | noExternal: ['@lucia-auth/sveltekit'] 8 | } 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /src/lib/components/AuthCard.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 |
7 |
8 | -------------------------------------------------------------------------------- /src/routes/(app)/dashboard/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad, Actions } from './$types'; 2 | 3 | export const load: PageServerLoad = async function () { 4 | return {}; 5 | }; 6 | 7 | export const actions: Actions = { 8 | default: async () => { 9 | return {}; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/components/AuthSessionStatus.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | {#if status} 8 |
9 | {status}> 10 |
11 | {/if} 12 | -------------------------------------------------------------------------------- /src/lib/components/Label.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /src/lib/axios_backend.ts: -------------------------------------------------------------------------------- 1 | import axios, { type AxiosInstance } from 'axios'; 2 | 3 | export const authClient: AxiosInstance = axios.create({ 4 | baseURL: import.meta.env.VITE_BACKEND_URL as string, 5 | headers: { 6 | 'X-Requested-With': 'XMLHttpRequest' 7 | }, 8 | withCredentials: true // required to handle the CSRF token 9 | }); 10 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | 4 | const config = { 5 | plugins: [ 6 | //Some plugins, like tailwindcss/nesting, need to run before Tailwind, 7 | tailwindcss(), 8 | //But others, like autoprefixer, need to run after, 9 | autoprefixer 10 | ] 11 | }; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /src/lib/components/ResponsiveNavButton.svelte: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/Input.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import preprocess from 'svelte-preprocess'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: [ 9 | preprocess({ 10 | postcss: true 11 | }) 12 | ], 13 | 14 | kit: { 15 | adapter: adapter() 16 | } 17 | }; 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /src/lib/components/AuthValidationErrors.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | {#if errors.length > 0} 8 |
9 |
Whoops! Something went wrong.
10 | 11 | 16 |
17 | {/if} 18 | -------------------------------------------------------------------------------- /src/lib/components/layouts/AppLayout.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/routes/(app)/profile/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad, Actions } from './$types'; 2 | 3 | export const load: PageServerLoad = async function ({ locals }) { 4 | // Could skip passing the user here because pagedata is merged with the page data in layout.server 5 | // Can be good to return it here in the case where you want light weight user populated in hooks and want to return more detailed user here. 6 | return { user: locals.user }; 7 | }; 8 | 9 | export const actions: Actions = { 10 | default: async () => { 11 | return {}; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/lib/components/Button.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/lib/components/NavLink.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/components/ResponsiveNavLink.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true 12 | } 13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 14 | // 15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 16 | // from the referenced tsconfig.json - TypeScript does not merge them in 17 | } 18 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // See https://kit.svelte.dev/docs#typescript 4 | // for information about these interfaces 5 | declare global { 6 | namespace App { 7 | interface Locals { 8 | user: User?; 9 | } 10 | 11 | interface PageData { 12 | user: User?; 13 | } 14 | 15 | // interface Platform {} 16 | 17 | // interface Session {} 18 | 19 | // interface Stuff {} 20 | } 21 | interface User { 22 | id: number; 23 | name: string; 24 | email: string; 25 | email_verified_at: Date; 26 | created_at: Date; 27 | updated_at: Date; 28 | } 29 | } 30 | 31 | export {}; 32 | -------------------------------------------------------------------------------- /src/routes/(app)/dashboard/+page.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | Laravel - Dashboard 7 | 8 | 9 | 10 |

Dashboard

11 | 12 |
13 |
14 |
15 |
You're logged in!
16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/routes/(app)/profile/+page.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | Laravel - Profile 11 | 12 | 13 | 14 |

Profile

15 | 16 |
17 |
18 |
19 |
20 | You're logged in as {data.user?.email} 21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | import { authClient } from '$lib/axios_backend'; 2 | import { redirect, type Handle } from '@sveltejs/kit'; 3 | 4 | export const handle: Handle = async ({ event, resolve }) => { 5 | // Verify we are authenticated 6 | const responseFromServer = await authClient('/user', { 7 | method: 'get', 8 | headers: { 9 | Referer: event.url.host, 10 | 'X-XSRF-TOKEN': event.cookies.get('XSRF-TOKEN'), 11 | Cookie: `XSRF-TOKEN=${event.cookies.get('XSRF-TOKEN')};laravel_session=${event.cookies.get( 12 | 'laravel_session' 13 | )}` 14 | } 15 | }).catch(() => { 16 | // Unauthenticated 17 | }); 18 | 19 | event.locals.user = responseFromServer?.data ?? null; 20 | const routeId = event.route.id ?? ''; 21 | 22 | if (event.locals.user && routeId.includes('/(auth)/') && routeId != '/(auth)/logout') { 23 | throw redirect(303, '/dashboard'); 24 | } 25 | 26 | if (!event.locals.user && routeId.includes('/(app)/')) { 27 | // Need authentication 28 | throw redirect(303, '/login'); 29 | } 30 | 31 | const response = await resolve(event); 32 | 33 | return response; 34 | }; 35 | -------------------------------------------------------------------------------- /src/routes/(auth)/logout/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { authClient } from '$lib/axios_backend'; 2 | import { redirect, type ServerLoadEvent } from '@sveltejs/kit'; 3 | import cookie from 'cookie'; 4 | import type { PageServerLoad, Actions, RequestEvent } from './$types'; 5 | 6 | export const load: PageServerLoad = async function (event) { 7 | if (event.locals.user) { 8 | await logoutAction(event); 9 | } 10 | throw redirect(307, '/login'); 11 | }; 12 | 13 | export const actions: Actions = { 14 | default: async (event) => { 15 | await logoutAction(event); 16 | throw redirect(307, '/login'); 17 | } 18 | }; 19 | 20 | async function logoutAction(event: ServerLoadEvent | RequestEvent) { 21 | const cookies = cookie.parse(event.request.headers.get('cookie') || ''); 22 | 23 | await authClient('/logout', { 24 | method: 'post', 25 | headers: { 26 | Referer: event.url.host, 27 | 'X-XSRF-TOKEN': cookies['XSRF-TOKEN'], 28 | Cookie: `XSRF-TOKEN=${cookies['XSRF-TOKEN']};laravel_session=${cookies['laravel_session']}` 29 | } 30 | }).catch(() => { 31 | // 32 | }); 33 | event.cookies.delete('XSRF-TOKEN'); 34 | event.cookies.delete('laravel_session'); 35 | event.cookies.delete('laravel_session'); 36 | event.locals.user = null; 37 | 38 | throw redirect(307, '/login'); 39 | } 40 | -------------------------------------------------------------------------------- /src/routes/(auth)/password-reset/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { authClient } from '$lib/axios_backend'; 2 | import { fail } from '@sveltejs/kit'; 3 | import cookie from 'cookie'; 4 | import type { PageServerLoad, Actions } from './$types'; 5 | 6 | export const load: PageServerLoad = async function () { 7 | return {}; 8 | }; 9 | 10 | export const actions: Actions = { 11 | default: async ({ request, url }) => { 12 | const data = await request.formData(); 13 | 14 | const response = await authClient.get('/sanctum/csrf-cookie'); 15 | const headersCookies = response?.headers['set-cookie'] ?? []; 16 | const cookies = cookie.parse(headersCookies.join(';')); 17 | 18 | let errorsResponse: Array = []; 19 | await authClient('/forgot-password', { 20 | method: 'post', 21 | headers: { 22 | Referer: url.host, 23 | 'X-XSRF-TOKEN': cookies['XSRF-TOKEN'], 24 | Cookie: `XSRF-TOKEN=${cookies['XSRF-TOKEN']};laravel_session=${cookies['laravel_session']}` 25 | }, 26 | data: { email: data.get('email') } 27 | }).catch((error) => { 28 | errorsResponse = Object.keys(error.response.data.errors).map((key) => { 29 | return error.response.data.errors[key]; 30 | }); 31 | }); 32 | 33 | if (errorsResponse.length > 0) { 34 | return fail(400, { errors: errorsResponse }); 35 | } 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "breeze-sveltekit", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "vite dev", 6 | "build": "vite build", 7 | "preview": "vite preview", 8 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 9 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 10 | "lint": "prettier --plugin-search-dir . --check . && eslint .", 11 | "format": "prettier --plugin-search-dir . --write ." 12 | }, 13 | "devDependencies": { 14 | "@sveltejs/adapter-auto": "next", 15 | "@sveltejs/kit": "^1.17.0", 16 | "@tailwindcss/forms": "^0.5.3", 17 | "@typescript-eslint/eslint-plugin": "^5.59.6", 18 | "@typescript-eslint/parser": "^5.59.6", 19 | "autoprefixer": "^10.4.14", 20 | "eslint": "^8.40.0", 21 | "eslint-config-prettier": "^8.8.0", 22 | "eslint-plugin-svelte3": "^4.0.0", 23 | "postcss": "^8.4.23", 24 | "postcss-load-config": "^4.0.1", 25 | "prettier": "^2.8.8", 26 | "prettier-plugin-svelte": "^2.10.0", 27 | "svelte": "^3.59.1", 28 | "svelte-check": "^3.3.2", 29 | "svelte-preprocess": "^5.0.3", 30 | "tailwindcss": "^3.3.2", 31 | "tslib": "^2.5.0", 32 | "typescript": "^5.0.4", 33 | "vite": "^4.3.7" 34 | }, 35 | "type": "module", 36 | "dependencies": { 37 | "axios": "^1.4.0", 38 | "cookie": "^0.5.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/components/Dropdown.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 | 39 |
40 | -------------------------------------------------------------------------------- /src/routes/(auth)/password-reset/[token]/+page.server.ts: -------------------------------------------------------------------------------- 1 | import { authClient } from '$lib/axios_backend'; 2 | import { fail, redirect } from '@sveltejs/kit'; 3 | import type { AxiosResponse } from 'axios'; 4 | import cookie from 'cookie'; 5 | import type { PageServerLoad, Actions } from './$types'; 6 | 7 | export const load: PageServerLoad = async function () { 8 | return {}; 9 | }; 10 | 11 | export const actions: Actions = { 12 | default: async ({ request, params, url }) => { 13 | const data = await request.formData(); 14 | const token = params.token; 15 | const email = url.searchParams.get('email'); 16 | 17 | const response: AxiosResponse = await authClient.get('/sanctum/csrf-cookie'); 18 | const headersCookies = response?.headers['set-cookie'] ?? []; 19 | const cookies = cookie.parse(headersCookies.join(';')); 20 | 21 | let errorsResponse: Array = []; 22 | await authClient('/reset-password', { 23 | method: 'post', 24 | headers: { 25 | Referer: url.host, 26 | 'X-XSRF-TOKEN': cookies['XSRF-TOKEN'], 27 | Cookie: `XSRF-TOKEN=${cookies['XSRF-TOKEN']};laravel_session=${cookies['laravel_session']}` 28 | }, 29 | data: { 30 | email: email, 31 | password: data.get('password'), 32 | password_confirmation: data.get('password_confirmation'), 33 | token: token 34 | } 35 | }).catch((error) => { 36 | errorsResponse = Object.keys(error.response.data.errors).map((key) => { 37 | return error.response.data.errors[key]; 38 | }); 39 | }); 40 | 41 | if (errorsResponse.length > 0) { 42 | return fail(400, { errors: errorsResponse }); 43 | } 44 | throw redirect(303, '/login'); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /src/routes/(auth)/register/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad, Actions } from './$types'; 2 | import cookie from 'cookie'; 3 | import { authClient } from '$lib/axios_backend'; 4 | import { fail, redirect } from '@sveltejs/kit'; 5 | 6 | export const load: PageServerLoad = async function () { 7 | return {}; 8 | }; 9 | 10 | export const actions: Actions = { 11 | default: async (event) => { 12 | const data = await event.request.formData(); 13 | const name = data.get('name'); 14 | const email = data.get('email'); 15 | const password = data.get('password'); 16 | const password_confirmation = data.get('password_confirmation'); 17 | 18 | const response = await authClient.get('/sanctum/csrf-cookie'); 19 | const headersCookies = response?.headers['set-cookie'] ?? []; 20 | const cookies = cookie.parse(headersCookies.join(';')); 21 | 22 | let errorsResponse: Array = []; 23 | await authClient('/register', { 24 | method: 'post', 25 | headers: { 26 | 'X-XSRF-TOKEN': cookies['XSRF-TOKEN'], 27 | Cookie: `XSRF-TOKEN=${cookies['XSRF-TOKEN']};laravel_session=${cookies['laravel_session']}` 28 | }, 29 | data: { 30 | name: name, 31 | email: email, 32 | password: password, 33 | password_confirmation: password_confirmation 34 | } 35 | }).catch((error) => { 36 | console.log(error); 37 | 38 | errorsResponse = Object.keys(error.response.data.errors).map((key) => { 39 | return error.response.data.errors[key]; 40 | }); 41 | }); 42 | 43 | if (errorsResponse.length > 0) { 44 | return fail(400, { email, name, errors: errorsResponse }); 45 | } 46 | throw redirect(303, '/login'); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/routes/(auth)/password-reset/+page.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 |
39 | 40 |
41 | Forgot your password? No problem. Just let us know your email address and we will email you a 42 | password reset link that will allow you to choose a new one. 43 |
44 | 45 | 46 | 47 | 48 |
49 | 50 |
51 | 52 | 61 |
62 | 63 |
64 | 65 |
66 |
67 |
68 |
69 | -------------------------------------------------------------------------------- /src/routes/(auth)/password-reset/[token]/+page.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 |
39 | 40 |
Enter your new password.
41 | 42 | 43 | 44 | 45 |
46 | 47 |
48 | 49 | 58 |
59 |
60 | 61 | 70 |
71 | 72 |
73 | 74 |
75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /src/routes/(auth)/login/+page.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | 36 | 45 |
46 | 47 | 48 |
49 | 50 | 51 | 59 |
60 | 61 | 62 |
63 | 73 |
74 | 75 | 81 |
82 |
83 |
84 | -------------------------------------------------------------------------------- /src/routes/(auth)/register/+page.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 | 44 |
45 | 46 | 47 |
48 | 49 | 57 |
58 | 59 | 60 |
61 | 62 | 70 |
71 | 72 | 73 |
74 | 75 | 82 |
83 | 84 |
85 | 86 | Already registered? 87 | 88 | 89 |
90 |
91 |
92 |
93 | -------------------------------------------------------------------------------- /src/routes/(auth)/login/+page.server.ts: -------------------------------------------------------------------------------- 1 | import type { PageServerLoad, Actions } from './$types'; 2 | import cookie from 'cookie'; 3 | import { authClient } from '$lib/axios_backend'; 4 | import type { AxiosResponse } from 'axios'; 5 | import { fail, redirect } from '@sveltejs/kit'; 6 | 7 | export const load: PageServerLoad = async function () { 8 | return {}; 9 | }; 10 | 11 | export const actions: Actions = { 12 | default: async ({ request, locals, cookies, url }) => { 13 | const data = await request.formData(); 14 | const email = data.get('email'); 15 | const password = data.get('password'); 16 | 17 | if (!email) { 18 | return fail(400, { email, missing: true }); 19 | } 20 | 21 | if (!password) { 22 | return fail(400, { password, missing: true }); 23 | } 24 | 25 | const response: AxiosResponse = await authClient.get('/sanctum/csrf-cookie'); 26 | let headersCookies = response?.headers['set-cookie'] ?? []; 27 | let headerCookies = cookie.parse(headersCookies.join(';')); 28 | 29 | let errorsResponse: Array = []; 30 | const loginResponse: AxiosResponse | void = await authClient('/login', { 31 | method: 'post', 32 | headers: { 33 | 'X-XSRF-TOKEN': headerCookies['XSRF-TOKEN'], 34 | Cookie: `XSRF-TOKEN=${headerCookies['XSRF-TOKEN']};laravel_session=${headerCookies['laravel_session']}` 35 | }, 36 | data: { email: data.get('email'), password: data.get('password') } 37 | }).catch((error) => { 38 | errorsResponse = Object.keys(error.response.data.errors).map((key) => { 39 | return error.response.data.errors[key]; 40 | }); 41 | }); 42 | 43 | if (errorsResponse.length > 0) { 44 | return fail(400, { errors: errorsResponse }); 45 | } 46 | 47 | headersCookies = loginResponse?.headers['set-cookie'] ?? []; 48 | headerCookies = cookie.parse(headersCookies.join(';')); 49 | const responseFromServer: AxiosResponse | void = await authClient('/user', { 50 | method: 'get', 51 | headers: { 52 | Referer: url.host, 53 | 'X-XSRF-TOKEN': headerCookies['XSRF-TOKEN'], 54 | Cookie: `XSRF-TOKEN=${headerCookies['XSRF-TOKEN']};laravel_session=${headerCookies['laravel_session']}` 55 | } 56 | }).catch((e) => { 57 | console.log('CATCHED USER ERROR', e.response.statusText); 58 | }); 59 | 60 | headersCookies = responseFromServer?.headers['set-cookie'] ?? []; 61 | headerCookies = cookie.parse(headersCookies.join(';')); 62 | 63 | locals.user = responseFromServer?.data ?? null; 64 | 65 | cookies.set('XSRF-TOKEN', headerCookies['XSRF-TOKEN'], { 66 | httpOnly: true, 67 | maxAge: 60 * 60 * 24 * 7, // 1 week 68 | path: '/' 69 | }); 70 | cookies.set('laravel_session', headerCookies['laravel_session'], { 71 | httpOnly: true, 72 | maxAge: 60 * 60 * 24 * 7, // 1 week 73 | path: '/' 74 | }); 75 | cookies.set('laravel_session', headerCookies['laravel_session'], { 76 | httpOnly: true, 77 | maxAge: 60 * 60 * 24 * 7, // 1 week 78 | path: '/' 79 | }); 80 | 81 | throw redirect(303, '/dashboard'); 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Breeze - SvelteKit Edition 🦅 2 | 3 | ## Introduction 4 | 5 | This repository is an SvelteKit implementation of the [Breeze Next](https://github.com/laravel/breeze-next) application. 6 | 7 | This is an implementing of the [Laravel Breeze](https://laravel.com/docs/starter-kits) application / authentication starter kit frontend in [SvelteKit](https://kit.svelte.dev/). 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 SvelteKit frontend with a powerful Laravel backend. 8 | 9 | If you do not want to install the backend from scratch you can find the repo tested against [here](https://github.com/lindgr3n/breeze-backend-api) 10 | 11 | ## Official Documentation 12 | 13 | ### Installation 14 | 15 | First, create a SvelteKit compatible Laravel backend by installing Laravel Breeze into a [fresh Laravel application](https://laravel.com/docs/installation) and installing Breeze's API scaffolding: 16 | 17 | ```bash 18 | # Create the Laravel application... 19 | laravel new sveltekit-backend 20 | 21 | cd next-backend 22 | 23 | # Install Breeze and dependencies... 24 | composer require laravel/breeze 25 | 26 | php artisan breeze:install api 27 | ``` 28 | 29 | > Note: Don't forget to create a database and set up your enviroment file to point out the databse. Also run the migrations. More info in [databases and migrations](https://laravel.com/docs/9.x/installation#databases-and-migrations) 30 | 31 | Next, ensure that your application's `APP_URL` and `FRONTEND_URL` environment variables are set to `http://localhost:8000` and `http://localhost:3000`, respectively. 32 | 33 | After defining the appropriate environment variables, you may serve the Laravel application using the `serve` Artisan command: 34 | 35 | ```bash 36 | # Serve the application... 37 | php artisan serve 38 | ``` 39 | 40 | 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: 41 | 42 | ``` 43 | VITE_BACKEND_URL=http://localhost:8000 44 | ``` 45 | 46 | Finally, run the application via `npm run dev`. The application will be available at `http://localhost:3000`: 47 | 48 | ``` 49 | npm run dev 50 | ``` 51 | 52 | > Note: Currently, we recommend using `localhost` during local development of your backend and frontend to avoid CORS "Same-Origin" issues. 53 | 54 | ### Authentication Hook 55 | 56 | Authentication of the user is done via the `hooks` file to check if the provided cookies is valid. Here we also check if the route accessed is a guest route or a protected route. 57 | By setting the user in the `getSession` we can access it in each page by the context `load` method to check if we are allowed to access this route together with the guest flag. This also makes us take advantage of SSR the page with correct user information. 58 | 59 | ## Security Vulnerabilities 60 | 61 | Please review the [security policy](https://github.com/laravel/breeze-next/security/policy) on how to report security vulnerabilities. 62 | -------------------------------------------------------------------------------- /src/lib/components/ApplicationLogo.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/components/layouts/Navigation.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 140 | -------------------------------------------------------------------------------- /src/routes/(guest)/+page.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
10 | 17 | 18 |
19 |
20 | 26 | 27 | 30 | 31 | 32 |
33 | 34 |
35 |
36 |
37 |
38 | 47 | 50 | 51 | 52 | 57 |
58 | 59 |
60 |
61 | Laravel has wonderful, thorough documentation covering every aspect of the framework. 62 | Whether you are new to the framework or have previous experience with Laravel, we 63 | recommend reading all of the documentation from beginning to end. 64 |
65 |
66 |
67 | 68 |
69 |
70 | 79 | 82 | 83 | 84 | 85 | 90 |
91 | 92 |
93 |
94 | Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript 95 | development. Check them out, see for yourself, and massively level up your development 96 | skills in the process. 97 |
98 |
99 |
100 | 101 |
102 |
103 | 112 | 115 | 116 | 117 | 122 |
123 | 124 |
125 |
126 | Laravel News is a community driven portal and newsletter aggregating all of the latest 127 | and most important news in the Laravel ecosystem, including new package releases and 128 | tutorials. 129 |
130 |
131 |
132 | 133 |
134 |
135 | 144 | 147 | 148 | 149 |
150 | Vibrant Ecosystem 151 |
152 |
153 | 154 |
155 |
156 | Laravel's robust library of first-party tools and libraries, such as{' '} 157 | Forge 158 | ,{' '} 159 | Vapor 160 | ,{' '} 161 | Nova 162 | , and{' '} 163 | Envoyer {' '} 164 | help you take your projects to the next level. Pair them with powerful open source libraries 165 | like{' '} 166 | Cashier 167 | ,{' '} 168 | Dusk 169 | ,{' '} 170 | Echo 171 | ,{' '} 172 | Horizon 173 | ,{' '} 174 | Sanctum 175 | ,{' '} 176 | Telescope 177 | , and more. 178 |
179 |
180 |
181 |
182 |
183 | 184 |
185 |
186 |
187 | 196 | 199 | 200 | 201 | Shop 202 | 203 | 212 | 215 | 216 | 217 | Sponsor 218 |
219 |
220 | 221 |
222 | Laravel Breeze + SvelteKit template 223 |
224 |
225 |
226 |
227 | --------------------------------------------------------------------------------