Initial commit 🚀

This commit is contained in:
2021-09-23 21:28:45 +02:00
commit 2fb96b2ca4
83 changed files with 9890 additions and 0 deletions

3
.eslintignore Normal file
View File

@@ -0,0 +1,3 @@
dist
node_modules
public

6
.eslintrc Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"@antfu",
"vue-global-api"
]
}

12
.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
.DS_Store
.vite-ssg-dist
.vite-ssg-temp
*.local
dist
dist-ssr
node_modules
# intellij stuff
.idea/
# logs
*.log
/.env

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 Anthony Fu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

158
auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,158 @@
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onMounted: typeof import('vue')['onMounted']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const computed: typeof import('vue')['computed']
const customRef: typeof import('vue')['customRef']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const unref: typeof import('vue')['unref']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const defineComponent: typeof import('vue')['defineComponent']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const nextTick: typeof import('vue')['nextTick']
const provide: typeof import('vue')['provide']
const useCssModule: typeof import('vue')['useCssModule']
const createApp: typeof import('vue')['createApp']
const triggerRef: typeof import('vue')['triggerRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const onDeactivated: typeof import('vue')['onDeactivated']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const useRouter: typeof import('vue-router')['useRouter']
const useRoute: typeof import('vue-router')['useRoute']
const useI18n: typeof import('vue-i18n')['useI18n']
const useHead: typeof import('@vueuse/head')['useHead']
const biSyncRef: typeof import('@vueuse/core')['biSyncRef']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const extendRef: typeof import('@vueuse/core')['extendRef']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const syncRef: typeof import('@vueuse/core')['syncRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toReactive: typeof import('@vueuse/core')['toReactive']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
const until: typeof import('@vueuse/core')['until']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useToggle: typeof import('@vueuse/core')['useToggle']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const computedInject: typeof import('@vueuse/core')['computedInject']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const templateRef: typeof import('@vueuse/core')['templateRef']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
const useDark: typeof import('@vueuse/core')['useDark']
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementSize: typeof import('@vueuse/core')['useElementSize']
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
const useEventBus: typeof import('@vueuse/core')['useEventBus']
const useEventListener: typeof import('@vueuse/core')['useEventListener']
const useEventSource: typeof import('@vueuse/core')['useEventSource']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNow: typeof import('@vueuse/core')['useNow']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
}
export {}

60
components.d.ts vendored Normal file
View File

@@ -0,0 +1,60 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/vue-next/pull/3399
declare module 'vue' {
export interface GlobalComponents {
ArrowIcon: typeof import('./src/components/icons/ArrowIcon.vue')['default']
BrushIcon: typeof import('./src/components/icons/BrushIcon.vue')['default']
CarbonCampsite: typeof import('virtual:vite-icons/carbon/campsite')['default']
CarbonDicomOverlay: typeof import('virtual:vite-icons/carbon/dicom-overlay')['default']
CarbonLanguage: typeof import('virtual:vite-icons/carbon/language')['default']
CarbonLogoGithub: typeof import('virtual:vite-icons/carbon/logo-github')['default']
CarbonMoon: typeof import('virtual:vite-icons/carbon/moon')['default']
CarbonPedestrian: typeof import('virtual:vite-icons/carbon/pedestrian')['default']
CarbonSun: typeof import('virtual:vite-icons/carbon/sun')['default']
CarbonWarning: typeof import('virtual:vite-icons/carbon/warning')['default']
CloudIcon: typeof import('./src/components/icons/CloudIcon.vue')['default']
CollapseIcon: typeof import('./src/components/icons/CollapseIcon.vue')['default']
ColorModeButton: typeof import('./src/components/ColorModeButton.vue')['default']
CompassIcon: typeof import('./src/components/icons/CompassIcon.vue')['default']
CrownIcon: typeof import('./src/components/icons/CrownIcon.vue')['default']
CubeIcon: typeof import('./src/components/icons/CubeIcon.vue')['default']
DegreeHatIcon: typeof import('./src/components/icons/DegreeHatIcon.vue')['default']
DeleteButton: typeof import('./src/components/DeleteButton.vue')['default']
FlagIcon: typeof import('./src/components/icons/FlagIcon.vue')['default']
Footer: typeof import('./src/components/Footer.vue')['default']
GearIcon: typeof import('./src/components/icons/GearIcon.vue')['default']
GlobeIcon: typeof import('./src/components/icons/GlobeIcon.vue')['default']
GraduationIcon: typeof import('./src/components/icons/GraduationIcon.vue')['default']
GraphIcon: typeof import('./src/components/icons/GraphIcon.vue')['default']
Header: typeof import('./src/components/Header.vue')['default']
HomeIcon: typeof import('./src/components/icons/HomeIcon.vue')['default']
LanguageButton: typeof import('./src/components/LanguageButton.vue')['default']
LightbulbIcon: typeof import('./src/components/icons/LightbulbIcon.vue')['default']
LinkIco: typeof import('./src/components/icons/LinkIco.vue')['default']
LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default']
Logo: typeof import('./src/components/Logo.vue')['default']
MediaIcon: typeof import('./src/components/icons/MediaIcon.vue')['default']
MoonIcon: typeof import('./src/components/icons/MoonIcon.vue')['default']
NewspaperIcon: typeof import('./src/components/icons/NewspaperIcon.vue')['default']
PageTitle: typeof import('./src/components/PageTitle.vue')['default']
PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default']
PenIcon: typeof import('./src/components/icons/PenIcon.vue')['default']
PresentationIcon: typeof import('./src/components/icons/PresentationIcon.vue')['default']
SideBar: typeof import('./src/components/SideBar.vue')['default']
SideBarCollapse: typeof import('./src/components/SideBarCollapse.vue')['default']
SideBarCollapseItem: typeof import('./src/components/SideBarCollapseItem.vue')['default']
SideBarDivider: typeof import('./src/components/SideBarDivider.vue')['default']
SideBarItem: typeof import('./src/components/SideBarItem.vue')['default']
StarIcon: typeof import('./src/components/icons/StarIcon.vue')['default']
SunIcon: typeof import('./src/components/icons/SunIcon.vue')['default']
TagIcon: typeof import('./src/components/icons/TagIcon.vue')['default']
TranslateIcon: typeof import('./src/components/icons/TranslateIcon.vue')['default']
TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default']
UpdateButton: typeof import('./src/components/UpdateButton.vue')['default']
UsersIcon: typeof import('./src/components/icons/UsersIcon.vue')['default']
}
}
export { }

24
index.html Normal file
View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/pwa-192x192.png">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#00aba9">
<meta name="msapplication-TileColor" content="#00aba9">
<meta name="theme-color" content="#ffffff">
</head>
<body>
<div id="app"></div>
<script>
(function() {
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const setting = localStorage.getItem('color-schema') || 'auto'
if (setting === 'dark' || (prefersDark && setting !== 'light'))
document.documentElement.classList.toggle('dark', true)
})()
</script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

47
locales/en.json Normal file
View File

@@ -0,0 +1,47 @@
{
"hello": "HomePage",
"logo": {
"short": "H.",
"long": "Hermes"
},
"sidebar": {
"home": "Home",
"users": "Users",
"subscribers": "Subscribers",
"translations": "Translations",
"files": "Files",
"ares": "Ares",
"announces": "Announces",
"informations": "Informations",
"skills": "Skills",
"formations": "Formations",
"experiences": "Experiences",
"tags": "Tags",
"colors": "Colors",
"posts": "Posts",
"artemis": "Artemis",
"links": "Links",
"stats": "Statistics",
"tools": "Tools",
"apollon": "Apollon"
},
"login": {
"form": {
"email": "Email Address",
"password": "Password",
"login": "Login"
},
"forget": "Forgot password?",
"no_account": "You don't have an account?",
"ask": "Ask for one.",
"secure": "Secure login process ✅",
"credits": "© 2021 - Hermes by ArtDanjProduction.",
"credentials": "Your credentials are incorrect ❌"
}
}

47
locales/fr.json Normal file
View File

@@ -0,0 +1,47 @@
{
"hello": "Page d'accueil",
"logo": {
"short": "H.",
"long": "Hermes"
},
"sidebar": {
"home": "Accueil",
"users": "Utilisateurs",
"subscribers": "Abonnés",
"translations": "Traductions",
"files": "Fichiers",
"ares": "Ares",
"announces": "Annonces",
"informations": "Informations",
"skills": "Compétences",
"formations": "Formations",
"experiences": "Expériences",
"tags": "Tags",
"colors": "Couleurs",
"posts": "Articles",
"artemis": "Artemis",
"links": "Liens",
"stats": "Statistiques",
"tools": "Outils",
"apollon": "Apollon"
},
"login": {
"form": {
"email": "Adresse email",
"password": "Mot de passe",
"login": "Connexion"
},
"forget": "Mot de passe oublié ?",
"no_account": "Vous n'avez pas de compte ?",
"ask": "Demandez-en un.",
"secure": "Processus de connexion sécurisé ✅",
"credits": "© 2021 - Hermes par ArtDanjProduction.",
"credentials": "Vos identifiants sont incorrects ❌"
}
}

53
package.json Normal file
View File

@@ -0,0 +1,53 @@
{
"private": true,
"scripts": {
"dev": "vite --port 3333 --open",
"build": "cross-env NODE_ENV=production vite-ssg build",
"preview": "vite preview",
"preview-https": "serve dist"
},
"dependencies": {
"@tiptap/starter-kit": "^2.0.0-beta.101",
"@tiptap/vue-3": "^2.0.0-beta.56",
"@vueuse/core": "^6.0.0",
"@vueuse/head": "^0.6.0",
"axios": "^0.21.4",
"nprogress": "^0.2.0",
"prism-theme-vars": "^0.2.2",
"sass": "^1.39.0",
"vue": "^3.2.2",
"vue-demi": "^0.11.3",
"vue-global-api": "^0.4.1",
"vue-i18n": "^9.1.7",
"vue-router": "^4.0.11",
"vuex": "^4.0.2"
},
"devDependencies": {
"@antfu/eslint-config": "^0.7.0",
"@iconify/json": "^1.1.398",
"@intlify/vite-plugin-vue-i18n": "^2.4.0",
"@types/markdown-it-link-attributes": "^3.0.1",
"@types/nprogress": "^0.2.0",
"@vitejs/plugin-vue": "^1.6.1",
"@vue/compiler-sfc": "^3.2.9",
"@vue/server-renderer": "^3.2.9",
"critters": "^0.0.10",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"https-localhost": "^4.6.5",
"markdown-it-link-attributes": "^3.0.0",
"markdown-it-prism": "^2.2.0",
"typescript": "^4.4.2",
"unplugin-auto-import": "^0.4.5",
"unplugin-icons": "^0.7.6",
"unplugin-vue-components": "^0.14.13",
"vite": "^2.5.4",
"vite-plugin-md": "^0.11.0",
"vite-plugin-pages": "^0.18.0",
"vite-plugin-pwa": "^0.11.2",
"vite-plugin-vue-layouts": "^0.4.1",
"vite-plugin-windicss": "^1.4.2",
"vite-ssg": "^0.15.1",
"vue-tsc": "^0.3.0"
}
}

9
public/favicon.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
path { fill: #222; }
@media (prefers-color-scheme: dark) {
path { fill: #ffffff; }
}
</style>
<path d="M27.562 26L17.17 8.928l2.366-3.888L17.828 4L16 7.005L14.17 4l-1.708 1.04l2.366 3.888L4.438 26H2v2h28v-2zM16 10.85L25.22 26H17v-8h-2v8H6.78z" />
</svg>

After

Width:  |  Height:  |  Size: 347 B

BIN
public/pwa-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
public/pwa-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Allow: /

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2916 6015 c-93 -57 -173 -108 -178 -113 -6 -6 7 -36 33 -78 23 -38
86 -141 139 -229 54 -88 135 -221 180 -295 46 -74 94 -155 108 -180 14 -25 29
-52 35 -60 7 -12 -9 -45 -62 -130 -39 -63 -85 -140 -103 -170 -18 -30 -117
-194 -222 -365 -104 -170 -199 -326 -210 -346 -12 -19 -61 -102 -111 -183 -49
-81 -101 -166 -115 -189 -14 -23 -39 -64 -55 -90 -17 -27 -77 -126 -134 -220
-57 -95 -127 -210 -156 -257 -194 -315 -325 -533 -325 -541 0 -5 -4 -9 -10 -9
-5 0 -10 -4 -10 -9 0 -5 -55 -98 -121 -207 -247 -404 -403 -660 -416 -684 -8
-14 -58 -97 -112 -185 l-98 -160 -189 -2 c-104 -1 -225 -2 -269 -2 l-80 -1 1
-210 c0 -116 4 -213 8 -218 11 -11 6107 -9 6114 2 8 13 8 406 0 419 -4 7 -88
10 -265 9 l-259 -2 -50 77 c-27 43 -54 87 -60 98 -6 11 -62 103 -124 205 -62
102 -120 197 -129 212 -9 16 -85 142 -170 280 -85 139 -160 262 -165 273 -6
11 -13 22 -16 25 -3 3 -30 46 -59 95 -30 50 -102 169 -161 265 -59 96 -240
393 -402 660 -163 267 -371 609 -463 760 -92 151 -194 318 -225 370 -31 52
-101 167 -155 255 l-97 160 27 50 c16 27 32 55 36 61 5 5 38 59 74 120 36 60
69 116 74 124 5 8 75 122 155 253 81 131 144 242 141 247 -4 7 -114 76 -183
115 -10 6 -52 32 -95 58 -42 27 -81 46 -87 42 -8 -5 -94 -140 -140 -219 -19
-33 -221 -365 -246 -404 -15 -22 -18 -18 -111 135 -52 87 -123 203 -157 258
-67 108 -67 110 -111 184 -16 28 -34 51 -40 50 -5 0 -86 -47 -179 -104z m739
-1642 c319 -526 519 -854 637 -1046 43 -70 78 -130 78 -133 0 -2 5 -10 10 -17
6 -7 69 -109 140 -227 72 -118 134 -222 139 -230 5 -8 55 -89 111 -180 56 -91
105 -172 110 -180 9 -14 52 -84 270 -445 54 -88 135 -221 180 -295 46 -74 91
-148 100 -165 9 -16 31 -53 48 -81 18 -28 32 -54 32 -57 0 -3 -403 -6 -895 -5
l-895 0 0 81 c-1 45 -1 439 -1 875 l0 792 -37 1 c-57 1 -344 1 -374 0 l-27 -1
0 -832 c0 -458 0 -852 0 -875 l-1 -42 -895 1 c-492 0 -895 3 -895 5 0 9 115
198 122 201 5 2 8 7 8 12 0 5 23 46 51 92 28 46 78 128 112 183 33 55 70 116
82 135 12 19 132 215 265 435 133 220 266 438 295 485 65 105 206 338 220 362
6 10 172 284 370 608 198 325 387 635 420 690 33 55 62 100 65 100 3 0 73
-111 155 -247z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

23
src/App.vue Normal file
View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
import { useHead } from '@vueuse/head'
import { useStore } from 'vuex'
// https://github.com/vueuse/head
// you can use this to manipulate the document head in any components,
// they will be rendered correctly in the html results with vite-ssg
useHead({
title: 'Hermes - ArtDanjProduction',
meta: [
{ name: 'description', content: 'Hermes is my personal dashboard used to manage my services.' },
],
})
const store = useStore()
onBeforeMount(() => {
store.dispatch('FETCH_USER')
})
</script>
<template>
<router-view />
</template>

View File

@@ -0,0 +1,17 @@
<template>
<div
class="h-9 w-9 cursor-pointer flex items-center p-1.5 rounded-xl hover:bg-gray-300 dark:hover:bg-dark-400 duration-200"
@click="toggleDark"
>
<div v-if="isDark">
<SunIcon />
</div>
<div v-else>
<MoonIcon />
</div>
</div>
</template>
<script setup lang="ts">
import { isDark, toggleDark } from '~/logic'
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div class="flex">
<div class="bg-red-300 hover:bg-red-500 duration-500 cursor-pointer py-2 px-2.5 rounded-lg">
<TrashIcon />
</div>
</div>
</template>
<script setup lang="ts">
</script>

15
src/components/Footer.vue Normal file
View File

@@ -0,0 +1,15 @@
<template>
<footer class="">
FOOTER
</footer>
</template>
<script>
export default {
name: 'Footer',
}
</script>
<style scoped>
</style>

35
src/components/Header.vue Normal file
View File

@@ -0,0 +1,35 @@
<template>
<header class="fixed bg-white dark:bg-dark-900 top-0 w-full h-16 border-b border-gray-200 dark:border-gray-800">
<div class="h-full flex items-center justify-center">
<div
class="bg-white dark:bg-dark-900 py-1 cursor-pointer z-10 absolute top-4 -left-3 transition-transform delay-300 duration-300 transform flex justify-center items-center"
:class="opened ? 'rotate-540' : 'rotate-0'"
@click="setOpened(!opened)"
>
<ArrowIcon />
</div>
<h1>Header</h1>
<ColorModeButton class="mx-2" />
<LanguageButton class="mx-2" />
<div
class="hover:text-red-400 cursor-pointer mx-4 duration-500"
@click.prevent="logout"
>
Logout
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
import { useAuth } from '~/logic/auth'
const store = useStore()
const opened = computed(() => store.getters.isOpened)
const setOpened = (value: boolean) => {
store.commit('SET_OPENED', value)
}
const { logout, } = useAuth()
</script>

View File

@@ -0,0 +1,18 @@
<template>
<div
class="h-9 w-9 cursor-pointer flex items-center justify-center p-1.5 rounded-xl hover:bg-gray-300 duration-200 dark:hover:bg-dark-400"
@click="changeLanguage()"
>
<FlagIcon :french="isFrench" />
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
const isFrench = computed(() => locale.value === 'fr')
const changeLanguage = () => {
locale.value = locale.value === 'fr' ? 'en' : 'fr'
}
</script>

23
src/components/Logo.vue Normal file
View File

@@ -0,0 +1,23 @@
<template>
<div
v-if="opened && router.currentRoute.name !== 'login'"
class="font-black text-xl tracking-widest"
>
{{ t('logo.long') }}
</div>
<div
v-else
class="font-black text-xl tracking-widest"
>
{{ t('logo.short') }}
</div>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const router = useRouter()
const store = useStore()
const opened = computed(() => store.getters.isOpened)
const { t } = useI18n()
</script>

View File

@@ -0,0 +1,19 @@
<template>
<div class="flex items-center mb-8">
<div class="mr-4 text-lg flex items-center justify-center p-2 bg-red-400 dark:bg-amber-400 rounded-xl text-black">
<slot />
</div>
<h1 class="font-medium text-4xl">
{{ title }}
</h1>
</div>
</template>
<script setup lang="ts">
defineProps({
title: {
type: String,
default: 'Title',
},
})
</script>

View File

@@ -0,0 +1,99 @@
<template>
<div>
<aside
class="fixed bg-white dark:bg-dark-900 top-0 left-0 duration-300 h-screen overflow-y-scroll select-none border-r border-gray-200 dark:border-gray-800"
:class="opened ? 'w-64' : 'w-20'"
>
<div class="my-8 flex justify-center">
<Logo />
</div>
<nav
class="flex flex-col p-4 duration-300"
:class="{'items-center': !opened}"
>
<SideBarItem title="home" link="/">
<HomeIcon />
</SideBarItem>
<SideBarItem title="users" link="users">
<UsersIcon />
</SideBarItem>
<SideBarItem title="subscribers" link="subscribers">
<StarIcon />
</SideBarItem>
<SideBarItem title="translations" link="translations">
<TranslateIcon />
</SideBarItem>
<SideBarItem title="files" link="files">
<MediaIcon />
</SideBarItem>
<SideBarDivider />
<SideBarCollapse title="ares">
<template #icon>
<GlobeIcon />
</template>
<template #children>
<SideBarCollapseItem title="announces" link="announces">
<NewspaperIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="informations" link="informations">
<CrownIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="skills" link="skills">
<CubeIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="formations" link="formations">
<DegreeHatIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="experiences" link="experiences">
<PresentationIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="tags" link="tags">
<TagIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="colors" link="colors">
<BrushIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="posts" link="posts">
<PenIcon />
</SideBarCollapseItem>
</template>
</SideBarCollapse>
<SideBarDivider />
<SideBarCollapse title="artemis">
<template #icon>
<CompassIcon />
</template>
<template #children>
<SideBarCollapseItem title="links" link="links">
<LinkIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="stats" link="statistiques">
<GraphIcon />
</SideBarCollapseItem>
</template>
</SideBarCollapse>
<SideBarDivider />
<SideBarCollapse title="tools">
<template #icon>
<GearIcon />
</template>
<template #children>
<SideBarCollapseItem title="apollon">
<CloudIcon />
</SideBarCollapseItem>
<SideBarCollapseItem title="stats">
<GraphIcon />
</SideBarCollapseItem>
</template>
</SideBarCollapse>
</nav>
</aside>
</div>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const opened = computed(() => store.getters.isOpened)
</script>

View File

@@ -0,0 +1,51 @@
<template>
<div class="flex flex-col my-2">
<div
class="flex cursor-pointer justify-between hover:(bg-red-400 dark:bg-amber-400 dark:text-black) rounded-2xl duration-300 p-3"
:class="[{'bg-red-300 dark:bg-amber-300 dark:text-black': collapsed}]"
@click.prevent="collapse()"
>
<div class="flex">
<slot name="icon" />
<h1 v-if="opened" class="font-semibold ml-2 duration-300">
{{ t(`sidebar.${title}`) }}
</h1>
</div>
<div
v-if="opened"
class="transform duration-300"
:class="collapsed ? 'rotate-0' : 'rotate-450'"
>
<CollapseIcon />
</div>
</div>
<div
v-show="collapsed"
class="font-lg"
:class="opened ? 'ml-8 border-l-2 border-gray-400 dark:border-gray-600' : 'mt-2'"
>
<slot name="children" />
</div>
</div>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const opened = computed(() => store.getters.isOpened)
const collapsed = ref(false)
const collapse = () => {
collapsed.value = !collapsed.value
}
const { t } = useI18n()
defineProps({
title: {
default: 'Title',
type: String,
},
})
</script>

View File

@@ -0,0 +1,36 @@
<template>
<div
class="my-2 flex py-1 z-50"
:class="{'pl-2': opened}"
>
<router-link
:to="link"
class="duration-300 flex hover:(bg-gray-300 dark:bg-gray-700) rounded-2xl cursor-pointer"
:class="opened ? 'p-3 w-full' : 'p-3'"
>
<slot />
<h1 v-if="opened" class="font-semibold ml-2">
{{ t(`sidebar.${title}`) }}
</h1>
</router-link>
</div>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const opened = computed(() => store.getters.isOpened)
const { t } = useI18n()
defineProps({
title: {
default: 'Title',
type: String,
},
link: {
default: '/',
type: String,
},
})
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div
class="border-2 rounded-full border-gray-100 dark:border-gray-800 my-4"
:class="{'w-full': !opened}"
></div>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const opened = computed(() => store.getters.isOpened)
</script>

View File

@@ -0,0 +1,33 @@
<template>
<div class="flex my-2">
<router-link
:to="link"
class="duration-300 flex hover:(bg-gray-300 dark:bg-gray-700) rounded-2xl cursor-pointer p-3"
:class="{'w-full': opened}"
>
<slot />
<h1 v-if="opened" class="font-semibold ml-2">
{{ t(`sidebar.${title}`) }}
</h1>
</router-link>
</div>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const opened = computed(() => store.getters.isOpened)
const { t } = useI18n()
defineProps({
title: {
default: 'Title',
type: String,
},
link: {
default: '/',
type: String,
},
})
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div class="flex">
<div class="bg-green-300 hover:bg-green-500 duration-500 cursor-pointer py-2 px-2.5 rounded-lg">
<PencilIcon />
</div>
</div>
</template>
<script>
export default {
name: 'UpdateButton',
}
</script>

View File

@@ -0,0 +1,14 @@
<template>
<svg
class="inline"
width="1.4em"
height="1.4em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M128 20a108 108 0 1 0 108 108A108.122 108.122 0 0 0 128 20zm0 192a84 84 0 1 1 84-84a84.096 84.096 0 0 1-84 84zm48.485-92.485a12 12 0 0 1 0 16.97l-33.941 33.942a12 12 0 0 1-16.97-16.97L139.028 140H88a12 12 0 0 1 0-24h51.03l-13.457-13.456a12 12 0 0 1 16.971-16.97z"
fill="currentColor"
/>
</svg>
</template>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M208 20H72a44.05 44.05 0 0 0-44 44v80a20.023 20.023 0 0 0 20 20h50.164l-6.043 42.303A12.045 12.045 0 0 0 92 208a36 36 0 0 0 72 0a12.045 12.045 0 0 0-.12-1.697L157.835 164H208a20.023 20.023 0 0 0 20-20V40a20.023 20.023 0 0 0-20-20zM72 44h84v28a12 12 0 0 0 24 0V44h24v56H52V64a20.023 20.023 0 0 1 20-20zm67.98 164.708a12 12 0 0 1-23.96 0L122.407 164h11.186zM52 140v-16h152v16z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'BrushIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M160 220H72a68 68 0 1 1 7.024-135.64A91.994 91.994 0 1 1 160 220zM70.181 108.037A44 44 0 0 0 72 196h88a68 68 0 1 0-68-68a12 12 0 0 1-24 0a92.006 92.006 0 0 1 2.181-19.963z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'CloudIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M128 188a11.962 11.962 0 0 1-8.485-3.515l-80-80a12 12 0 0 1 16.97-16.97L128 159.029l71.515-71.514a12 12 0 0 1 16.97 16.97l-80 80A11.962 11.962 0 0 1 128 188z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'CollapseIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M128 20a108 108 0 1 0 108 108A108.122 108.122 0 0 0 128 20zm0 192a84 84 0 1 1 84-84a84.096 84.096 0 0 1-84 84zm34.947-134.657l-56.379 22.545a11.999 11.999 0 0 0-6.686 6.685l-22.627 56.569a12 12 0 0 0 15.598 15.598l56.569-22.628a12 12 0 0 0 6.695-6.712l22.437-56.485a12 12 0 0 0-15.607-15.572zm-27.23 58.402l-25.78 10.313l10.318-25.796l25.694-10.274z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'CompassIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M241.28 70.454a19.884 19.884 0 0 0-20.883-2.874l-47.239 20.996l-27.675-49.815a20 20 0 0 0-34.966-.001L82.842 88.576l-47.25-21a20 20 0 0 0-27.59 22.85L33.446 198.76a20.066 20.066 0 0 0 24.866 14.682a261.895 261.895 0 0 1 139.33-.013a20.012 20.012 0 0 0 24.858-14.674l25.49-108.318a19.883 19.883 0 0 0-6.71-19.983zM56.81 193.272l-.001-.008l.002.01zm143.276-4.04a286.037 286.037 0 0 0-144.22.013L33.206 92.78l43.231 19.214a19.902 19.902 0 0 0 25.607-8.562L128 56.71l25.957 46.723a19.897 19.897 0 0 0 25.605 8.56l43.22-19.209z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'CrownIcon',
}
</script>

View File

@@ -0,0 +1,36 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 24 24"
focusable="false"
>
<g
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M6 17.6l-2-1.1V14" />
<path d="M4 10V7.5l2-1.1" />
<path d="M10 4.1L12 3l2 1.1" />
<path d="M18 6.4l2 1.1V10" />
<path d="M20 14v2.5l-2 1.12" />
<path d="M14 19.9L12 21l-2-1.1" />
<path d="M12 12l2-1.1" />
<path d="M18 8.6l2-1.1" />
<path d="M12 12v2.5" />
<path d="M12 18.5V21" />
<path d="M12 12l-2-1.12" />
<path d="M6 8.6L4 7.5" />
</g>
</svg>
</template>
<script>
export default {
name: 'CubeIcon',
}
</script>

View File

@@ -0,0 +1,26 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 48 48"
focusable="false"
>
<g
fill="none"
stroke="currentColor"
stroke-width="4"
stroke-linejoin="round"
>
<path d="M2 17.4L23.022 9l21.022 8.4l-21.022 8.4L2 17.4z" />
<path d="M44.044 17.51v9.223" stroke-linecap="round" />
<path d="M11.556 21.825v12.442S16.366 39 23.021 39c6.657 0 11.467-4.733 11.467-4.733V21.825" stroke-linecap="round" />
</g>
</svg>
</template>
<script>
export default {
name: 'DegreeHatIcon',
}
</script>

View File

@@ -0,0 +1,60 @@
<template>
<svg
v-if="french"
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 36 36"
focusable="false"
>
<path
fill="#00247D"
d="M0 9.059V13h5.628zM4.664 31H13v-5.837zM23 25.164V31h8.335zM0 23v3.941L5.63 23zM31.337 5H23v5.837zM36 26.942V23h-5.631zM36 13V9.059L30.371 13zM13 5H4.664L13 10.837z"
/>
<path
fill="#CF1B2B"
d="M25.14 23l9.712 6.801a3.977 3.977 0 0 0 .99-1.749L28.627 23H25.14zM13 23h-2.141l-9.711 6.8c.521.53 1.189.909 1.938 1.085L13 23.943V23zm10-10h2.141l9.711-6.8a3.988 3.988 0 0 0-1.937-1.085L23 12.057V13zm-12.141 0L1.148 6.2a3.994 3.994 0 0 0-.991 1.749L7.372 13h3.487z"
/>
<path
fill="#EEE"
d="M36 21H21v10h2v-5.836L31.335 31H32a3.99 3.99 0 0 0 2.852-1.199L25.14 23h3.487l7.215 5.052c.093-.337.158-.686.158-1.052v-.058L30.369 23H36v-2zM0 21v2h5.63L0 26.941V27c0 1.091.439 2.078 1.148 2.8l9.711-6.8H13v.943l-9.914 6.941c.294.07.598.116.914.116h.664L13 25.163V31h2V21H0zM36 9a3.983 3.983 0 0 0-1.148-2.8L25.141 13H23v-.943l9.915-6.942A4.001 4.001 0 0 0 32 5h-.663L23 10.837V5h-2v10h15v-2h-5.629L36 9.059V9zM13 5v5.837L4.664 5H4a3.985 3.985 0 0 0-2.852 1.2l9.711 6.8H7.372L.157 7.949A3.968 3.968 0 0 0 0 9v.059L5.628 13H0v2h15V5h-2z"
/>
<path
fill="#CF1B2B"
d="M21 15V5h-6v10H0v6h15v10h6V21h15v-6z"
/>
</svg>
<svg
v-else
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 36 36"
focusable="false"
>
<path
fill="#ED2939"
d="M36 27a4 4 0 0 1-4 4h-8V5h8a4 4 0 0 1 4 4v18z"
/>
<path
fill="#002495"
d="M4 5a4 4 0 0 0-4 4v18a4 4 0 0 0 4 4h8V5H4z"
/>
<path
fill="#EEE"
d="M12 5h12v26H12z"
/>
</svg>
</template>
<script>
export default {
name: 'TranslateIcon',
props: {
french: {
type: Boolean,
default: true,
},
},
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M128 72a56 56 0 1 0 56 56a56.064 56.064 0 0 0-56-56zm0 88a32 32 0 1 1 32-32a32.036 32.036 0 0 1-32 32zm107.602-15.202l-11.608-15.477c.006-.44.009-.881.009-1.32q0-.66-.009-1.324l11.603-15.475a20.037 20.037 0 0 0 3.097-17.966a115.685 115.685 0 0 0-7.824-18.91a20.045 20.045 0 0 0-14.905-10.532l-19.152-2.735q-.46-.474-.927-.94q-.467-.467-.943-.93l-2.738-19.15a20.04 20.04 0 0 0-10.514-14.891a115.71 115.71 0 0 0-18.904-7.84a20.049 20.049 0 0 0-17.986 3.093l-15.477 11.608a99.385 99.385 0 0 0-2.643 0l-15.478-11.605a20.043 20.043 0 0 0-17.965-3.095a115.63 115.63 0 0 0-18.908 7.824a20.041 20.041 0 0 0-10.533 14.905L61.062 59.19q-.475.461-.94.927q-.467.467-.93.943l-19.15 2.738A20.04 20.04 0 0 0 25.15 74.312a115.71 115.71 0 0 0-7.838 18.904a20.044 20.044 0 0 0 3.092 17.986l11.608 15.477c-.006.44-.01.881-.01 1.32q0 .66.01 1.324l-11.603 15.475a20.037 20.037 0 0 0-3.097 17.966a115.685 115.685 0 0 0 7.824 18.91a20.045 20.045 0 0 0 14.905 10.532l19.151 2.735q.462.474.928.94q.467.467.942.93l2.739 19.15a20.04 20.04 0 0 0 10.513 14.892a115.71 115.71 0 0 0 18.905 7.838a20.028 20.028 0 0 0 17.986-3.092l15.477-11.608c.882.012 1.76.013 2.643 0l15.478 11.605a20.044 20.044 0 0 0 17.965 3.095a115.63 115.63 0 0 0 18.908-7.824a20.041 20.041 0 0 0 10.532-14.905l2.736-19.152q.475-.461.94-.927q.467-.467.93-.943l19.15-2.738a20.04 20.04 0 0 0 14.891-10.514a115.71 115.71 0 0 0 7.84-18.904a20.044 20.044 0 0 0-3.093-17.986zm-25.083 23.939l-21.301 3.046a12.002 12.002 0 0 0-7.282 3.92a73.23 73.23 0 0 1-6.233 6.233a12.003 12.003 0 0 0-3.918 7.282l-3.043 21.302a91.672 91.672 0 0 1-11.197 4.633l-17.216-12.908a12.029 12.029 0 0 0-7.919-2.377a73.74 73.74 0 0 1-8.815 0a12.038 12.038 0 0 0-7.92 2.378L98.46 215.158a91.716 91.716 0 0 1-11.194-4.642l-3.046-21.3a11.997 11.997 0 0 0-3.92-7.282a74.047 74.047 0 0 1-3.208-3.02a74.19 74.19 0 0 1-3.023-3.212a12 12 0 0 0-7.284-3.92l-21.302-3.043a91.579 91.579 0 0 1-4.632-11.197l12.907-17.215a11.999 11.999 0 0 0 2.377-7.921a73.472 73.472 0 0 1 0-8.814a12 12 0 0 0-2.378-7.92L40.846 98.456a91.741 91.741 0 0 1 4.641-11.194l21.301-3.046a12.002 12.002 0 0 0 7.282-3.92a73.224 73.224 0 0 1 6.233-6.233a12.003 12.003 0 0 0 3.918-7.282l3.043-21.302a91.672 91.672 0 0 1 11.197-4.633l17.215 12.908a12.02 12.02 0 0 0 7.92 2.377a73.74 73.74 0 0 1 8.815 0a12.018 12.018 0 0 0 7.92-2.378l17.215-12.912a91.716 91.716 0 0 1 11.194 4.642l3.046 21.3a11.997 11.997 0 0 0 3.92 7.282a74.047 74.047 0 0 1 3.208 3.02a74.19 74.19 0 0 1 3.023 3.212a12 12 0 0 0 7.284 3.92l21.301 3.043a91.579 91.579 0 0 1 4.633 11.197l-12.907 17.215a11.999 11.999 0 0 0-2.377 7.921a73.473 73.473 0 0 1 0 8.814a12 12 0 0 0 2.378 7.92l12.911 17.215a91.74 91.74 0 0 1-4.641 11.194z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'GearIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M227.107 170.886a11.857 11.857 0 0 0 .642-1.505a107.912 107.912 0 0 0-37.674-129.707a12.15 12.15 0 0 0-1.24-.86A107.944 107.944 0 0 0 39.221 189.43c.031.054.055.111.088.165a12.002 12.002 0 0 0 1.1 1.514a107.84 107.84 0 0 0 153.1 22.69a11.981 11.981 0 0 0 1.73-1.352a108.591 108.591 0 0 0 31.867-41.561zm-42.28 13.568a20.037 20.037 0 0 0-9.08-5.219l-17.995-4.721l1.533-10.412l26.005-10.808l16.393 14.992a84.49 84.49 0 0 1-14.136 18.894zM127.997 44a83.474 83.474 0 0 1 42.99 11.871l.793 16.5l-20.12 23.247l-27.893 3.778l-14.072-11.818a20 20 0 0 0-29.724 4.556l-19.716 30.901a20.097 20.097 0 0 0-3.043 8.79l-3.461 35.036l-.144.087A83.946 83.946 0 0 1 127.997 44zM67.748 186.446l.124-.075a20.093 20.093 0 0 0 9.565-15.155l3.565-36.08l16.748-26.248l11.938 10.026a20.05 20.05 0 0 0 15.545 4.503l31.148-4.219a19.992 19.992 0 0 0 12.437-6.73l22.156-25.6a19.898 19.898 0 0 0 4.428-8.934a83.716 83.716 0 0 1 15.082 65.877l-10.879-9.949a20.073 20.073 0 0 0-21.173-3.709L147.98 142.81a20.1 20.1 0 0 0-12.109 15.554l-2.385 16.197a19.979 19.979 0 0 0 14.71 22.258l19.588 5.14a83.82 83.82 0 0 1-100.036-15.513z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'GlobeIcon',
}
</script>

View File

@@ -0,0 +1,21 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 16 16"
focusable="false"
>
<path
fill-rule="evenodd"
d="M1.5 1.75a.75.75 0 0 0-1.5 0v12.5c0 .414.336.75.75.75h14.5a.75.75 0 0 0 0-1.5H1.5V1.75zm14.28 2.53a.75.75 0 0 0-1.06-1.06L10 7.94L7.53 5.47a.75.75 0 0 0-1.06 0L3.22 8.72a.75.75 0 0 0 1.06 1.06L7 7.06l2.47 2.47a.75.75 0 0 0 1.06 0l5.25-5.25z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'GraphIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M208 227.999h-.002l-48.007-.006a20.023 20.023 0 0 1-19.997-20v-44.001h-24v44a20.023 20.023 0 0 1-19.998 20L48.003 228a20 20 0 0 1-20.003-20v-92.46a20.036 20.036 0 0 1 6.547-14.8l79.992-72.733a19.922 19.922 0 0 1 26.908-.002l80.007 72.736a20.046 20.046 0 0 1 6.546 14.8V208a20 20 0 0 1-20 20zm-44.006-24.006L204 204v-86.69L127.994 48.21L52 117.308v86.69l39.994-.005v-44.001a20.023 20.023 0 0 1 20-20h32a20.023 20.023 0 0 1 20 20z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'HomeIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M85.568 153.446l67.883-67.882a12 12 0 1 1 16.97 16.971l-67.882 67.882a12 12 0 1 1-16.971-16.97zm50.91 16.979l-28.284 28.284a36 36 0 0 1-50.912-50.911l28.284-28.284a12 12 0 0 0-16.97-16.971L40.31 130.827a60 60 0 0 0 84.854 84.853l28.284-28.284a12 12 0 0 0-16.971-16.971zm79.201-130.113a60.068 60.068 0 0 0-84.853 0l-28.284 28.285a12 12 0 0 0 16.97 16.97l28.284-28.284a36 36 0 0 1 50.913 50.911l-28.285 28.285a12 12 0 0 0 16.971 16.97l28.284-28.284a60 60 0 0 0 0-84.853z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'LinkIco',
}
</script>

View File

@@ -0,0 +1,19 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
>
<path
d="M88.687 103.314A16 16 0 1 1 116 92v.005a16.001 16.001 0 0 1-27.314 11.31zM228 48v160a20.022 20.022 0 0 1-20 20H48a20.022 20.022 0 0 1-20-20V48a20.022 20.022 0 0 1 20-20h160a20.022 20.022 0 0 1 20 20zm-24 116.97l-35.999-36l-41.858 41.86a20.025 20.025 0 0 1-28.285 0L80 152.97l-28 28V204h152zM204 52H52v95.03l13.858-13.858a20.023 20.023 0 0 1 28.285 0L112 151.03l41.857-41.858a20.025 20.025 0 0 1 28.285 0L204 131.029z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'MediaIcon',
}
</script>

