init frontend
12
frontend/.editorconfig
Normal file
|
@ -0,0 +1,12 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
[*.{js,jsx,html,sass,vue,ts,tsx,json}]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
10
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.*-debug.log
|
||||
*.log
|
||||
.vercel
|
||||
.vite-ssg-temp
|
||||
.idea
|
9
frontend/.prettierrc
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"semi": true,
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"vueIndentScriptAndStyle": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
21
frontend/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Christopher Reeve
|
||||
|
||||
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.
|
137
frontend/README.md
Normal file
|
@ -0,0 +1,137 @@
|
|||
<p align='center'>
|
||||
<img src='https://user-images.githubusercontent.com/45350572/138070856-731c849a-466b-41a2-b39d-c5b5e76e94fa.png' alt='Vitailse - Opinionated Vite Starter Template with TailwindCSS' width='300'/>
|
||||
</p>
|
||||
|
||||
Opinionated Vite starter template with [TailwindCSS](https://tailwindcss.com/)
|
||||
|
||||
Inspired by [Vitesse](https://github.com/antfu/vitesse) ❤
|
||||
|
||||
## Features
|
||||
|
||||
- ⚡️ [Vue 3](https://github.com/vuejs/vue-next), [Vite 2](https://github.com/vitejs/vite), [pnpm](https://pnpm.js.org/), [ESBuild](https://github.com/evanw/esbuild) - born with fastness
|
||||
|
||||
- 🗂 [File based routing](./src/pages)
|
||||
|
||||
- 📦 [Components auto importing](./src/components)
|
||||
|
||||
- 🍍 [State Management via Pinia](https://pinia.esm.dev/)
|
||||
|
||||
- 📑 [Layout system](./src/layouts)
|
||||
|
||||
- 📲 [PWA](https://github.com/antfu/vite-plugin-pwa)
|
||||
|
||||
- 🌍 [I18n ready](./locales)
|
||||
|
||||
- 🎨 [Tailwind CSS](https://tailwindcss.com/) - Rapidly build modern websites without ever leaving your HTML.
|
||||
|
||||
- 😃 [Use icons from any icon sets, with no compromise](https://github.com/antfu/unplugin-icons)
|
||||
|
||||
- 🔥 Use the [new `<script setup>` syntax](https://github.com/vuejs/rfcs/pull/227)
|
||||
|
||||
- 📥 [APIs auto importing](https://github.com/antfu/unplugin-auto-import) - use Composition API and others directly
|
||||
|
||||
- 🖨 Server-side generation (SSG) via [vite-ssg](https://github.com/antfu/vite-ssg)
|
||||
|
||||
- 🦔 Critical CSS via [critters](https://github.com/GoogleChromeLabs/critters)
|
||||
|
||||
- 🦾 TypeScript, of course
|
||||
|
||||
## Pre-packed
|
||||
|
||||
### UI Frameworks
|
||||
|
||||
- [TailwindCSS](https://tailwindcss.com/)
|
||||
- [TailwindCSS Typography](https://github.com/tailwindlabs/tailwindcss-typography)
|
||||
- [TailwindCSS Forms](https://github.com/tailwindlabs/tailwindcss-forms)
|
||||
- [TailwindCSS Aspect Ratio](https://github.com/tailwindlabs/tailwindcss-aspect-ratio)
|
||||
|
||||
### Icons
|
||||
|
||||
- [Iconify](https://iconify.design) - use icons from any icon sets
|
||||
- [`unplugin-icons`](https://github.com/antfu/unplugin-icons) - icons as Vue components
|
||||
|
||||
### Plugins
|
||||
|
||||
- [Vue Router](https://github.com/vuejs/vue-router)
|
||||
- [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) - file system based routing
|
||||
- [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) - layouts for pages
|
||||
- [Pinia](https://pinia.esm.dev) - Intuitive, type safe, light and flexible Store for Vue using the composition api
|
||||
- [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components) - components auto import
|
||||
- [`unplugin-auto-import`](https://github.com/antfu/unplugin-auto-import) - Directly use Vue Composition API and others without importing
|
||||
- [VueUse](https://github.com/antfu/vueuse) - collection of useful composition APIs
|
||||
- [`@vueuse/head`](https://github.com/vueuse/head) - manipulate document head reactively
|
||||
- [Vue I18n](https://github.com/intlify/vue-i18n-next) - Internationalization
|
||||
- [`vite-plugin-vue-i18n`](https://github.com/intlify/vite-plugin-vue-i18n) - Vite plugin for Vue I18n
|
||||
- [`vite-plugin-pwa`](https://github.com/antfu/vite-plugin-pwa) - PWA
|
||||
|
||||
### Coding Style
|
||||
|
||||
- Use Composition API with [`<script setup>` SFC syntax](https://github.com/vuejs/rfcs/pull/227)
|
||||
|
||||
### Dev tools
|
||||
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [pnpm](https://pnpm.js.org/) - fast, disk space efficient package manager
|
||||
- [`vite-ssg`](https://github.com/antfu/vite-ssg) - Server-side generation
|
||||
- [critters](https://github.com/GoogleChromeLabs/critters) - Critical CSS
|
||||
- [VS Code Extensions](./.vscode/extensions.json)
|
||||
- [Vite](https://marketplace.visualstudio.com/items?itemName=antfu.vite) - Fire up Vite server automatically
|
||||
- [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) - Vue 3 `<script setup>` IDE support
|
||||
- [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Icon inline display and autocomplete
|
||||
- [TailwindCSS Intellisense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) - IDE support for Tailwind CSS
|
||||
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) - All in one i18n support
|
||||
## Try it now!
|
||||
### GitHub Template
|
||||
|
||||
[Create a repo from this template on GitHub](https://github.com/zynth17/vitailse/generate).
|
||||
|
||||
### Clone to local
|
||||
|
||||
If you prefer to do it manually with the cleaner git history
|
||||
|
||||
```bash
|
||||
npx degit zynth17/vitailse my-vitailse-app
|
||||
cd my-vitailse-app
|
||||
pnpm i # If you don't have pnpm installed, run: npm install -g pnpm
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
When you use this template, try follow the checklist to update your info properly
|
||||
|
||||
- [ ] Rename `name` field in `package.json`
|
||||
- [ ] Change the author name in `LICENSE`
|
||||
- [ ] Change the title in `App.vue`
|
||||
- [ ] Change the favicon in `public`
|
||||
- [ ] Remove the `.github` folder which contains the funding info
|
||||
- [ ] Clean up the READMEs and remove routes
|
||||
|
||||
And, enjoy :)
|
||||
|
||||
## Usage
|
||||
|
||||
### Development
|
||||
|
||||
Just run and visit http://localhost:3000
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Preview in Https
|
||||
|
||||
Just run and visit https://localhost
|
||||
|
||||
```bash
|
||||
pnpm build && pnpm run https-preview
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
To build the App, run
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
And you will see the generated file in `dist` that ready to be served.
|
252
frontend/auto-imports.d.ts
vendored
Normal file
|
@ -0,0 +1,252 @@
|
|||
// Generated by 'unplugin-auto-import'
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const logicAnd: typeof import('@vueuse/core')['logicAnd']
|
||||
const logicNot: typeof import('@vueuse/core')['logicNot']
|
||||
const logicOr: typeof import('@vueuse/core')['logicOr']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
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 onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
||||
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useClamp: typeof import('@vueuse/core')['useClamp']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
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 useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||
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 useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useHead: typeof import('@vueuse/head')['useHead']
|
||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useImage: typeof import('@vueuse/core')['useImage']
|
||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
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 useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||
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 useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
||||
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
||||
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 usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
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 useRoute: typeof import('@vue-router')['useRoute']
|
||||
const useRouter: typeof import('@vue-router')['useRouter']
|
||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
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 useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||
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']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchArray: typeof import('@vueuse/core')['watchArray']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
18
frontend/components.d.ts
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AppHeader: typeof import('./src/components/AppHeader.vue')['default']
|
||||
'Icon:bx:bxMoon': typeof import('~icons/bx/bx-moon')['default']
|
||||
'Icon:bx:bxsMoon': typeof import('~icons/bx/bxs-moon')['default']
|
||||
'IconAkarIcons:githubFill': typeof import('~icons/akar-icons/github-fill')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
TextField: typeof import('./src/components/Form/TextField.vue')['default']
|
||||
}
|
||||
}
|
34
frontend/index.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="night">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>
|
||||
Vitailse | Opinionated vite starter template with TailwindCSS
|
||||
</title>
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="Opinionated vite starter template with TailwindCSS"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="icon" href="/favicon.png" type="image/png" />
|
||||
<link
|
||||
rel="alternate icon"
|
||||
href="/favicon.ico"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="/apple-touch-icon.png"
|
||||
sizes="180x180"
|
||||
/>
|
||||
<link rel="mask-icon" href="/favicon.png" color="#076AE0" />
|
||||
<meta name="theme-color" content="#076AE0" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
22
frontend/locales/en.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"pages": {
|
||||
"home": "Home",
|
||||
"other": {
|
||||
"menu": "Other Page",
|
||||
"desc": "An example of other pages"
|
||||
},
|
||||
"not-found": "Page not found"
|
||||
},
|
||||
"app": {
|
||||
"offline": "App ready to work offline",
|
||||
"new-content": "New content available, click on reload button to update."
|
||||
},
|
||||
"intro": {
|
||||
"desc": "Welcome to Vitailse, Vite starter template with {tailwindurl}",
|
||||
"github": "Please give stars and report any issues on our {githuburl}"
|
||||
},
|
||||
"button": {
|
||||
"reload": "Reload",
|
||||
"close": "Close"
|
||||
}
|
||||
}
|
22
frontend/locales/id.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"pages": {
|
||||
"home": "Beranda",
|
||||
"other": {
|
||||
"menu": "Halaman lain",
|
||||
"desc": "Contoh untuk halaman lain"
|
||||
},
|
||||
"not-found": "Laman tidak ditemukan"
|
||||
},
|
||||
"app": {
|
||||
"offline": "Aplikasi siap digunakan tanpa jaringan internet",
|
||||
"new-content": "Konten baru ditemukan, Tekan tombol perbarui untuk memperbarui laman."
|
||||
},
|
||||
"intro": {
|
||||
"desc": "Selamat datang di Vitailse, Template awal vite dengan ",
|
||||
"github": "Mohon berikan bintang dan laporkan masalah pada "
|
||||
},
|
||||
"button": {
|
||||
"reload": "Perbarui",
|
||||
"close": "Tutup"
|
||||
}
|
||||
}
|
69
frontend/package.json
Normal file
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "@zynth/vitailse",
|
||||
"description": "Vite starter template with TailwindCSS",
|
||||
"version": "0.1.0",
|
||||
"main": "src/main.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zynth17/vitailse.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vitailse",
|
||||
"tailwindcss",
|
||||
"vite",
|
||||
"vitesse"
|
||||
],
|
||||
"author": "Christopher Reeeve",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/zynth17/vitailse/issues"
|
||||
},
|
||||
"homepage": "https://github.com/zynth17/vitailse#readme",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite-ssg build",
|
||||
"serve": "vite preview",
|
||||
"https-preview": "serve dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/aspect-ratio": "^0.4.0",
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.4",
|
||||
"@types/node": "^18.0.4",
|
||||
"@vueuse/components": "^8.9.3",
|
||||
"@vueuse/core": "^8.9.3",
|
||||
"@vueuse/head": "^0.7.6",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"daisyui": "^2.24.0",
|
||||
"pinia": "^2.0.16",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.1.6",
|
||||
"vue": "^3.2.37",
|
||||
"vue-i18n": "^9.1.10",
|
||||
"vue-router": "^4.1.2",
|
||||
"workbox": "^0.0.0",
|
||||
"workbox-window": "^6.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.1.78",
|
||||
"@intlify/vite-plugin-vue-i18n": "^5.0.0",
|
||||
"@vitejs/plugin-vue": "^3.0.0",
|
||||
"@vue/compiler-sfc": "^3.2.37",
|
||||
"@vue/server-renderer": "^3.2.37",
|
||||
"critters": "^0.0.16",
|
||||
"https-localhost": "^4.7.1",
|
||||
"typescript": "^4.7.4",
|
||||
"unplugin-auto-import": "^0.9.3",
|
||||
"unplugin-icons": "^0.14.7",
|
||||
"unplugin-vue-components": "0.21.1",
|
||||
"unplugin-vue-router": "^0.0.21",
|
||||
"vite": "^3.0.0",
|
||||
"vite-plugin-pwa": "^0.12.3",
|
||||
"vite-plugin-vue-layouts": "^0.7.0",
|
||||
"vite-plugin-vue-type-imports": "^0.2.0",
|
||||
"vite-ssg": "^0.20.2",
|
||||
"vite-ssg-sitemap": "^0.3.2",
|
||||
"vitest": "^0.18.0",
|
||||
"vue-tsc": "^0.38.5"
|
||||
}
|
||||
}
|
5409
frontend/pnpm-lock.yaml
Normal file
6
frontend/postcss.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
BIN
frontend/public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
frontend/public/favicon-16x16.png
Normal file
After Width: | Height: | Size: 647 B |
BIN
frontend/public/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
frontend/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
frontend/public/pwa-192x192.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
frontend/public/pwa-512x512.png
Normal file
After Width: | Height: | Size: 22 KiB |
2
frontend/public/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Allow: /
|
1
frontend/public/site.webmanifest
Normal file
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
3
frontend/src/App.vue
Normal file
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<router-view />
|
||||
</template>
|
7
frontend/src/__test__/basic.spec.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
describe('tests', () => {
|
||||
it('should works', () => {
|
||||
expect(1 + 1).toEqual(2)
|
||||
})
|
||||
})
|
BIN
frontend/src/assets/logo.png
Normal file
After Width: | Height: | Size: 35 KiB |
80
frontend/src/components/AppHeader.vue
Normal file
|
@ -0,0 +1,80 @@
|
|||
<script setup lang="ts">
|
||||
defineEmits(['toggleSidebar']);
|
||||
|
||||
const { availableLocales } = useI18n();
|
||||
|
||||
const preferedDark = usePreferredDark();
|
||||
const isDark = useStorage('isDark', preferedDark.value);
|
||||
const body = ref<HTMLBodyElement | null>(null);
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
if (body.value) {
|
||||
if (isDark.value) {
|
||||
body.value.classList.remove('dark');
|
||||
} else {
|
||||
body.value.classList.add('dark');
|
||||
}
|
||||
}
|
||||
isDark.value = !isDark.value;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
|
||||
body.value = document.querySelector('body') as HTMLBodyElement;
|
||||
if (body.value) {
|
||||
if (isDark.value) body.value.classList.add('dark');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<nav
|
||||
class="
|
||||
w-full
|
||||
bg-white
|
||||
text-gray-800
|
||||
dark:bg-gray-800 dark:text-white
|
||||
py-4
|
||||
px-8
|
||||
shadow-md
|
||||
dark:shadow-md
|
||||
flex
|
||||
items-center
|
||||
border-b border-gray-400/50
|
||||
"
|
||||
>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
<div class="font-bold lg:text-xl md:text-lg text-md">Vitailse</div>
|
||||
</router-link>
|
||||
<div class="ml-auto flex items-center h-full">
|
||||
<select
|
||||
id="language"
|
||||
v-model="$i18n.locale"
|
||||
class="py-1 focus:outline-none rounded dark:text-gray-800"
|
||||
>
|
||||
<option
|
||||
v-for="locale in availableLocales"
|
||||
:key="locale"
|
||||
:value="locale"
|
||||
>
|
||||
{{ locale }}
|
||||
</option>
|
||||
</select>
|
||||
<button
|
||||
class="mx-5 cursor-pointer focus:outline-none"
|
||||
@click="toggleDarkMode"
|
||||
>
|
||||
<icon:bx:bx-moon class="w-6 h-6" v-if="!isDark" />
|
||||
<icon:bx:bxs-moon class="w-6 h-6" v-else />
|
||||
</button>
|
||||
<a href="https://github.com/zynth17/vitailse">
|
||||
<icon-akar-icons:github-fill />
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
31
frontend/src/components/Form/TextField.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">{{ label }}</span>
|
||||
</label>
|
||||
<input
|
||||
:type="type"
|
||||
v-model="value"
|
||||
class="input input-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
});
|
||||
|
||||
const value = useVModel(props, 'modelValue');
|
||||
</script>
|
8
frontend/src/env.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
5
frontend/src/layouts/404.vue
Normal file
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<main class="w-full min-h-screen bg-blue-100 grid place-items-center">
|
||||
<router-view />
|
||||
</main>
|
||||
</template>
|
18
frontend/src/layouts/default.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts"></script>
|
||||
<template>
|
||||
<header>
|
||||
<app-header />
|
||||
</header>
|
||||
|
||||
<main
|
||||
class="
|
||||
p-8
|
||||
dark:bg-gray-800 dark:text-white
|
||||
bg-white
|
||||
text-gray-800
|
||||
min-h-screen
|
||||
"
|
||||
>
|
||||
<router-view />
|
||||
</main>
|
||||
</template>
|
19
frontend/src/main.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import App from '@/App.vue';
|
||||
import { ViteSSG } from 'vite-ssg';
|
||||
|
||||
import '@/styles/index.css';
|
||||
import { ViteSetupModule } from './types/ViteSetupModule';
|
||||
import { extendedRoutes } from '@/router';
|
||||
|
||||
export const createApp = ViteSSG(
|
||||
App,
|
||||
{ routes: extendedRoutes },
|
||||
async ctx => {
|
||||
Object.values(
|
||||
import.meta.glob<{ install: ViteSetupModule }>('./modules/*.ts', {
|
||||
eager: true,
|
||||
})
|
||||
).map(i => i.install?.(ctx));
|
||||
},
|
||||
{}
|
||||
);
|
29
frontend/src/modules/i18n.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { ViteSetupModule } from '@/types/ViteSetupModule';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
// Import i18n resources
|
||||
// https://vitejs.dev/guide/features.html#glob-import
|
||||
|
||||
// Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite
|
||||
const messages = Object.fromEntries(
|
||||
Object.entries(
|
||||
import.meta.glob<{ default: any }>('../../locales/*.{y(a)?ml,json}', {
|
||||
eager: true,
|
||||
})
|
||||
).map(([key, value]) => {
|
||||
const isYamlOrJson = key.endsWith('.yaml') || key.endsWith('.json');
|
||||
|
||||
return [key.slice(14, isYamlOrJson ? -5 : -4), value.default];
|
||||
})
|
||||
);
|
||||
|
||||
export const install: ViteSetupModule = ({ app }) => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages,
|
||||
globalInjection: true,
|
||||
});
|
||||
|
||||
app.use(i18n);
|
||||
};
|
14
frontend/src/modules/pinia.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { ViteSetupModule } from '@/types/ViteSetupModule';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
// Setup Pinia
|
||||
// https://pinia.esm.dev/
|
||||
export const install: ViteSetupModule = ({ isClient, initialState, app }) => {
|
||||
const pinia = createPinia();
|
||||
app.use(pinia);
|
||||
// Refer to
|
||||
// https://github.com/antfu/vite-ssg/blob/main/README.md#state-serialization
|
||||
// for other serialization strategies.
|
||||
if (isClient) pinia.state.value = initialState.pinia || {};
|
||||
else initialState.pinia = pinia.state.value;
|
||||
};
|
10
frontend/src/modules/pwa.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { ViteSetupModule } from '@/types/ViteSetupModule';
|
||||
|
||||
export const install: ViteSetupModule = ({ isClient, router }) => {
|
||||
if (!isClient) return;
|
||||
|
||||
router.isReady().then(async () => {
|
||||
const { registerSW } = await import('virtual:pwa-register');
|
||||
registerSW({ immediate: true });
|
||||
});
|
||||
};
|
19
frontend/src/pages/[...all].vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
useHead({
|
||||
title: '404. Not Found',
|
||||
});
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 class="text-blue-500 font-extrabold flex flex-col text-center">
|
||||
<span class="text-7xl">404.</span>
|
||||
<span class="text-5xl mt-5">{{ t('pages.not-found') }}</span>
|
||||
</h1>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
name : not-found
|
||||
meta:
|
||||
layout: 404
|
||||
</route>
|
157
frontend/src/pages/index.vue
Normal file
|
@ -0,0 +1,157 @@
|
|||
<script setup lang="ts">
|
||||
import TextField from '@/components/Form/TextField.vue';
|
||||
useHead({
|
||||
title: 'Homebox | Organize and Tag Your Stuff',
|
||||
});
|
||||
|
||||
const registerFields = [
|
||||
{
|
||||
label: "What's your name?",
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: "What's your email?",
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'Name your group',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'Set your password',
|
||||
value: '',
|
||||
type: 'password',
|
||||
},
|
||||
{
|
||||
label: 'Confirm your password',
|
||||
value: '',
|
||||
type: 'password',
|
||||
},
|
||||
];
|
||||
|
||||
function registerUser() {
|
||||
// Print Values of registerFields
|
||||
|
||||
for (let i = 0; i < registerFields.length; i++) {
|
||||
console.log(registerFields[i].label, registerFields[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
const loginFields = [
|
||||
{
|
||||
label: 'Email',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'Password',
|
||||
value: '',
|
||||
type: 'password',
|
||||
},
|
||||
];
|
||||
|
||||
const registerForm = ref(false);
|
||||
|
||||
function toggleLogin() {
|
||||
registerForm.value = !registerForm.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="sm:px-6 py-2 lg:p-14 sm:py-6">
|
||||
<h2
|
||||
class="mt-1 text-4xl font-bold tracking-tight text-gray-200 sm:text-5xl lg:text-6xl"
|
||||
>
|
||||
Homebox
|
||||
</h2>
|
||||
<p class="ml-1 text-lg text-gray-400">
|
||||
Track, Organize, and Manage your Shit.
|
||||
</p>
|
||||
</header>
|
||||
<div class="grid p-6 sm:place-items-center min-h-[50vh]">
|
||||
<Transition name="slide-fade">
|
||||
<form v-if="registerForm" @submit.prevent="registerUser">
|
||||
<div
|
||||
class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl"
|
||||
>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Register</h2>
|
||||
<TextField
|
||||
v-for="field in registerFields"
|
||||
v-model="field.value"
|
||||
:label="field.label"
|
||||
:key="field.label"
|
||||
:type="field.type"
|
||||
/>
|
||||
<div class="card-actions justify-end">
|
||||
<button type="submit" class="btn btn-primary mt-2">
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<button @click="toggleLogin">Already a User? Login</button>
|
||||
</div>
|
||||
</form>
|
||||
<div v-else>
|
||||
<div
|
||||
class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl"
|
||||
>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Login</h2>
|
||||
<TextField
|
||||
v-for="field in loginFields"
|
||||
v-model="field.value"
|
||||
:label="field.label"
|
||||
:key="field.label"
|
||||
:type="field.type"
|
||||
/>
|
||||
<div class="card-actions justify-end mt-2">
|
||||
<button class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mt-2">
|
||||
<button @click="toggleLogin">Not a User? Register</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="min-w-full absolute bottom-0 z-[-1]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="none"
|
||||
viewBox="0 0 1440 320"
|
||||
>
|
||||
<path
|
||||
fill="#0099ff9b"
|
||||
fill-opacity="1"
|
||||
d="M0,32L30,42.7C60,53,120,75,180,80C240,85,300,75,360,80C420,85,480,107,540,128C600,149,660,171,720,160C780,149,840,107,900,90.7C960,75,1020,85,1080,122.7C1140,160,1200,224,1260,234.7C1320,245,1380,203,1410,181.3L1440,160L1440,320L1410,320C1380,320,1320,320,1260,320C1200,320,1140,320,1080,320C1020,320,960,320,900,320C840,320,780,320,720,320C660,320,600,320,540,320C480,320,420,320,360,320C300,320,240,320,180,320C120,320,60,320,30,320L0,320Z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="bg-[#0099ff9b] flex-col flex min-h-[32vh]">
|
||||
<div class="mt-auto mx-auto mb-8">
|
||||
<p class="text-center text-gray-200">
|
||||
© 2022 Contents. All Rights Reserved. Haybytes LLC
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
name: home
|
||||
</route>
|
||||
|
||||
<style lang="css">
|
||||
.slide-fade-enter-active {
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
.slide-fade-enter-from,
|
||||
.slide-fade-leave-to {
|
||||
position: absolute;
|
||||
transform: translateX(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
17
frontend/src/router.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
createRouter,
|
||||
createWebHistory,
|
||||
createMemoryHistory,
|
||||
} from '@vue-router';
|
||||
|
||||
import { setupLayouts } from 'virtual:generated-layouts';
|
||||
export let extendedRoutes: any = null;
|
||||
export const router = createRouter({
|
||||
history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
|
||||
// You don't need to pass the routes anymore,
|
||||
// the plugin writes it for you 🤖
|
||||
extendRoutes: routes => {
|
||||
extendedRoutes = routes;
|
||||
return setupLayouts(routes);
|
||||
},
|
||||
});
|
7
frontend/src/store/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { defineStore } from 'pinia';
|
||||
|
||||
export const useStore = defineStore('store', {
|
||||
state: () => ({
|
||||
count: 0,
|
||||
}),
|
||||
});
|
3
frontend/src/styles/index.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
3
frontend/src/types/ViteSetupModule.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { ViteSSGContext } from 'vite-ssg';
|
||||
|
||||
export type ViteSetupModule = (ctx: ViteSSGContext) => void;
|
16
frontend/tailwind.config.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||
darkMode: 'class', // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('daisyui'),
|
||||
],
|
||||
};
|
34
frontend/tsconfig.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"types": [
|
||||
"vite/client",
|
||||
"vite-plugin-vue-layouts/client",
|
||||
"unplugin-icons/types/vue",
|
||||
"vite-plugin-pwa/client",
|
||||
"@intlify/vite-plugin-vue-i18n/client"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"components.d.ts",
|
||||
"auto-imports.d.ts",
|
||||
"typed-router.d.ts"
|
||||
]
|
||||
}
|
95
frontend/typed-router.d.ts
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
|
||||
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
|
||||
// It's recommended to commit this file.
|
||||
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||
|
||||
/// <reference types="unplugin-vue-router/client" />
|
||||
|
||||
import type {
|
||||
// type safe route locations
|
||||
RouteLocationTypedList,
|
||||
RouteLocationResolvedTypedList,
|
||||
RouteLocationNormalizedTypedList,
|
||||
RouteLocationNormalizedLoadedTypedList,
|
||||
|
||||
// helper types
|
||||
// route definitions
|
||||
RouteRecordInfo,
|
||||
ParamValue,
|
||||
ParamValueOneOrMore,
|
||||
ParamValueZeroOrMore,
|
||||
ParamValueZeroOrOne,
|
||||
|
||||
// vue-router extensions
|
||||
_RouterTyped,
|
||||
RouterLinkTyped,
|
||||
NavigationGuard,
|
||||
UseLinkFnTyped,
|
||||
} from 'unplugin-vue-router'
|
||||
|
||||
declare module '@vue-router/routes' {
|
||||
export interface RouteNamedMap {
|
||||
'home': RouteRecordInfo<'home', '/', Record<never, never>, Record<never, never>>,
|
||||
'not-found': RouteRecordInfo<'not-found', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@vue-router' {
|
||||
import type { RouteNamedMap } from '@vue-router/routes'
|
||||
|
||||
export type RouterTyped = _RouterTyped<RouteNamedMap>
|
||||
|
||||
/**
|
||||
* Type safe version of `RouteLocationNormalized` (the type of `to` and `from` in navigation guards).
|
||||
* Allows passing the name of the route to be passed as a generic.
|
||||
*/
|
||||
export type RouteLocationNormalized<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationNormalizedTypedList<RouteNamedMap>[Name]
|
||||
|
||||
/**
|
||||
* Type safe version of `RouteLocationNormalizedLoaded` (the return type of `useRoute()`).
|
||||
* Allows passing the name of the route to be passed as a generic.
|
||||
*/
|
||||
export type RouteLocationNormalizedLoaded<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[Name]
|
||||
|
||||
/**
|
||||
* Type safe version of `RouteLocationResolved` (the returned route of `router.resolve()`).
|
||||
* Allows passing the name of the route to be passed as a generic.
|
||||
*/
|
||||
export type RouteLocationResolved<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationResolvedTypedList<RouteNamedMap>[Name]
|
||||
|
||||
/**
|
||||
* Type safe version of `RouteLocation` . Allows passing the name of the route to be passed as a generic.
|
||||
*/
|
||||
export type RouteLocation<Name extends keyof RouteNamedMap = keyof RouteNamedMap> = RouteLocationTypedList<RouteNamedMap>[Name]
|
||||
|
||||
/**
|
||||
* Generate a type safe params for a route location. Requires the name of the route to be passed as a generic.
|
||||
*/
|
||||
export type RouteParams<Name extends keyof RouteNamedMap> = RouteNamedMap[Name]['params']
|
||||
/**
|
||||
* Generate a type safe raw params for a route location. Requires the name of the route to be passed as a generic.
|
||||
*/
|
||||
export type RouteParamsRaw<Name extends keyof RouteNamedMap> = RouteNamedMap[Name]['paramsRaw']
|
||||
|
||||
export function useRouter(): RouterTyped
|
||||
export function useRoute<Name extends keyof RouteNamedMap = keyof RouteNamedMap>(name?: Name): RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[Name]
|
||||
|
||||
export const useLink: UseLinkFnTyped<RouteNamedMap>
|
||||
|
||||
export function onBeforeRouteLeave(guard: NavigationGuard<RouteNamedMap>): void
|
||||
export function onBeforeRouteUpdate(guard: NavigationGuard<RouteNamedMap>): void
|
||||
}
|
||||
|
||||
declare module 'vue-router' {
|
||||
import type { RouteNamedMap } from '@vue-router/routes'
|
||||
|
||||
export interface TypesConfig {
|
||||
beforeRouteUpdate: NavigationGuard<RouteNamedMap>
|
||||
beforeRouteLeave: NavigationGuard<RouteNamedMap>
|
||||
|
||||
$route: RouteLocationNormalizedLoadedTypedList<RouteNamedMap>[keyof RouteNamedMap]
|
||||
$router: _RouterTyped<RouteNamedMap>
|
||||
|
||||
RouterLink: RouterLinkTyped<RouteNamedMap>
|
||||
}
|
||||
}
|
131
frontend/vite.config.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { resolve } from 'path';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
import IconsResolver from 'unplugin-icons/resolver';
|
||||
import Layouts from 'vite-plugin-vue-layouts';
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import VueI18n from '@intlify/vite-plugin-vue-i18n';
|
||||
import generateSitemap from 'vite-ssg-sitemap';
|
||||
import VueRouter from 'unplugin-vue-router/vite';
|
||||
import { VueRouterExports } from 'unplugin-vue-router';
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
VueRouter({
|
||||
dts: true,
|
||||
routesFolder: 'src/pages',
|
||||
}),
|
||||
Components({
|
||||
dts: true,
|
||||
resolvers: [
|
||||
IconsResolver({
|
||||
prefix: 'icon',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
Icons({
|
||||
compiler: 'vue3',
|
||||
}),
|
||||
AutoImport({
|
||||
dts: true,
|
||||
// targets to transform
|
||||
include: [
|
||||
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
|
||||
/\.vue\??/, // .vue
|
||||
],
|
||||
|
||||
// global imports to register
|
||||
imports: [
|
||||
// presets
|
||||
'vue',
|
||||
{ '@vue-router': VueRouterExports },
|
||||
'vue-i18n',
|
||||
'@vueuse/core',
|
||||
'@vueuse/head',
|
||||
// custom
|
||||
],
|
||||
|
||||
// custom resolvers
|
||||
// see https://github.com/antfu/unplugin-auto-import/pull/23/
|
||||
resolvers: [],
|
||||
}),
|
||||
Layouts(),
|
||||
VitePWA({
|
||||
includeAssets: [
|
||||
'favicon-16x16.png',
|
||||
'favicon-32x32.png',
|
||||
'favicon.ico',
|
||||
'robots.txt',
|
||||
'apple-touch-icon.png',
|
||||
],
|
||||
manifest: {
|
||||
name: 'Vitailse',
|
||||
short_name: 'Vitailse',
|
||||
description: 'Opinionated vite template with TailwindCSS',
|
||||
theme_color: '#076AE0',
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
VueI18n({
|
||||
runtimeOnly: true,
|
||||
compositionOnly: true,
|
||||
include: [resolve(__dirname, 'locales/**')],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
strict: true,
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: ['vue', 'vue-router', '@vueuse/core', '@vueuse/head'],
|
||||
},
|
||||
// @ts-ignore
|
||||
ssgOptions: {
|
||||
script: 'async',
|
||||
formatting: 'minify',
|
||||
format: 'cjs',
|
||||
onFinished() {
|
||||
generateSitemap();
|
||||
},
|
||||
mock: true
|
||||
},
|
||||
// https://github.com/vitest-dev/vitest
|
||||
test: {
|
||||
include: ['src/__test__/**/*.test.ts', 'src/__test__/**/*.spec.ts'],
|
||||
environment: 'jsdom',
|
||||
deps: {
|
||||
inline: ['@vue', '@vueuse', 'vue-demi'],
|
||||
},
|
||||
},
|
||||
ssr: {
|
||||
// TODO: workaround until they support native ESM
|
||||
noExternal: ['workbox-window', /vue-i18n/],
|
||||
},
|
||||
});
|