├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .zshrc ├── README.md ├── demo.env ├── jsconfig.json ├── package.json ├── secrets └── .gitignore ├── src ├── app.css ├── app.html ├── hooks.server.js ├── lib │ ├── Home.svelte │ ├── Input.svelte │ ├── Loader.svelte │ ├── Message.svelte │ ├── Nav.svelte │ ├── NetworkError.svelte │ ├── Noti.svelte │ ├── Tabs.svelte │ ├── images │ │ ├── Gighub.svelte │ │ └── Logo.svelte │ ├── paginate │ │ ├── PaginationNav.svelte │ │ ├── generateNavigationOptions.js │ │ ├── index.js │ │ ├── paginate.js │ │ └── symbolTypes.js │ ├── themes │ │ ├── DarkModeToggle.svelte │ │ └── themeStore.js │ └── utils │ │ ├── api.js │ │ ├── auth.js │ │ ├── browser.js │ │ ├── timeAgo.js │ │ ├── username.js │ │ ├── validation.js │ │ └── variables.js └── routes │ ├── +error.svelte │ ├── +layout.server.js │ ├── +layout.svelte │ ├── +page.svelte │ ├── admin │ ├── +page.js │ ├── +page.svelte │ ├── user │ │ └── [id] │ │ │ ├── +page.js │ │ │ └── +page.svelte │ └── users │ │ └── [p] │ │ ├── +page.js │ │ └── +page.svelte │ ├── forgot │ └── +page.svelte │ ├── login │ ├── +page.js │ └── +page.svelte │ ├── register │ ├── +page.js │ └── +page.svelte │ └── user │ ├── activation │ └── [token] │ │ └── +page.svelte │ ├── profile │ ├── +page.js │ └── +page.svelte │ └── reset │ └── [token] │ └── +page.svelte ├── static ├── favicon.ico ├── img │ ├── 1.webp │ ├── 404.gif │ ├── 502.gif │ └── github.svg └── robots.txt ├── svelte.config.js └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['eslint:recommended', 'prettier'], 4 | plugins: ['svelte3'], 5 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 6 | parserOptions: { 7 | sourceType: 'module', 8 | ecmaVersion: 2019 9 | }, 10 | env: { 11 | browser: true, 12 | es2017: true, 13 | node: true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .svelte 4 | .env 5 | .idea 6 | .svelte-kit 7 | package-lock.json 8 | .build -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .svelte/** 2 | static/** 3 | build/** 4 | node_modules/** 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "semi": false, 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /.zshrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mylastore/svelte-kit/bf1c6b13fa78404d786c71c4d6d7b21b9d2fcf02/.zshrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sveltekit Template 2 | 3 | Sveltekit template - inspired by [Hackathon Starter](https://hackathon-starter.walcony.com) 4 | 5 | Koa API with authentication, refresh token, password reset - repo can be found here [koa-api](https://github.com/mylastore/koa-blog-api) 6 | 7 | ## DEMO 8 | [Demo App](https://sveltekit.mylastore.com/) 9 | 10 | ## Included 11 | 12 | - Bootstrap 5 CSS (Bootstrap 5 is now Modular) 13 | - Formatting with ESLint and Prettier 14 | - User authentication with JWT token (register users must confirm email to create an account) 15 | - User profile page with [gravatar](https://en.gravatar.com/) if available else displays a default image 16 | - User forgot password 17 | - User roles (customer, admin) 18 | - Admin panel section displaying all register users and stats 19 | - Pagination inspired by [svelte-paginate](https://github.com/TahaSh/svelte-paginate#readme) 20 | 21 | ## Getting started 22 | - Rename the demo.env to .env enter your info 23 | - Create certs directory inside the secrets directory and generate local certs inside. Secure cookie are used on local development (to simulate production issue). 24 | 25 | git clone https://github.com/mylastore/svelte-kit 26 | 27 | npm install && npm start 28 | 29 | Now head over to your favorite browser and open up `localhost:3001` and you are ready to go. 30 | 31 | ## IMPORTANT! Start the [API](https://github.com/mylastore/koa-blog-api) repository and follow the instructions on how to seed the sample users data 32 | 33 | Login as ADMIN me@me.com and Password1 34 | 35 | Login as customer me1@me.com Password1 36 | 37 | ## License 38 | 39 | [MIT](http://opensource.org/licenses/MIT) 40 | -------------------------------------------------------------------------------- /demo.env: -------------------------------------------------------------------------------- 1 | 2 | VITE_NODE_ENV=development 3 | VITE_APP_NAME="SVELTE KIT" 4 | VITE_API_DEVELOPMENT_PATH="http://localhost:8001/api" 5 | VITE_API_PRODUCTION_PATH="https://production.com/api" 6 | VITE_API_ANALYTICS="UA-12345678-9" 7 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true, 11 | "sourceMap": true, 12 | "strict": true, 13 | "paths": { 14 | "$lib": [ 15 | "src/lib" 16 | ], 17 | "$lib/*": [ 18 | "src/lib/*" 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sveltekit", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "start": "vite dev --port 3001", 6 | "build": "NODE_ENV=production vite build", 7 | "live": "NODE_ENV=production PORT=3001 node build", 8 | "test": "NODE_ENV=production PORT=3001 node build", 9 | "preview": "vite preview --port 3001", 10 | "lint": "prettier --check . && eslint --ignore-path .gitignore .", 11 | "format": "prettier --write ." 12 | }, 13 | "dependencies": { 14 | "bootstrap": "^5.1.3", 15 | "cookie": "^0.4.2", 16 | "jwt-decode": "^3.1.2", 17 | "luxon": "^1.28.0", 18 | "svelte-feather-icons": "^3.6.0" 19 | }, 20 | "type": "module", 21 | "devDependencies": { 22 | "@sveltejs/adapter-node": "^1.2.3", 23 | "@sveltejs/kit": "^1.12.0", 24 | "eslint": "^7.32.0", 25 | "eslint-config-prettier": "^8.5.0", 26 | "eslint-plugin-svelte3": "^3.4.1", 27 | "fetch-ponyfill": "^7.1.0", 28 | "prettier": "~2.2.1", 29 | "prettier-plugin-svelte": "^2.2.0", 30 | "svelte": "^3.50.0", 31 | "svelte-check": "^2.7.1", 32 | "vite": "^4.2.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /secrets/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | /* 4 | # Except this file 5 | !.gitignore -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: scroll; 3 | } 4 | 5 | :root { 6 | --bs-danger: #e92228; 7 | --bs-warning: gold; 8 | --bs-success: green; 9 | } 10 | 11 | .site { 12 | display: flex; 13 | min-height: 98vh; 14 | flex-direction: column; 15 | } 16 | 17 | .site-content { 18 | flex: 1; 19 | } 20 | 21 | .btn-danger, .alert-danger { 22 | background-color: var(--bs-danger); 23 | } 24 | 25 | .btn-warning, .alert-warning { 26 | background-color: var(--bs-warning); 27 | } 28 | 29 | .btn-success, .alert-success { 30 | background-color: var(--bs-success); 31 | } 32 | 33 | .alert-danger { 34 | color: white; 35 | border-color: var(--bs-danger); 36 | } 37 | 38 | .alert-warning { 39 | color: #333; 40 | border-color: var(--bs-warning) 41 | } 42 | 43 | .alert-success { 44 | color: white; 45 | border-color: var(--bs-success) 46 | } 47 | 48 | @media all { 49 | body.dark { 50 | background: #2B2A32; 51 | color: white; 52 | } 53 | 54 | body.dark .card { 55 | background: #42414C; 56 | border-color: #4d4d4d; 57 | } 58 | 59 | body.dark .card-body { 60 | color: white; 61 | } 62 | 63 | body.dark .bg-white, 64 | body.dark .bg-light { 65 | background: #212020 !important; 66 | } 67 | 68 | body.dark .card-footer a, 69 | body.dark .text-black-50 { 70 | color: gray !important; 71 | } 72 | 73 | body.dark .page-link { 74 | background: #42414c; 75 | border-color: #42414c; 76 | } 77 | 78 | body.dark .disabled > .page-link { 79 | background: #565656; 80 | border-color: #565656; 81 | color: #444; 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/hooks.server.js: -------------------------------------------------------------------------------- 1 | import * as cookie from 'cookie' 2 | 3 | export async function handle({ event, resolve }) { 4 | const cookies = cookie.parse(event.request.headers.get('cookie') || '') 5 | event.locals.token = cookies.token ? cookies.token : '' 6 | event.locals.user = cookies.user ? JSON.parse(cookies.user) : null 7 | return await resolve(event) 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/Home.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |
9 |
10 | 11 |
Simple starter template to build svelte apps
12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |
20 | mountains 21 |
22 |
Power by Svelte
23 |

SvelteKit is an application framework powered by Svelte — build bigger apps with a 24 | smaller footprint and no headaches

25 | Learn more 26 |
27 |
28 |
29 |
30 |
31 | mountains 32 |
33 |
SvelteKit
34 |

A framework for building web applications, with a simple development experience and 35 | flexible filesystem-based routing

36 | Go somewhere 37 |
38 |
39 |
40 |
41 |
42 | mountains 43 |
44 |
Build fast
45 |

Hit the ground running with advanced routing, server-side rendering, code-splitting, 46 | offline support and more

47 | Read docs 48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 | 82 | -------------------------------------------------------------------------------- /src/lib/Input.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 | 19 | {#if controlType === 'textarea'} 20 | 28 | {#if help} 29 |
{help}
30 | {/if} 31 | {/if} 32 | {#if controlType === 'input'} 33 | (touched = true)} 41 | /> 42 | {#if help} 43 |
{help}
44 | {/if} 45 | {/if} 46 | {#if validityMessage && !valid && touched} 47 |

{validityMessage}

48 | {/if} 49 |
50 | 51 | 65 | -------------------------------------------------------------------------------- /src/lib/Loader.svelte: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Loading... 5 |
6 |
7 |
8 | 9 | -------------------------------------------------------------------------------- /src/lib/Message.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | {message} 12 | {#if closeMessage} 13 | 16 | {/if} 17 |
18 | 19 | 46 | -------------------------------------------------------------------------------- /src/lib/Nav.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 | 122 | 123 | 209 | -------------------------------------------------------------------------------- /src/lib/NetworkError.svelte: -------------------------------------------------------------------------------- 1 | 2 | Network Error! 3 | 4 | 5 | 6 |
7 |
8 |

Oops!

9 | 12 | Network Error 13 |
14 |
15 | 16 | 26 | -------------------------------------------------------------------------------- /src/lib/Noti.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | 34 | 35 | {#if $notifications[0]} 36 |
37 | 40 |
41 | {/if} 42 | 43 | -------------------------------------------------------------------------------- /src/lib/Tabs.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /src/lib/images/Gighub.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/images/Logo.svelte: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /src/lib/paginate/PaginationNav.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 | 100 | 101 | 118 | -------------------------------------------------------------------------------- /src/lib/paginate/generateNavigationOptions.js: -------------------------------------------------------------------------------- 1 | import { PREVIOUS_PAGE, NEXT_PAGE, ELLIPSIS } from './symbolTypes' 2 | 3 | export default function ({ 4 | totalItems, 5 | pageSize, 6 | currentPage, 7 | limit = null, 8 | showStepOptions = false 9 | }) { 10 | const totalPages = Math.ceil(totalItems / pageSize) 11 | const limitThreshold = getLimitThreshold({ limit }) 12 | const limited = limit && totalPages > limitThreshold 13 | let options = limited 14 | ? generateLimitedOptions({ totalPages, limit, currentPage }) 15 | : generateUnlimitedOptions({ totalPages }) 16 | return showStepOptions ? addStepOptions({ options, currentPage, totalPages }) : options 17 | } 18 | 19 | function generateUnlimitedOptions({ totalPages }) { 20 | return new Array(totalPages).fill(null).map((value, index) => ({ 21 | type: 'number', 22 | value: index + 1 23 | })) 24 | } 25 | 26 | function generateLimitedOptions({ totalPages, limit, currentPage }) { 27 | const boundarySize = limit * 2 + 2 28 | const firstBoundary = 1 + boundarySize 29 | const lastBoundary = totalPages - boundarySize 30 | const totalShownPages = firstBoundary + 2 31 | 32 | if (currentPage <= firstBoundary - limit) { 33 | return Array(totalShownPages) 34 | .fill(null) 35 | .map((value, index) => { 36 | if (index === totalShownPages - 1) { 37 | return { 38 | type: 'number', 39 | value: totalPages 40 | } 41 | } else if (index === totalShownPages - 2) { 42 | return { 43 | type: 'symbol', 44 | symbol: ELLIPSIS, 45 | value: firstBoundary + 1 46 | } 47 | } 48 | return { 49 | type: 'number', 50 | value: index + 1 51 | } 52 | }) 53 | } else if (currentPage >= lastBoundary + limit) { 54 | return Array(totalShownPages) 55 | .fill(null) 56 | .map((value, index) => { 57 | if (index === 0) { 58 | return { 59 | type: 'number', 60 | value: 1 61 | } 62 | } else if (index === 1) { 63 | return { 64 | type: 'symbol', 65 | symbol: ELLIPSIS, 66 | value: lastBoundary - 1 67 | } 68 | } 69 | return { 70 | type: 'number', 71 | value: lastBoundary + index - 2 72 | } 73 | }) 74 | } else if (currentPage >= firstBoundary - limit && currentPage <= lastBoundary + limit) { 75 | return Array(totalShownPages) 76 | .fill(null) 77 | .map((value, index) => { 78 | if (index === 0) { 79 | return { 80 | type: 'number', 81 | value: 1 82 | } 83 | } else if (index === 1) { 84 | return { 85 | type: 'symbol', 86 | symbol: ELLIPSIS, 87 | value: currentPage - limit + (index - 2) 88 | } 89 | } else if (index === totalShownPages - 1) { 90 | return { 91 | type: 'number', 92 | value: totalPages 93 | } 94 | } else if (index === totalShownPages - 2) { 95 | return { 96 | type: 'symbol', 97 | symbol: ELLIPSIS, 98 | value: currentPage + limit + 1 99 | } 100 | } 101 | return { 102 | type: 'number', 103 | value: currentPage - limit + (index - 2) 104 | } 105 | }) 106 | } 107 | } 108 | 109 | function addStepOptions({ options, currentPage, totalPages }) { 110 | return [ 111 | { 112 | type: 'symbol', 113 | symbol: PREVIOUS_PAGE, 114 | value: currentPage <= 1 ? 1 : currentPage - 1 115 | }, 116 | ...options, 117 | { 118 | type: 'symbol', 119 | symbol: NEXT_PAGE, 120 | value: currentPage >= totalPages ? totalPages : currentPage + 1 121 | } 122 | ] 123 | } 124 | 125 | function getLimitThreshold({ limit }) { 126 | const maximumUnlimitedPages = 3 // This means we cannot limit 3 pages or less 127 | const numberOfBoundaryPages = 2 // The first and last pages are always shown 128 | return limit * 2 + maximumUnlimitedPages + numberOfBoundaryPages 129 | } 130 | -------------------------------------------------------------------------------- /src/lib/paginate/index.js: -------------------------------------------------------------------------------- 1 | export { default as paginate } from './paginate.js' 2 | export { default as PaginationNav } from './PaginationNav.svelte' 3 | -------------------------------------------------------------------------------- /src/lib/paginate/paginate.js: -------------------------------------------------------------------------------- 1 | export default function ({ items, pageSize, currentPage }) { 2 | return items.slice((currentPage - 1) * pageSize, (currentPage - 1) * pageSize + pageSize) 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/paginate/symbolTypes.js: -------------------------------------------------------------------------------- 1 | export const PREVIOUS_PAGE = 'PREVIOUS_PAGE' 2 | export const NEXT_PAGE = 'NEXT_PAGE' 3 | export const ELLIPSIS = 'ELLIPSIS' 4 | -------------------------------------------------------------------------------- /src/lib/themes/DarkModeToggle.svelte: -------------------------------------------------------------------------------- 1 | 40 | 41 | 47 | 48 | 143 | -------------------------------------------------------------------------------- /src/lib/themes/themeStore.js: -------------------------------------------------------------------------------- 1 | import {writable} from "svelte/store"; 2 | 3 | export const theme = writable('dark' ) 4 | -------------------------------------------------------------------------------- /src/lib/utils/api.js: -------------------------------------------------------------------------------- 1 | import fetchPonyfill from "fetch-ponyfill" 2 | import {variables} from "$lib/utils/variables.js" 3 | import {logout} from "$lib/utils/auth.js" 4 | import {notifications} from "$lib/Noti.svelte" 5 | import {browser} from "$lib/utils/browser.js" 6 | 7 | const {fetch} = fetchPonyfill() 8 | const apiPath = variables.env === 'development' ? variables.apiDevPath : variables.apiLivePath 9 | 10 | export const api = async (method, path, data) => { 11 | const gotData = typeof data === 'object' && !Array.isArray(data) && data !== null 12 | const formData = data instanceof FormData 13 | try{ 14 | const response = (await fetch(`${apiPath}/${path}`, { 15 | method: method, 16 | 'credentials': 'include', 17 | headers: { 18 | Accept: 'application/json', 19 | 'Content-Type': 'application/json' 20 | }, 21 | // if FormData we set body: data else JSON stringify(data) & we don't set body when no data 22 | ...(gotData ? { body: !formData ? JSON.stringify(data) : data } : null) 23 | })) 24 | const res = await response.json() 25 | // logout user when no token is found on the BE 26 | if (res.status === 440) return await logout() 27 | // display all error messages 28 | if (res.status >= 400) { 29 | return browser && notifications.push(res.message) 30 | } else { 31 | return res 32 | } 33 | 34 | }catch (err){ 35 | if (err && err instanceof Error) { 36 | // server error 37 | return browser && notifications.push('Something went wrong. Please try later.') 38 | } else { 39 | throw err 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/utils/auth.js: -------------------------------------------------------------------------------- 1 | import {browser} from "$app/environment" 2 | 3 | export const logout = async () => { 4 | if (browser) { 5 | await localStorage.removeItem('username') 6 | return window.location.replace('/') 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/utils/browser.js: -------------------------------------------------------------------------------- 1 | 2 | export const browser = (typeof window !== 'undefined' && typeof document !== 'undefined') 3 | -------------------------------------------------------------------------------- /src/lib/utils/timeAgo.js: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon' 2 | 3 | const units = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'] 4 | 5 | const timeAgo = (date) => { 6 | if(!date) return 'invalid date' 7 | let dateTime = DateTime.fromISO(date) 8 | const diff = dateTime.diffNow().shiftTo(...units) 9 | const unit = units.find((unit) => diff.get(unit) !== 0) || 'second' 10 | 11 | const relativeFormatter = new Intl.RelativeTimeFormat('en', { 12 | numeric: 'auto' 13 | }) 14 | return relativeFormatter.format(Math.trunc(diff.as(unit)), unit) 15 | } 16 | 17 | export default timeAgo 18 | -------------------------------------------------------------------------------- /src/lib/utils/username.js: -------------------------------------------------------------------------------- 1 | import {writable} from "svelte/store" 2 | import {browser} from "$app/environment" 3 | 4 | const userName = browser && localStorage.getItem('username') 5 | 6 | export const username = writable( (browser && userName && (JSON.parse(userName)) || '') ) 7 | username.subscribe((v)=> browser && ( localStorage.username = JSON.stringify(v)) ) 8 | -------------------------------------------------------------------------------- /src/lib/utils/validation.js: -------------------------------------------------------------------------------- 1 | export function isRequire (str) { 2 | if (str) { 3 | return typeof str === 'string' && str !== ''; 4 | } 5 | } 6 | 7 | // 8 characters or more, one capital letter and one special character 8 | export function isPassword (val) { 9 | return new RegExp('^(?=.*[a-z])(?=.*[A-Z])(.{8,50})$').test(val) 10 | } 11 | 12 | export function isEmail (val) { 13 | return new RegExp('^\\w+([-+.\']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$').test(val) 14 | } 15 | 16 | export function isUrl (val) { 17 | return new RegExp( 18 | /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/ 19 | ).test(val) 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/utils/variables.js: -------------------------------------------------------------------------------- 1 | export const variables = { 2 | env: import.meta.env.VITE_NODE_ENV, 3 | apiDevPath: import.meta.env.VITE_API_DEVELOPMENT_PATH, 4 | apiLivePath: import.meta.env.VITE_API_PRODUCTION_PATH, 5 | appName: import.meta.env.VITE_APP_NAME, 6 | currencyLocation: {symbol: "$", code: 'USA'}, 7 | analyticsID: import.meta.env.VITE_APP_ANALYTICS 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/+error.svelte: -------------------------------------------------------------------------------- 1 | 2 | Not Found! 3 | 4 | 5 | 6 |
7 |
8 | 404 Not Found 9 | 12 |
13 |
14 | 15 | 24 | -------------------------------------------------------------------------------- /src/routes/+layout.server.js: -------------------------------------------------------------------------------- 1 | export const load = ({locals}) => { 2 | return { 3 | token: locals.token, 4 | user: locals.user 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | {#if $theme && (typeof window != 'undefined')} 14 |
15 |
27 | {/if} 28 | 29 | 35 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | {variables.appName} | Marketplace for the new generation 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/routes/admin/+page.js: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit' 2 | 3 | export async function load({parent}) { 4 | const session = await parent() 5 | if (!session.user || session.user && session.user.role !== 'admin') { 6 | throw redirect(302, '/') 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/admin/+page.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | Admin Panel 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |

{userCount}

34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /src/routes/admin/user/[id]/+page.js: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | 3 | export async function load({parent}) { 4 | const session = await parent() 5 | if (!session.user || session.user && session.user.role !== 'admin') { 6 | throw redirect(302, '/') 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/admin/user/[id]/+page.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 | 43 | Admin User Profile 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 |
53 |

User Profile

54 |
55 |
56 | username image 57 |
58 |
59 | {#if name}

Name: {name}

{/if} 60 | {#if userGender}

Gender: {userGender}

{/if} 61 | {#if userLocation}

Location: {userLocation}

{/if} 62 | {#if userWebsite}

Website: {userWebsite}

{/if} 63 | {#if userAbout}

About: {userAbout}

{/if} 64 |

Role: {userRole}

65 |

Member Since: {memberSince}

66 |
67 |
68 |
69 |
70 |
71 | 72 | 73 | 84 | -------------------------------------------------------------------------------- /src/routes/admin/users/[p]/+page.js: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | 3 | export async function load({parent}) { 4 | const session = await parent() 5 | if (!session.user || session.user && session.user.role !== 'admin') { 6 | throw redirect(302, '/') 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/routes/admin/users/[p]/+page.svelte: -------------------------------------------------------------------------------- 1 | 57 | 58 | 59 | Admin Panel 60 | 61 | 62 | 63 | 64 | 65 |
66 |
67 |
68 |
69 | 70 | 71 | 72 | 75 | 78 | 81 | 84 | 87 | 90 | 93 | 96 | 97 | 98 | 99 | {#each users as user, i} 100 | 101 | 102 | 109 | 110 | 111 | 112 | 113 | 114 | 136 | 137 | {/each} 138 | 139 |
73 | Role 74 | 76 | Image 77 | 79 | Name 80 | 82 | Gender 83 | 85 | Website 86 | 88 | Location 89 | 91 | Member Since 92 | 94 | Action 95 |
{user.role} 103 | {#if user.avatar} 104 | User Image 105 | {:else} 106 | User Image 107 | {/if} 108 | {user.name}{user.gender}{user.website}{user.location}{timeAgo(user.createdAt)} 115 | 116 | 117 | 133 | 134 | 135 |
140 |
141 | handleSetPage(e)} 148 | /> 149 |
150 |
151 |
152 | 153 | 154 | 183 | -------------------------------------------------------------------------------- /src/routes/forgot/+page.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | Forgot Password 30 | 31 | 32 | 33 |
34 |
35 |
36 |
37 |

Password Reset

38 | (email = event.target.value)} 47 | /> 48 |
49 | 56 | Cancel 57 |
58 |
59 |
60 |
61 |
62 | 63 | 68 | -------------------------------------------------------------------------------- /src/routes/login/+page.js: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | 3 | export async function load({parent}) { 4 | const session = await parent() 5 | if (session.user) { 6 | throw redirect(302, '/'); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/login/+page.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 | 43 | 44 | 45 | Login Form 46 | 47 | 48 | 49 |
50 | 51 |
52 | 96 |
97 | 98 |
99 | 108 |
109 |
110 | 111 | 112 | 122 | -------------------------------------------------------------------------------- /src/routes/register/+page.js: -------------------------------------------------------------------------------- 1 | import { redirect } from '@sveltejs/kit'; 2 | 3 | export async function load({parent}) { 4 | const session = await parent() 5 | if (session.user) { 6 | throw redirect(302, '/'); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/register/+page.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 | 43 | 44 | 45 | Register Form 46 | 47 | 48 | 49 |
50 |
51 |
52 |
53 |
54 |
55 |

Sing Up

56 |

We only offer our services in the United States.

57 |
58 |
59 | (name = event.target.value)} 68 | /> 69 | (email = event.target.value)} 77 | /> 78 | (password = event.target.value)} 87 | /> 88 | (passwordConfirmation = event.target.value)} 98 | /> 99 |
100 | 108 | By signing up you accept our Privacy Policy. 109 |
110 |
111 |
112 | 115 |
116 |
117 |
118 |
119 | 120 | 128 | -------------------------------------------------------------------------------- /src/routes/user/activation/[token]/+page.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | Account Activation 31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 |
Activate account for {email}
39 |
40 | 43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /src/routes/user/profile/+page.js: -------------------------------------------------------------------------------- 1 | import {redirect} from '@sveltejs/kit' 2 | 3 | export async function load({parent}) { 4 | const session = await parent() 5 | if (!session.user) { 6 | throw redirect(302, '/') 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/user/profile/+page.svelte: -------------------------------------------------------------------------------- 1 | 108 | 109 | 110 | Profile Page 111 | 112 | 113 | 114 |
115 |
116 | {#if user} 117 |
118 |
119 |
120 |
121 |
Profile Information
122 |
:::
123 |
124 | User Image 130 |
131 | {#if name} 132 |

133 | Name: 134 | {name} 135 |

136 | {/if} 137 |

138 | Email: 139 | {email} 140 |

141 | {#if website} 142 |

143 | Website: 144 | {website} 145 |

146 | {/if} 147 | {#if location} 148 |

149 | Location: 150 | {location} 151 |

152 | {/if} 153 | {#if gender} 154 |

155 | Gender: 156 | {gender} 157 |

158 | {/if} 159 | {#if about} 160 |

161 | About: 162 | {about} 163 |

164 | {/if} 165 |

166 | Role: 167 | {role} 168 |

169 |
170 | 176 |
177 |
178 |
179 |
180 |
181 |
182 | 191 |
192 | 193 | 194 |

Email can not be updated.

195 |
196 | (about = event.target.value)} 201 | /> 202 | (website = event.target.value)} 207 | /> 208 | (location = event.target.value)} 213 | /> 214 |
215 |
216 | 217 |
218 |
219 |
220 |
221 | 225 | 229 | 233 |
234 |
235 |
236 |
237 | 244 |
245 |
246 | Fields with *asterisk are required. 247 |
248 |
249 | 250 | 251 |
252 |
253 | (password = event.target.value)} 261 | /> 262 | (passwordConfirmation = event.target.value)} 271 | /> 272 | 279 |
280 |
281 | 282 |
283 |
284 | 287 |
288 |
289 | 290 | 291 | Warning! Deleting your account is irreversible. 292 | 293 |
294 |
295 |
296 |
297 | {/if} 298 |
299 |
300 | 301 | 302 | 307 | -------------------------------------------------------------------------------- /src/routes/user/reset/[token]/+page.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | Password Reset 37 | 38 | 39 | 40 |
41 |
42 |
43 |
44 |
45 |

NEW PASSWORD

46 | (password = event.target.value)} 54 | /> 55 | (passwordConfirmation = event.target.value)} 64 | /> 65 | 72 |
73 |
74 |
75 |
76 |
77 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mylastore/svelte-kit/bf1c6b13fa78404d786c71c4d6d7b21b9d2fcf02/static/favicon.ico -------------------------------------------------------------------------------- /static/img/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mylastore/svelte-kit/bf1c6b13fa78404d786c71c4d6d7b21b9d2fcf02/static/img/1.webp -------------------------------------------------------------------------------- /static/img/404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mylastore/svelte-kit/bf1c6b13fa78404d786c71c4d6d7b21b9d2fcf02/static/img/404.gif -------------------------------------------------------------------------------- /static/img/502.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mylastore/svelte-kit/bf1c6b13fa78404d786c71c4d6d7b21b9d2fcf02/static/img/502.gif -------------------------------------------------------------------------------- /static/img/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-node' 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | adapter: adapter(), 7 | } 8 | }; 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import {sveltekit} from '@sveltejs/kit/vite' 3 | import fs from "fs" 4 | 5 | export default defineConfig(({ command }) => { 6 | if (command === 'serve') { 7 | const key = fs.readFileSync('secrets/certs/localhost.key') 8 | const pem = fs.readFileSync('secrets/certs/localhost.crt') 9 | 10 | return { 11 | server: { 12 | https: { 13 | key: key, 14 | cert: pem, 15 | }, 16 | host: 'localhost', 17 | port: '3007', 18 | strictPort: true 19 | }, 20 | plugins: [sveltekit()] 21 | } 22 | } else { 23 | return { 24 | server: { 25 | host: 'localhost', 26 | port: '3007', 27 | strictPort: true 28 | }, 29 | plugins: [sveltekit()] 30 | } 31 | } 32 | }) 33 | --------------------------------------------------------------------------------