View File

@@ -0,0 +1,21 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 16 16"
focusable="false"
>
<g fill="currentColor">
<path
d="M6 .278a.768.768 0 0 1 .08.858a7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277c.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316a.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71C0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"
/>
</g>
</svg>
</template>
<script>
export default {
name: 'MoonIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M92 108a12 12 0 0 1 12-12h72a12 12 0 0 1 0 24h-72a12 12 0 0 1-12-12zm12 52h72a12 12 0 0 0 0-24h-72a12 12 0 0 0 0 24zm132-96v120a28.031 28.031 0 0 1-28 28H36a32.037 32.037 0 0 1-32-32V88a12 12 0 0 1 24 0v92a8 8 0 0 0 16 0V64a20.022 20.022 0 0 1 20-20h152a20.022 20.022 0 0 1 20 20zm-24 4H68v112a31.927 31.927 0 0 1-1.013 8H208a4.004 4.004 0 0 0 4-4z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'NewspaperIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M234.834 89.861l-68.686-68.686a20.023 20.023 0 0 0-28.284 0l-24.505 24.504l-57.246 21.467A20.061 20.061 0 0 0 43.41 82.585L20.169 222.03c-.015.09-.016.18-.028.27a12.057 12.057 0 0 0-.12 1.385c-.008.307-.002.612.013.919c.007.132.009.266.02.398a12.011 12.011 0 0 0 .503 2.582c.03.097.068.19.101.287c.11.322.232.641.37.956c.05.115.104.228.158.342q.208.435.453.855c.067.114.131.228.201.34c.174.278.364.548.563.814c.074.098.142.2.219.297a12.089 12.089 0 0 0 .874.985c.009.01.016.02.025.028a11.916 11.916 0 0 0 .894.808c.265.217.54.414.818.605c.044.03.085.066.13.096a11.91 11.91 0 0 0 3.145 1.483l.026.01c.329.098.662.178.996.248c.05.011.097.026.146.036c.309.06.62.102.933.139c.076.008.151.025.228.032q.575.057 1.153.057h.019c.355 0 .711-.018 1.068-.05c.126-.012.252-.032.378-.047c.175-.021.35-.036.524-.065l139.445-23.24a20.056 20.056 0 0 0 15.439-12.705l21.466-57.245l24.505-24.504a20.023 20.023 0 0 0 0-28.285zm-67.632 99.445L66.965 206.01l33.524-33.529a36.014 36.014 0 1 0-16.97-16.97l-33.52 33.524L66.704 88.808l50.28-18.856l69.073 69.072zM104 140a12 12 0 1 1 12 12a12.013 12.013 0 0 1-12-12zm96.006-20.968l-63.03-63.03l15.03-15.028l63.03 63.029z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'PenIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M226.829 73.858l-44.687-44.686a20 20 0 0 0-28.284 0l-26.343 26.342l-.001.002l-93.655 93.655A19.866 19.866 0 0 0 28 163.313V208a20.023 20.023 0 0 0 20 20h48a12 12 0 0 0 8.485-3.515L226.83 102.142a20.023 20.023 0 0 0 0-28.284zM91.029 204H52v-39.03l84-84L175.03 120zM192 103.03L152.97 64L168 48.97L207.03 88z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'PencilIcon',
}
</script>

View File

@@ -0,0 +1,31 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 24 24"
focusable="false"
>
<g
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M9 12V8" />
<path d="M15 12v-2" />
<path d="M12 12v-1" />
<path d="M3 4h18" />
<path d="M4 4v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4" />
<path d="M12 16v4" />
<path d="M9 20h6" />
</g>
</svg>
</template>
<script>
export default {
name: 'PresentationIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M187.264 235.988a20.215 20.215 0 0 1-10.84-3.165L128 202.143l-44.86 28.421a21.78 21.78 0 0 1-24.569-.772a22.35 22.35 0 0 1-8.527-23.74l13.015-51.2l-43.614-36.3a20.639 20.639 0 0 1-6.409-22.372a20.34 20.34 0 0 1 18.068-14.13l56.749-3.683l21.217-53.428a20.317 20.317 0 0 1 37.86 0l21.217 53.428l56.749 3.683a20.34 20.34 0 0 1 18.068 14.13a20.638 20.638 0 0 1-6.41 22.372l-43.613 36.3l14.074 55.362a20.615 20.615 0 0 1-7.871 21.895a20.2 20.2 0 0 1-11.88 3.879zm2.005-23.438zM128 177.469a20.237 20.237 0 0 1 10.833 3.127l42.78 27.103l-12.43-48.894a20.834 20.834 0 0 1 6.792-21.056l38.812-32.305l-50.51-3.278a20.517 20.517 0 0 1-17.624-12.898L128 42.297L109.346 89.27a20.515 20.515 0 0 1-17.622 12.896l-50.511 3.278l38.813 32.305a20.833 20.833 0 0 1 6.791 21.055L74.39 207.698l42.778-27.102A20.235 20.235 0 0 1 128 177.469zm-2.01 23.4l.002.003l-.003-.003z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'StarIcon',
}
</script>

View File

@@ -0,0 +1,52 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 512 512"
focusable="false"
>
<path
d="M256 118a22 22 0 0 1-22-22V48a22 22 0 0 1 44 0v48a22 22 0 0 1-22 22z"
fill="currentColor"
/>
<path
d="M256 486a22 22 0 0 1-22-22v-48a22 22 0 0 1 44 0v48a22 22 0 0 1-22 22z"
fill="currentColor"
/>
<path
d="M369.14 164.86a22 22 0 0 1-15.56-37.55l33.94-33.94a22 22 0 0 1 31.11 31.11l-33.94 33.94a21.93 21.93 0 0 1-15.55 6.44z"
fill="currentColor"
/>
<path
d="M108.92 425.08a22 22 0 0 1-15.55-37.56l33.94-33.94a22 22 0 1 1 31.11 31.11l-33.94 33.94a21.94 21.94 0 0 1-15.56 6.45z"
fill="currentColor"
/>
<path
d="M464 278h-48a22 22 0 0 1 0-44h48a22 22 0 0 1 0 44z"
fill="currentColor"
/>
<path
d="M96 278H48a22 22 0 0 1 0-44h48a22 22 0 0 1 0 44z"
fill="currentColor"
/>
<path
d="M403.08 425.08a21.94 21.94 0 0 1-15.56-6.45l-33.94-33.94a22 22 0 0 1 31.11-31.11l33.94 33.94a22 22 0 0 1-15.55 37.56z"
fill="currentColor"
/>
<path
d="M142.86 164.86a21.89 21.89 0 0 1-15.55-6.44l-33.94-33.94a22 22 0 0 1 31.11-31.11l33.94 33.94a22 22 0 0 1-15.56 37.55z"
fill="currentColor"
/>
<path
d="M256 358a102 102 0 1 1 102-102a102.12 102.12 0 0 1-102 102z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'SunIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M242.828 124.034L138.376 19.582a19.956 19.956 0 0 0-18.064-5.47L39.656 30.243a11.998 11.998 0 0 0-9.413 9.413l-16.13 80.656a19.96 19.96 0 0 0 5.469 18.064l104.451 104.451a19.999 19.999 0 0 0 28.285.001l90.51-90.509a19.999 19.999 0 0 0 0-28.285zM138.177 223.03L38.065 122.92l14.143-70.711l70.711-14.143l100.11 100.112zM100 84a16 16 0 1 1-16-16a16.018 16.018 0 0 1 16 16z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'TagIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M242.715 210.634l-56-112a12 12 0 0 0-21.465 0l-20.504 41.006a84.114 84.114 0 0 1-38.76-13.44A107.568 107.568 0 0 0 131.312 68h20.67a12 12 0 0 0 0-24h-52V32a12 12 0 0 0-24 0v12h-52a12 12 0 0 0 0 24h83.13a83.665 83.665 0 0 1-19.142 42.332A83.593 83.593 0 0 1 75.6 90.992a12 12 0 0 0-21.809 10.019a107.462 107.462 0 0 0 16.2 25.216A83.49 83.49 0 0 1 23.982 140a12 12 0 1 0 0 24a107.423 107.423 0 0 0 64.031-21.085a108.427 108.427 0 0 0 45.386 19.42l-24.15 48.299a12 12 0 1 0 21.466 10.732L143.4 196h65.167l12.684 25.366a12 12 0 1 0 21.465-10.732zM155.4 172l20.583-41.167L196.566 172z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'TranslateIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M215.996 48H180V36a28.031 28.031 0 0 0-28-28h-48a28.031 28.031 0 0 0-28 28v12H39.996a12 12 0 0 0 0 24h4v136a20.023 20.023 0 0 0 20 20h128a20.023 20.023 0 0 0 20-20V72h4a12 12 0 0 0 0-24zM100 36a4.005 4.005 0 0 1 4-4h48a4.005 4.005 0 0 1 4 4v12h-56zm87.996 168h-120V72h120zM116 104v64a12 12 0 0 1-24 0v-64a12 12 0 0 1 24 0zm48 0v64a12 12 0 0 1-24 0v-64a12 12 0 0 1 24 0z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'TrashIcon',
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<svg
class="inline"
width="1.5em"
height="1.5em"
viewBox="0 0 256 256"
focusable="false"
>
<path
d="M129.21 156.914a64 64 0 1 0-82.42 0a100.18 100.18 0 0 0-40.606 33.574a12 12 0 1 0 19.624 13.817a76.017 76.017 0 0 1 124.38-.005a12 12 0 1 0 19.624-13.818a100.179 100.179 0 0 0-40.602-33.568zM48 108a40 40 0 1 1 40 40a40.045 40.045 0 0 1-40-40zm200.432 99.203a11.998 11.998 0 0 1-16.721-2.903a76.164 76.164 0 0 0-62.189-32.3a12 12 0 0 1 0-24a40 40 0 1 0-10.86-78.511a12 12 0 0 1-6.499-23.104a63.978 63.978 0 0 1 58.568 110.53a100.18 100.18 0 0 1 40.603 33.567a12 12 0 0 1-2.902 16.72z"
fill="currentColor"
/>
</svg>
</template>
<script>
export default {
name: 'UsersIcon',
}
</script>

13
src/layouts/404.vue Normal file
View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
const router = useRouter()
const { t } = useI18n()
</script>
<template>
<main class="px-4 py-10 text-center text-teal-700 dark:text-gray-200">
ERROR
</main>
</template>

3
src/layouts/auth.vue Normal file
View File

@@ -0,0 +1,3 @@
<template>
<router-view />
</template>

22
src/layouts/default.vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<main class="flex">
<SideBar />
<div
class="w-full h-full duration-300"
:class="opened ? 'ml-64' : 'ml-20'"
>
<Header />
<div class="mt-16 p-4">
<router-view />
<Footer />
</div>
</div>
</main>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
const store = useStore()
const opened = computed(() => store.getters.isOpened)
</script>

60
src/logic/auth.ts Normal file
View File

@@ -0,0 +1,60 @@
import { store } from '~/store'
import { useAxios } from '~/logic/axios'
export interface User {
id: number
username: string
email: string
}
interface Credentials {
email: string
password: string
}
interface Auth {
readonly isAuthenticated: Boolean
readonly user: User
readonly login: (credentials: Credentials) => Promise<void>
readonly logout: () => Promise<void>
}
export const useAuth = (): Auth => {
const axios = useAxios()
const router = useRouter()
const user = computed(() => store.getters.getUser)
const isAuthenticated = computed(() => store.getters.isAuthenticated)
const login = async(
credentials: Credentials,
) => {
const response = await axios.post('/auth/web/login', {
email: credentials.email,
password: credentials.password,
})
if (response.status === 200) {
store.commit('SET_AUTHENTICATED', true)
store.commit('SET_USER', {
id: response.data.user.id,
email: response.data.user.email,
username: response.data.user.username,
})
await router.push({ name: 'index' })
}
}
const logout = async() => {
const response = await axios.post('/auth/web/logout')
if (response.status === 200) {
await store.commit('SET_AUTHENTICATED', false)
await store.commit('RESET_USER')
await router.push({ name: 'login' })
}
}
return {
login,
logout,
isAuthenticated: isAuthenticated.value,
user: user.value,
}
}

10
src/logic/axios.ts Normal file
View File

@@ -0,0 +1,10 @@
import axios from 'axios'
const instance = axios.create({
baseURL: String(import.meta.env.VITE_ENV) === 'development' ? String(import.meta.env.VITE_API_URL_DEV) : String(import.meta.env.VITE_API_URL),
withCredentials: true,
})
export const useAxios = () => {
return instance
}

2
src/logic/dark.ts Normal file
View File

@@ -0,0 +1,2 @@
export const isDark = useDark()
export const toggleDark = useToggle(isDark)

1
src/logic/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './dark'

27
src/main.ts Normal file
View File

@@ -0,0 +1,27 @@
// register vue composition api globally
import 'vue-global-api'
import { ViteSSG } from 'vite-ssg'
import generatedRoutes from 'virtual:generated-pages'
import { setupLayouts } from 'virtual:generated-layouts'
import App from './App.vue'
// windicss layers
import 'virtual:windi-base.css'
import 'virtual:windi-components.css'
// your custom styles here
import './styles/main.css'
// windicss utilities should be the last style import
import 'virtual:windi-utilities.css'
// windicss devtools support (dev only)
import 'virtual:windi-devtools'
const routes = setupLayouts(generatedRoutes)
export const createApp = ViteSSG(
App,
{ routes },
(ctx) => {
// install all modules under `modules/`
Object.values(import.meta.globEager('./modules/*.ts')).map(i => i.install?.(ctx))
},
)

11
src/modules/README.md Normal file
View File

@@ -0,0 +1,11 @@
## Modules
A custom user module system. Place a `.ts` file with the following template, it will be installed automatically.
```ts
import { UserModule } from '~/types'
export const install: UserModule = ({ app, router, isClient }) => {
// do something
}
```

24
src/modules/auth.ts Normal file
View File

@@ -0,0 +1,24 @@
import { store } from '~/store'
import { useAuth } from '~/logic/auth'
import { UserModule } from '~/types'
export const install: UserModule = ({ isClient, router }) => {
if (isClient) {
router.beforeEach(async(from, to, next) => {
const { isAuthenticated, user } = useAuth()
if (user === null)
await store.dispatch('FETCH_USER')
// redirect to login page if not connected
if (from.meta.auth && !isAuthenticated)
next({ name: 'login' })
// redirect to home page if connected
else if (from.meta.guest && isAuthenticated)
next({ name: 'index' })
else next()
})
}
}

21
src/modules/i18n.ts Normal file
View File

@@ -0,0 +1,21 @@
import { createI18n } from 'vue-i18n'
import { UserModule } from '~/types'
const messages = Object.fromEntries(
Object.entries(
import.meta.globEager('../../locales/*.json'))
.map(([key, value]) => {
return [key.slice(14, -5), value.default]
}),
)
export const install: UserModule = ({ app }) => {
const i18n = createI18n({
legacy: false,
locale: 'en',
messages,
fallbackLocale: 'en',
})
app.use(i18n)
}

9
src/modules/nprogress.ts Normal file
View File

@@ -0,0 +1,9 @@
import NProgress from 'nprogress'
import { UserModule } from '~/types'
export const install: UserModule = ({ isClient, router }) => {
if (isClient) {
router.beforeEach(() => { NProgress.start() })
router.afterEach(() => { NProgress.done() })
}
}

12
src/modules/pwa.ts Normal file
View File

@@ -0,0 +1,12 @@
import { UserModule } from '~/types'
// https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available
export const install: UserModule = ({ isClient, router }) => {
if (!isClient)
return
router.isReady().then(async() => {
const { registerSW } = await import('virtual:pwa-register')
registerSW({ immediate: true })
})
}

7
src/modules/store.ts Normal file
View File

@@ -0,0 +1,7 @@
import { UserModule } from '~/types'
import { store } from '~/store'
export const install: UserModule = ({ isClient, app }) => {
if (isClient)
app.use(store)
}

15
src/pages/index.vue Normal file
View File

@@ -0,0 +1,15 @@
<template>
<div>
{{ t('hello') }} <br />
</div>
</template>
<route lang="yaml">
meta:
layout: default
auth: true
</route>
<script setup lang="ts">
const { t } = useI18n()
</script>

100
src/pages/login.vue Normal file
View File

@@ -0,0 +1,100 @@
<template>
<main class="p-12 h-screen w-screen bg-gradient-to-r from-amber-400 to-pink-400 dark:(bg-gradient-to-r from-blue-900 to-purple-900) flex items-center justify-center">
<div class="relative bg-white dark:bg-gray-900 p-4 rounded-xl flex flex-col justify-between w-full lg:w-1/3">
<div class="absolute top-5 left-5 flex">
<ColorModeButton class="mr-2" />
<LanguageButton />
</div>
<div class="mb-8">
<div class="text-center mt-8 mb-12">
<Logo />
</div>
<div class="w-full">
<form class="w-full">
<div class="w-full mb-8">
<input
id="email"
v-model="form.email"
required
:placeholder="t('login.form.email')"
type="text"
class="w-full py-2 px-4 border-gray-200 focus:bg-gray-100 duration-300 dark:(border-gray-800 bg-gray-900 focus:bg-gray-800) border-2 rounded-lg"
/>
</div>
<div class=" w-full">
<input
id="password"
v-model="form.password"
required
:placeholder="t('login.form.password')"
type="password"
autocomplete="suggested"
class="w-full py-2 px-4 border-gray-200 focus:bg-gray-100 duration-300 dark:(border-gray-800 bg-gray-900 focus:bg-gray-800) border-2 rounded-lg"
/>
</div>
<div class="flex justify-between my-2">
<div class="font-thin text-sm duration-300" :class="error ? 'text-red-500': 'text-transparent'">
{{ t('login.credentials') }}
</div>
<div class="text-sm duration-300 cursor-pointer border-b-2 border-gray-200 hover:(border-red-400 text-red-400 dark:text-amber-400 dark:border-amber-400) text-gray-400">
{{ t('login.forget') }}
</div>
</div>
<div class="my-8 flex justify-center">
<button
class="w-full font-bold px-8 py-2 border-2 rounded-lg border-red-400 dark:border-amber-400 text-red-400 dark:text-amber-400 hover:(bg-red-400 dark:bg-amber-400 text-white) hover:dark:text-black duration-300 cursor-pointer"
@click.prevent="handleLogin()"
>
{{ t('login.form.login') }}
</button>
</div>
</form>
</div>
<div class="mt-2 flex justify-center">
<div class="text-sm cursor-pointer">
{{ t('login.no_account') }} <span class="duration-300 border-b-2 border-gray-200 hover:(border-red-400 text-red-400 dark:text-amber-400 dark:border-amber-400) text-gray-400">{{ t('login.ask') }}</span>
</div>
</div>
</div>
<div class="flex justify-center flex-col items-center">
<p class="block text-xs text-gray-700 dark:text-gray-300">
{{ t('login.secure') }}
</p>
<p class="text-xs text-gray-700 dark:text-gray-300">
{{ t('login.credits') }}
</p>
</div>
</div>
</main>
</template>
<script setup lang="ts">
import { useStore } from 'vuex'
import { useAuth } from '~/logic/auth'
const store = useStore()
const { t } = useI18n()
const form = ref({ email: '', password: '' })
const error = ref(false)
const { login } = useAuth()
const handleLogin = () => {
login({
email: form.value.email,
password: form.value.password,
}).catch(() => {
form.value.password = ''
error.value = true
setTimeout(() => error.value = false, 7000)
})
}
</script>
<route lang="yaml">
meta:
layout: auth
guest: true
</route>
<style scoped lang="scss">
</style>

58
src/pages/users/index.vue Normal file
View File

@@ -0,0 +1,58 @@
<template>
<main class="mb-8 duration-300 rounded-xl bg-white shadow-white dark:(bg-dark-900 shadow-black) h-100 p-4">
<PageTitle title="Users">
<UsersIcon />
</PageTitle>
<div class="w-full">
<div class="table empty-cells-hidden w-full">
<div class="table-caption">Liste des utilisateurs</div>
<div class="table-header-group">
<div class="table-row">
<div class="table-cell">Id</div>
<div class="table-cell">Nom</div>
<div class="table-cell">Email</div>
<div class="table-cell">Actions</div>
</div>
</div>
<div class="table-row-group">
<div class="table-row" v-for="user in users" :key="user.id">
<div class="table-cell">{{user.id}}</div>
<div class="table-cell">{{user.username}}</div>
<div class="table-cell">{{user.email}}</div>
<div class="table-cell">
<div class="flex">
<DeleteButton class="mr-2" />
<UpdateButton />
</div>
</div>
</div>
</div>
<div class="table-footer-group">
total : {{ users.length }}
</div>
</div>
</div>
</main>
</template>
<route lang="yaml">
meta:
layout: default
auth: true
</route>
<script setup lang="ts">
import { useAxios } from '~/logic/axios'
const axios = useAxios()
const users = ref<Array<any>>([])
onMounted(async() => {
const response = await axios.get('/users')
if (response.status === 200) {
response.data.users.forEach((user: any) => {
return users.value.push(user)
})
}
})
</script>

18
src/shims.d.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
declare interface Window {
// extend the window
}
// with vite-plugin-md, markdowns can be treat as Vue components
declare module '*.md' {
import { ComponentOptions } from 'vue'
const component: ComponentOptions
export default component
}
declare module '@vue/runtime-core' {
import { Store } from 'vuex'
interface ComponentCustomProperties {
$store: Store
}
}

10
src/store/index.ts Normal file
View File

@@ -0,0 +1,10 @@
import { createStore } from 'vuex'
import auth from '~/store/modules/auth'
import sidebar from '~/store/modules/sidebar'
export const store = createStore({
modules: {
auth,
sidebar,
},
})

54
src/store/modules/auth.ts Normal file
View File

@@ -0,0 +1,54 @@
import { Module } from 'vuex'
import { User } from '~/logic/auth'
import { useAxios } from '~/logic/axios'
const axios = useAxios()
const authModule: Module<any, any> = {
state: {
user: null,
authenticated: false,
},
getters: {
getUser(state: any) {
return state.user
},
isAuthenticated(state: any) {
return state.authenticated
},
},
mutations: {
SET_USER(state: any, user: User) {
state.user = user
},
RESET_USER(state: any) {
state.user = null
},
SET_AUTHENTICATED(state: any, value: boolean) {
state.authenticated = value
},
},
actions: {
async FETCH_USER({ commit, state }) {
let user: User | null = state.user
if (!user) {
try {
const response = await axios.get('/auth/me')
if (response.status === 200) {
user = {
id: response.data.user.id,
username: response.data.user.username,
email: response.data.user.email,
}
}
}
catch (error) {
user = null
}
}
commit('SET_USER', user)
commit('SET_AUTHENTICATED', true)
},
},
}
export default authModule

View File

@@ -0,0 +1,20 @@
import { Module } from 'vuex'
const sidebarModule: Module<any, any> = {
state: {
opened: true,
},
getters: {
isOpened(state: any) {
return state.opened
},
},
mutations: {
SET_OPENED(state: any, value: boolean) {
state.opened = value
},
},
actions: {},
}
export default sidebarModule

33
src/styles/main.css Executable file
View File

@@ -0,0 +1,33 @@
@import './markdown.css';
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
}
html {
@apply duration-300 bg-gray-100;
}
html.dark {
@apply bg-dark-800 text-white;
}
#nprogress {
pointer-events: none;
}
#nprogress .bar {
@apply bg-teal-600 opacity-75;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}

54
src/styles/markdown.css Normal file
View File

@@ -0,0 +1,54 @@
/* https://github.com/antfu/prism-theme-vars */
@import 'prism-theme-vars/base.css';
.prose {
--prism-font-family: 'Input Mono', monospace;
}
html:not(.dark) .prose {
--prism-foreground: #393a34;
--prism-background: #fbfbfb;
--prism-comment: #a0ada0;
--prism-string: #b56959;
--prism-literal: #2f8a89;
--prism-number: #296aa3;
--prism-keyword: #1c6b48;
--prism-function: #6c7834;
--prism-boolean: #1c6b48;
--prism-constant: #a65e2b;
--prism-deleted: #a14f55;
--prism-class: #2993a3;
--prism-builtin: #ab5959;
--prism-property: #b58451;
--prism-namespace: #b05a78;
--prism-punctuation: #8e8f8b;
--prism-decorator: #bd8f8f;
--prism-regex: #ab5e3f;
--prism-json-property: #698c96;
}
html.dark .prose {
--prism-foreground: #d4cfbf;
--prism-background: #151515;
--prism-comment: #758575;
--prism-string: #d48372;
--prism-literal: #429988;
--prism-keyword: #4d9375;
--prism-boolean: #1c6b48;
--prism-number: #6394bf;
--prism-variable: #c2b36e;
--prism-function: #a1b567;
--prism-deleted: #a14f55;
--prism-class: #54b1bf;
--prism-builtin: #e0a569;
--prism-property: #dd8e6e;
--prism-namespace: #db889a;
--prism-punctuation: #858585;
--prism-decorator: #bd8f8f;
--prism-regex: #ab5e3f;
--prism-json-property: #6b8b9e;
--prism-line-number: #888888;
--prism-line-number-gutter: #eeeeee;
--prism-line-highlight-background: #444444;
--prism-selection-background: #444444;
}

3
src/types.ts Normal file
View File

@@ -0,0 +1,3 @@
import { ViteSSGContext } from 'vite-ssg'
export type UserModule = (ctx: ViteSSGContext) => void

27
tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"baseUrl": ".",
"module": "ESNext",
"target": "es2016",
"lib": ["DOM", "ESNext"],
"strict": true,
"esModuleInterop": true,
"incremental": false,
"skipLibCheck": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"types": [
"vite/client",
"vite-plugin-pages/client",
"vite-plugin-vue-layouts/client",
"@types/axios",
],
"paths": {
"~/*": ["src/*"]
}
},
"exclude": ["dist", "node_modules"]
}

158
vite.config.ts Normal file
View File

@@ -0,0 +1,158 @@
import path from 'path'
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import Pages from 'vite-plugin-pages'
import Layouts from 'vite-plugin-vue-layouts'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import Components from 'unplugin-vue-components/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Markdown from 'vite-plugin-md'
import WindiCSS from 'vite-plugin-windicss'
import { VitePWA } from 'vite-plugin-pwa'
import VueI18n from '@intlify/vite-plugin-vue-i18n'
import Prism from 'markdown-it-prism'
import LinkAttributes from 'markdown-it-link-attributes'
const markdownWrapperClasses = 'prose prose-sm m-auto text-left'
export default defineConfig({
resolve: {
alias: {
'~/': `${path.resolve(__dirname, 'src')}/`,
},
},
plugins: [
Vue({
include: [/\.vue$/, /\.md$/],
}),
// https://github.com/hannoeru/vite-plugin-pages
Pages({
extensions: ['vue', 'md'],
}),
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
Layouts(),
// https://github.com/antfu/unplugin-auto-import
AutoImport({
imports: [
'vue',
'vue-router',
'vue-i18n',
'@vueuse/head',
'@vueuse/core',
],
dts: true,
}),
// https://github.com/antfu/unplugin-vue-components
Components({
// allow auto load markdown components under `./src/components/`
extensions: ['vue', 'md'],
dts: true,
deep: true,
// allow auto import and register components used in markdown
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
// custom resolvers
resolvers: [
// auto import icons
// https://github.com/antfu/unplugin-icons
IconsResolver({
componentPrefix: '',
// enabledCollections: ['carbon']
}),
],
}),
// https://github.com/antfu/unplugin-icons
Icons(),
// https://github.com/antfu/vite-plugin-windicss
WindiCSS({
safelist: markdownWrapperClasses,
}),
// https://github.com/antfu/vite-plugin-md
// Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite
Markdown({
wrapperClasses: markdownWrapperClasses,
headEnabled: true,
markdownItSetup(md) {
// https://prismjs.com/
md.use(Prism)
md.use(LinkAttributes, {
pattern: /^https?:\/\//,
attrs: {
target: '_blank',
rel: 'noopener',
},
})
},
}),
// https://github.com/antfu/vite-plugin-pwa
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.svg', 'robots.txt', 'safari-pinned-tab.svg'],
manifest: {
name: 'Vitesse',
short_name: 'Vitesse',
theme_color: '#ffffff',
icons: [
{
src: '/pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: '/pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable',
},
],
},
}),
// https://github.com/intlify/bundle-tools/tree/main/packages/vite-plugin-vue-i18n
VueI18n({
runtimeOnly: true,
compositionOnly: true,
include: [path.resolve(__dirname, 'locales/**')],
}),
],
server: {
fs: {
strict: true,
},
},
// https://github.com/antfu/vite-ssg
ssgOptions: {
script: 'async',
formatting: 'minify',
},
optimizeDeps: {
include: [
'vue',
'vue-router',
'@vueuse/core',
],
exclude: [
'vue-demi',
],
},
})

763
windi.config.ts Normal file
View File

@@ -0,0 +1,763 @@
import { defineConfig } from 'windicss/helpers'
import colors from 'windicss/colors'
import typography from 'windicss/plugin/typography'
export default defineConfig({
darkMode: 'class',
attributify: true,
plugins: [
typography(),
],
theme: {
extend: {
typography: {
DEFAULT: {
css: {
maxWidth: '65ch',
color: 'inherit',
a: {
'color': 'inherit',
'opacity': 0.75,
'fontWeight': '500',
'textDecoration': 'underline',
'&:hover': {
opacity: 1,
color: colors.teal[600],
},
},
b: { color: 'inherit' },
strong: { color: 'inherit' },
em: { color: 'inherit' },
h1: { color: 'inherit' },
h2: { color: 'inherit' },
h3: { color: 'inherit' },
h4: { color: 'inherit' },
code: { color: 'inherit' },
},
},
},
},
screens: {
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
},
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
rose: colors.rose,
pink: colors.pink,
fuchsia: colors.fuchsia,
purple: colors.purple,
violet: colors.violet,
indigo: colors.indigo,
blue: colors.blue,
lightBlue: colors.lightBlue,
cyan: colors.cyan,
teal: colors.teal,
emerald: colors.emerald,
green: colors.green,
lime: colors.lime,
yellow: colors.yellow,
amber: colors.amber,
orange: colors.orange,
red: colors.red,
warmGray: colors.warmGray,
trueGray: colors.trueGray,
gray: colors.gray,
coolGray: colors.coolGray,
blueGray: colors.blueGray,
dark: colors.dark,
light: colors.light,
},
spacing: {
'px': '1px',
'0': '0px',
'0.5': '0.125rem',
'1': '0.25rem',
'1.5': '0.375rem',
'2': '0.5rem',
'2.5': '0.625rem',
'3': '0.75rem',
'3.5': '0.875rem',
'4': '1rem',
'5': '1.25rem',
'6': '1.5rem',
'7': '1.75rem',
'8': '2rem',
'9': '2.25rem',
'10': '2.5rem',
'11': '2.75rem',
'12': '3rem',
'14': '3.5rem',
'16': '4rem',
'20': '5rem',
'24': '6rem',
'28': '7rem',
'32': '8rem',
'36': '9rem',
'40': '10rem',
'44': '11rem',
'48': '12rem',
'52': '13rem',
'56': '14rem',
'60': '15rem',
'64': '16rem',
'72': '18rem',
'80': '20rem',
'96': '24rem',
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'1/5': '20%',
'2/5': '40%',
'3/5': '60%',
'4/5': '80%',
'1/6': '16.666667%',
'2/6': '33.333333%',
'3/6': '50%',
'4/6': '66.666667%',
'5/6': '83.333333%',
'1/12': '8.333333%',
'2/12': '16.666667%',
'3/12': '25%',
'4/12': '33.333333%',
'5/12': '41.666667%',
'6/12': '50%',
'7/12': '58.333333%',
'8/12': '66.666667%',
'9/12': '75%',
'10/12': '83.333333%',
'11/12': '91.666667%',
'full': '100%',
},
animation: {
none: 'none',
spin: 'spin 1s linear infinite',
ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
bounce: 'bounce 1s infinite',
},
backgroundColor: theme => theme('colors'),
backgroundImage: {
'none': 'none',
'gradient-to-t': 'linear-gradient(to top, var(--tw-gradient-stops))',
'gradient-to-tr': 'linear-gradient(to top right, var(--tw-gradient-stops))',
'gradient-to-r': 'linear-gradient(to right, var(--tw-gradient-stops))',
'gradient-to-br': 'linear-gradient(to bottom right, var(--tw-gradient-stops))',
'gradient-to-b': 'linear-gradient(to bottom, var(--tw-gradient-stops))',
'gradient-to-bl': 'linear-gradient(to bottom left, var(--tw-gradient-stops))',
'gradient-to-l': 'linear-gradient(to left, var(--tw-gradient-stops))',
'gradient-to-tl': 'linear-gradient(to top left, var(--tw-gradient-stops))',
},
backgroundOpacity: theme => theme('opacity'),
backgroundPosition: {
'bottom': 'bottom',
'center': 'center',
'left': 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
'right': 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
'top': 'top',
},
backgroundSize: {
auto: 'auto',
cover: 'cover',
contain: 'contain',
},
borderColor: theme => ({
...theme('colors'),
DEFAULT: theme('colors.gray.200', 'currentColor'),
}),
borderOpacity: theme => theme('opacity'),
borderRadius: {
'none': '0px',
'sm': '0.125rem',
'DEFAULT': '0.25rem',
'md': '0.375rem',
'lg': '0.5rem',
'xl': '0.75rem',
'2xl': '1rem',
'3xl': '1.5rem',
'full': '9999px',
},
borderWidth: {
DEFAULT: '1px',
0: '0px',
2: '2px',
4: '4px',
6: '6px',
8: '8px',
},
boxShadow: {
'sm': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
'DEFAULT': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
'md': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
'xl': '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
'inner': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
'white': '2px 4px 8px rgb(0 0 0 / 8%)',
'black': '2px 4px 12px rgb(255 255 255 / 8%)',
'none': 'none',
},
container: {},
cursor: {
'auto': 'auto',
'default': 'default',
'pointer': 'pointer',
'wait': 'wait',
'text': 'text',
'move': 'move',
'not-allowed': 'not-allowed',
},
divideColor: theme => theme('borderColor'),
divideOpacity: theme => theme('borderOpacity'),
divideWidth: theme => theme('borderWidth'),
fill: theme => ({
'current': 'currentColor',
'adonis-dark': theme('colors.purple.500'),
'adonis-light': theme('colors.purple.800'),
'twitter-dark': theme('colors.cyan.400'),
'twitter-light': theme('colors.cyan.500'),
'heart': theme('colors.red.500'),
}),
flex: {
1: '1 1 0%',
auto: '1 1 auto',
initial: '0 1 auto',
none: 'none',
},
flexGrow: {
0: '0',
DEFAULT: '1',
},
flexShrink: {
0: '0',
DEFAULT: '1',
},
fontFamily: {
sans: [
'Raleway',
'ui-sans-serif',
'system-ui',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'"Noto Sans"',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
'"Noto Color Emoji"',
],
serif: ['Raleway', 'ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
mono: [
'Raleway',
'ui-monospace',
'SFMono-Regular',
'Menlo',
'Monaco',
'Consolas',
'"Liberation Mono"',
'"Courier New"',
'monospace',
],
color: ['Roboto', 'ui-sans-serif', 'system-ui', '-apple-system', 'sans-serif'],
},
fontSize: {
'xs': ['0.75rem', { lineHeight: '1rem' }],
'sm': ['0.875rem', { lineHeight: '1.25rem' }],
'base': ['1rem', { lineHeight: '1.5rem' }],
'lg': ['1.125rem', { lineHeight: '1.75rem' }],
'xl': ['1.25rem', { lineHeight: '1.75rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.25rem' }],
'4xl': ['2.25rem', { lineHeight: '2.5rem' }],
'5xl': ['3rem', { lineHeight: '1' }],
'6xl': ['3.75rem', { lineHeight: '1' }],
'7xl': ['4.5rem', { lineHeight: '1' }],
'8xl': ['6rem', { lineHeight: '1' }],
'9xl': ['8rem', { lineHeight: '1' }],
},
fontWeight: {
thin: '100',
extralight: '200',
light: '300',
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
black: '900',
},
gap: theme => theme('spacing'),
gradientColorStops: theme => theme('colors'),
gridAutoColumns: {
auto: 'auto',
min: 'min-content',
max: 'max-content',
fr: 'minmax(0, 1fr)',
},
gridAutoRows: {
auto: 'auto',
min: 'min-content',
max: 'max-content',
fr: 'minmax(0, 1fr)',
},
gridColumn: {
'auto': 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-7': 'span 7 / span 7',
'span-8': 'span 8 / span 8',
'span-9': 'span 9 / span 9',
'span-10': 'span 10 / span 10',
'span-11': 'span 11 / span 11',
'span-12': 'span 12 / span 12',
'span-full': '1 / -1',
},
gridColumnEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridColumnStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
13: '13',
},
gridRow: {
'auto': 'auto',
'span-1': 'span 1 / span 1',
'span-2': 'span 2 / span 2',
'span-3': 'span 3 / span 3',
'span-4': 'span 4 / span 4',
'span-5': 'span 5 / span 5',
'span-6': 'span 6 / span 6',
'span-full': '1 / -1',
},
gridRowStart: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
gridRowEnd: {
auto: 'auto',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
},
transformOrigin: {
'center': 'center',
'top': 'top',
'top-right': 'top right',
'right': 'right',
'bottom-right': 'bottom right',
'bottom': 'bottom',
'bottom-left': 'bottom left',
'left': 'left',
'top-left': 'top left',
},
gridTemplateColumns: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
7: 'repeat(7, minmax(0, 1fr))',
8: 'repeat(8, minmax(0, 1fr))',
9: 'repeat(9, minmax(0, 1fr))',
10: 'repeat(10, minmax(0, 1fr))',
11: 'repeat(11, minmax(0, 1fr))',
12: 'repeat(12, minmax(0, 1fr))',
},
gridTemplateRows: {
none: 'none',
1: 'repeat(1, minmax(0, 1fr))',
2: 'repeat(2, minmax(0, 1fr))',
3: 'repeat(3, minmax(0, 1fr))',
4: 'repeat(4, minmax(0, 1fr))',
5: 'repeat(5, minmax(0, 1fr))',
6: 'repeat(6, minmax(0, 1fr))',
},
height: theme => ({
auto: 'auto',
...theme('spacing'),
screen: '100vh',
}),
inset: (theme, { negative }) => ({
'auto': 'auto',
...theme('spacing'),
...negative(theme('spacing')),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'full': '100%',
'-1/2': '-50%',
'-1/3': '-33.333333%',
'-2/3': '-66.666667%',
'-1/4': '-25%',
'-2/4': '-50%',
'-3/4': '-75%',
'-full': '-100%',
}),
keyframes: {
spin: {
to: {
transform: 'rotate(360deg)',
},
},
ping: {
'75%, 100%': {
transform: 'scale(2)',
opacity: '0',
},
},
pulse: {
'50%': {
opacity: '.5',
},
},
bounce: {
'0%, 100%': {
transform: 'translateY(-25%)',
animationTimingFunction: 'cubic-bezier(0.8,0,1,1)',
},
'50%': {
transform: 'none',
animationTimingFunction: 'cubic-bezier(0,0,0.2,1)',
},
},
},
letterSpacing: {
tighter: '-0.05em',
tight: '-0.025em',
normal: '0em',
wide: '0.025em',
wider: '0.05em',
widest: '0.1em',
},
lineHeight: {
none: '1',
tight: '1.25',
snug: '1.375',
normal: '1.5',
relaxed: '1.625',
loose: '2',
3: '.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
7: '1.75rem',
8: '2rem',
9: '2.25rem',
10: '2.5rem',
},
listStyleType: {
none: 'none',
disc: 'disc',
decimal: 'decimal',
},
margin: (theme, { negative }) => ({
auto: 'auto',
...theme('spacing'),
...negative(theme('spacing')),
}),
maxHeight: theme => ({
...theme('spacing'),
full: '100%',
screen: '100vh',
}),
maxWidth: (theme, { breakpoints }) => ({
'none': 'none',
'0': '0rem',
'xs': '20rem',
'sm': '24rem',
'md': '28rem',
'lg': '32rem',
'xl': '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
'7xl': '80rem',
'full': '100%',
'min': 'min-content',
'max': 'max-content',
'prose': '65ch',
...breakpoints(theme('screens')),
}),
minHeight: {
0: '0px',
full: '100%',
screen: '100vh',
},
minWidth: {
0: '0px',
full: '100%',
min: 'min-content',
max: 'max-content',
},
objectPosition: {
'bottom': 'bottom',
'center': 'center',
'left': 'left',
'left-bottom': 'left bottom',
'left-top': 'left top',
'right': 'right',
'right-bottom': 'right bottom',
'right-top': 'right top',
'top': 'top',
},
opacity: {
0: '0',
5: '0.05',
10: '0.1',
20: '0.2',
25: '0.25',
30: '0.3',
40: '0.4',
50: '0.5',
60: '0.6',
70: '0.7',
75: '0.75',
80: '0.8',
90: '0.9',
95: '0.95',
100: '1',
},
order: {
first: '-9999',
last: '9999',
none: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: '10',
11: '11',
12: '12',
},
outline: {
none: ['2px solid transparent', '2px'],
white: ['2px dotted white', '2px'],
black: ['2px dotted black', '2px'],
},
padding: theme => theme('spacing'),
placeholderColor: theme => theme('colors'),
placeholderOpacity: theme => theme('opacity'),
ringColor: theme => ({
DEFAULT: theme('colors.blue.500', '#3b82f6'),
...theme('colors'),
}),
ringOffsetColor: theme => theme('colors'),
ringOffsetWidth: {
0: '0px',
1: '1px',
2: '2px',
4: '4px',
8: '8px',
},
ringOpacity: theme => ({
DEFAULT: '0.5',
...theme('opacity'),
}),
ringWidth: {
DEFAULT: '3px',
0: '0px',
1: '1px',
2: '2px',
4: '4px',
8: '8px',
},
rotate: {
'-180': '-180deg',
'-90': '-90deg',
'-45': '-45deg',
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
'-2': '-2deg',
'-1': '-1deg',
'0': '0deg',
'1': '1deg',
'2': '2deg',
'3': '3deg',
'6': '6deg',
'12': '12deg',
'45': '45deg',
'90': '90deg',
'180': '180deg',
},
scale: {
0: '0',
50: '.5',
75: '.75',
90: '.9',
95: '.95',
100: '1',
105: '1.05',
110: '1.1',
125: '1.25',
150: '1.5',
},
skew: {
'-12': '-12deg',
'-6': '-6deg',
'-3': '-3deg',
'-2': '-2deg',
'-1': '-1deg',
'0': '0deg',
'1': '1deg',
'2': '2deg',
'3': '3deg',
'6': '6deg',
'12': '12deg',
},
space: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
}),
stroke: {
current: 'currentColor',
},
strokeWidth: {
0: '0',
1: '1',
2: '2',
},
textColor: theme => theme('colors'),
textOpacity: theme => theme('opacity'),
transitionDuration: {
DEFAULT: '150ms',
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms',
},
transitionDelay: {
75: '75ms',
100: '100ms',
150: '150ms',
200: '200ms',
300: '300ms',
500: '500ms',
700: '700ms',
1000: '1000ms',
},
transitionProperty: {
none: 'none',
all: 'all',
DEFAULT: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',
colors: 'background-color, border-color, color, fill, stroke',
opacity: 'opacity',
shadow: 'box-shadow',
transform: 'transform',
},
transitionTimingFunction: {
'DEFAULT': 'cubic-bezier(0.4, 0, 0.2, 1)',
'linear': 'linear',
'in': 'cubic-bezier(0.4, 0, 1, 1)',
'out': 'cubic-bezier(0, 0, 0.2, 1)',
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
},
translate: (theme, { negative }) => ({
...theme('spacing'),
...negative(theme('spacing')),
'1/2': '50%',
'1/3': '33.333333%',
'2/3': '66.666667%',
'1/4': '25%',
'2/4': '50%',
'3/4': '75%',
'full': '100%',
'-1/2': '-50%',
'-1/3': '-33.333333%',
'-2/3': '-66.666667%',
'-1/4': '-25%',
'-2/4': '-50%',
'-3/4': '-75%',
'-full': '-100%',
}),
width: theme => ({
auto: 'auto',
...theme('spacing'),
screen: '100vw',
min: 'min-content',
max: 'max-content',
}),
zIndex: {
auto: 'auto',
0: '0',
10: '10',
20: '20',
30: '30',
40: '40',
50: '50',
},
},
})

6846
yarn.lock Normal file

File diff suppressed because it is too large Load Diff