refactor(frontend): 🏗️ scafold pages and componenets folder

Setup the file structure for components, pages, layouts, api, and all new frontend structure
This commit is contained in:
hay-kot 2021-07-31 14:45:28 -08:00
parent 8d3db89327
commit 91f56b42de
47 changed files with 1027 additions and 146 deletions

View file

@ -3,12 +3,12 @@ name: ci
on:
push:
branches:
- main
- master
- mealie-next
- mealie-next
pull_request:
branches:
- main
- master
- mealie-next
- mealie-next
jobs:
ci:
@ -47,4 +47,3 @@ jobs:
- name: Run linter 👀
run: yarn lint

View file

@ -15,5 +15,6 @@
"i18n-ally.keystyle": "nested",
"cSpell.words": ["compression", "hkotel", "performant", "postgres", "webp"],
"search.mode": "reuseEditor",
"python.linting.flake8Enabled": true
"python.linting.flake8Enabled": true,
"conventionalCommits.scopes": ["frontend"]
}

View file

@ -2,15 +2,24 @@ module.exports = {
root: true,
env: {
browser: true,
node: true
node: true,
},
extends: [
'@nuxtjs/eslint-config-typescript',
'plugin:nuxt/recommended',
'prettier'
],
plugins: [
],
parserOptions: {
parser: "@typescript-eslint/parser",
requireConfigFile: false,
},
extends: ["@nuxtjs/eslint-config-typescript", "plugin:nuxt/recommended", "prettier"],
plugins: ["prettier"],
// add your custom rules here
rules: {}
}
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
quotes: ["error", "double"],
"vue/component-name-in-template-casing": ["error", "PascalCase"],
camelcase: 0,
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline": "off",
"vue/no-mutating-props": "off",
"vue/no-v-for-template-key-on-child": "off",
},
};

View file

@ -1,5 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
yarn lint-staged

View file

@ -1,4 +1,9 @@
{
"semi": false,
"singleQuote": true
}
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"printWidth": 120,
"arrowParens": "always",
"eslintIntegration": true
}

5
frontend/api/index.ts Normal file
View file

@ -0,0 +1,5 @@
export const api = {}

49
frontend/api/requests.ts Normal file
View file

@ -0,0 +1,49 @@
import axios, { AxiosResponse } from "axios";
interface RequestResponse<T> {
response: AxiosResponse<T> | null;
data: T | null;
error: any;
}
const request = {
async safe<T>(funcCall: any, url: string, data: object = {}): Promise<RequestResponse<T>> {
const response = await funcCall(url, data).catch(function (error: object) {
console.log(error);
// Insert Generic Error Handling Here
return { response: null, error, data: null };
});
return { response, error: null, data: response.data };
},
};
export const requests = {
async get<T>(url: string, queryParams = {}): Promise<RequestResponse<T>> {
let error = null;
const response = await axios.get<T>(url, { params: { queryParams } }).catch((e) => {
error = e;
});
if (response != null) {
return { response, error, data: response?.data };
}
return { response: null, error, data: null };
},
async post<T>(url: string, data: object) {
return await request.safe<T>(axios.post, url, data);
},
async put<T>(url: string, data: object) {
return await request.safe<T>(axios.put, url, data);
},
async patch<T>(url: string, data: object) {
return await request.safe<T>(axios.patch, url, data);
},
async delete<T>(url: string) {
return await request.safe<T>(axios.delete, url);
},
};

View file

@ -0,0 +1,7 @@
.v-btn {
text-transform: none !important;
}
.v-tab {
text-transform: none !important;
}

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -1,11 +0,0 @@
<template>
<svg class="nuxt-logo" viewBox="0 0 45 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.7203 29.704H41.1008C41.6211 29.7041 42.1322 29.5669 42.5828 29.3061C43.0334 29.0454 43.4075 28.6704 43.6675 28.2188C43.9275 27.7672 44.0643 27.2549 44.0641 26.7335C44.0639 26.2121 43.9266 25.6999 43.6662 25.2485L32.6655 6.15312C32.4055 5.70162 32.0315 5.32667 31.581 5.06598C31.1305 4.8053 30.6195 4.66805 30.0994 4.66805C29.5792 4.66805 29.0682 4.8053 28.6177 5.06598C28.1672 5.32667 27.7932 5.70162 27.5332 6.15312L24.7203 11.039L19.2208 1.48485C18.9606 1.03338 18.5864 0.658493 18.1358 0.397853C17.6852 0.137213 17.1741 0 16.6538 0C16.1336 0 15.6225 0.137213 15.1719 0.397853C14.7213 0.658493 14.3471 1.03338 14.0868 1.48485L0.397874 25.2485C0.137452 25.6999 0.000226653 26.2121 2.8053e-07 26.7335C-0.000226092 27.2549 0.136554 27.7672 0.396584 28.2188C0.656614 28.6704 1.03072 29.0454 1.48129 29.3061C1.93185 29.5669 2.44298 29.7041 2.96326 29.704H13.2456C17.3195 29.704 20.3239 27.9106 22.3912 24.4118L27.4102 15.7008L30.0986 11.039L38.1667 25.0422H27.4102L24.7203 29.704ZM13.0779 25.0374L5.9022 25.0358L16.6586 6.36589L22.0257 15.7008L18.4322 21.9401C17.0593 24.2103 15.4996 25.0374 13.0779 25.0374Z" fill="#00DC82" />
</svg>
</template>
<style>
.nuxt-logo {
height: 180px;
}
</style>

View file

@ -0,0 +1,35 @@
# Components Folder Guide
## Domain Components
Domain Components are specific to the domain or section of a website. For example if you have an admin page and a user page that have specific, unshared elements. These can be placed in the Domain/Admin folder.
**Rules**
- Components should be prefixed with their domain name
- Examples: AdminDashboard, AdminSettings, UserProfile
## Global Components
This folder is for widely reused components that provide little functionality and are primarily used for styling or consistency. Primary examples are Card and Button components.
**Rules**
- Global components cannot container a subfolder to be globally imported
- All elements should start with the 'Base' Prefix
- Examples: BaseButton, BaseCard, BaseTitleSection
## Layout Components
The layout folder is for reusable components that are specifically **only** used in the layouts for the Nuxt Application. They may take props or may not. They should be larger layout style components that don't have wide the ability to be widely reused in the application.
**Rules:**
- Layout folder should not have a subfolder
- If they take props they should start with a 'App' Prefix.
- Examples: AppSidebar, AppHeader, AppFooter.
- If they do not they should begin with the 'The' prefix
- Examples: TheSidebar, TheHeader, TheFooter.
## Page Components
The Page folder is dedicated to 'single-use' component to break up large amounts on content in the pages themselves. A good examples of this is breaking your landing page into separate sections to make it more readable and less monolithic. Page components typically consume other components.
**Rules:**
- These are *last resort* components. Only to be used when the page becomes unmanageable.
- Page components should be prefixed with their page name
- Examples: HomeAbout, HomeContact, ClientProfile

View file

@ -1,46 +0,0 @@
<!-- Please remove this file from your project -->
<template>
<div class="relative flex items-top justify-center min-h-screen bg-gray-100 sm:items-center sm:pt-0">
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.1.2/dist/tailwind.min.css" rel="stylesheet">
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8">
<a class="flex justify-center pt-8 sm:pt-0" href="https://nuxtjs.org" target="_blank">
<svg width="218" height="45" viewBox="0 0 159 30" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M55.5017 6.81866H60.1727L70.0719 22.9912V6.81866H74.3837V29.7345H69.7446L59.8135 13.5955V29.7345H55.5017V6.81866Z" fill="#003543" /> <path d="M93.657 29.7344H89.6389V27.1747C88.7241 28.9761 86.8628 29.9904 84.5113 29.9904C80.7869 29.9904 78.3684 27.3059 78.3684 23.4423V13.2339H82.3865V22.5976C82.3865 24.8566 83.7594 26.4276 85.8171 26.4276C88.0712 26.4276 89.6389 24.6598 89.6389 22.2377V13.2339H93.657V29.7344Z" fill="#003543" /> <path d="M107.64 29.7344L103.784 24.2342L99.9291 29.7344H95.6492L101.596 21.1242L96.1074 13.2339H100.485L103.784 17.9821L107.051 13.2339H111.461L105.94 21.1242L111.886 29.7344H107.64Z" fill="#003543" /> <path d="M120.053 8.25848V13.2339H124.627V16.6063H120.053V24.7974C120.053 25.0725 120.162 25.3363 120.356 25.531C120.55 25.7257 120.813 25.8353 121.087 25.8357H124.627V29.728H121.98C118.386 29.728 116.035 27.6323 116.035 23.9687V16.6095H112.801V13.2339H114.83C115.776 13.2339 116.327 12.6692 116.327 11.7349V8.25848H120.053Z" fill="#003543" /> <path d="M134.756 24.5446V6.81866H139.066V23.1864C139.066 27.6067 136.943 29.7345 133.349 29.7345H128.332V25.8421H133.461C133.804 25.8421 134.134 25.7054 134.377 25.4621C134.619 25.2188 134.756 24.8888 134.756 24.5446Z" fill="#003543" /> <path d="M141.649 22.0409H145.799C146.029 24.6006 147.728 26.2308 150.472 26.2308C152.923 26.2308 154.623 25.2501 154.623 23.2199C154.623 18.3085 142.331 21.7129 142.331 12.9395C142.334 9.17515 145.568 6.55945 150.215 6.55945C155.05 6.55945 158.317 9.34153 158.516 13.6306H154.388C154.193 11.6341 152.632 10.2918 150.207 10.2918C147.953 10.2918 146.548 11.3397 146.548 12.9427C146.548 18.0173 159 14.2226 159 23.1576C159 27.4131 155.504 30 150.474 30C145.279 30 141.882 26.8563 141.654 22.0441" fill="#003543" /> <path d="M24.7203 29.704H41.1008C41.6211 29.7041 42.1322 29.5669 42.5828 29.3061C43.0334 29.0454 43.4075 28.6704 43.6675 28.2188C43.9275 27.7672 44.0643 27.2549 44.0641 26.7335C44.0639 26.2121 43.9266 25.6999 43.6662 25.2485L32.6655 6.15312C32.4055 5.70162 32.0315 5.32667 31.581 5.06598C31.1305 4.8053 30.6195 4.66805 30.0994 4.66805C29.5792 4.66805 29.0682 4.8053 28.6177 5.06598C28.1672 5.32667 27.7932 5.70162 27.5332 6.15312L24.7203 11.039L19.2208 1.48485C18.9606 1.03338 18.5864 0.658493 18.1358 0.397853C17.6852 0.137213 17.1741 0 16.6538 0C16.1336 0 15.6225 0.137213 15.1719 0.397853C14.7213 0.658493 14.3471 1.03338 14.0868 1.48485L0.397874 25.2485C0.137452 25.6999 0.000226653 26.2121 2.8053e-07 26.7335C-0.000226092 27.2549 0.136554 27.7672 0.396584 28.2188C0.656614 28.6704 1.03072 29.0454 1.48129 29.3061C1.93185 29.5669 2.44298 29.7041 2.96326 29.704H13.2456C17.3195 29.704 20.3239 27.9106 22.3912 24.4118L27.4102 15.7008L30.0986 11.039L38.1667 25.0422H27.4102L24.7203 29.704ZM13.0779 25.0374L5.9022 25.0358L16.6586 6.36589L22.0257 15.7008L18.4322 21.9401C17.0593 24.2103 15.4996 25.0374 13.0779 25.0374Z" fill="#00DC82" /></svg>
</a>
<div class="mt-8 bg-white overflow-hidden shadow sm:rounded-lg p-6">
<h2 class="text-2xl leading-7 font-semibold">
Welcome to your Nuxt Application
</h2>
<p class="mt-3 text-gray-600">
We recommend you take a look at the <a href="https://nuxtjs.org" target="_blank" class="text-green-500 hover:underline">Nuxt documentation</a>, whether you are new or have previous experience with the framework.<br>
</p>
<p class="mt-4 pt-4 text-gray-800 border-t border-dashed">
To get started, remove <code class="bg-gray-100 text-sm p-1 rounded border">components/Tutorial.vue</code> and start coding in <code class="bg-gray-100 text-sm p-1 rounded border">pages/index.vue</code>. Have fun!
</p>
</div>
<div class="flex justify-center pt-4 space-x-2">
<a href="https://github.com/nuxt/nuxt.js" target="_blank"><svg
class="w-6 h-6 text-gray-600 hover:text-gray-800"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
width="32"
height="32"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
><path d="M12 2.247a10 10 0 0 0-3.162 19.487c.5.088.687-.212.687-.475c0-.237-.012-1.025-.012-1.862c-2.513.462-3.163-.613-3.363-1.175a3.636 3.636 0 0 0-1.025-1.413c-.35-.187-.85-.65-.013-.662a2.001 2.001 0 0 1 1.538 1.025a2.137 2.137 0 0 0 2.912.825a2.104 2.104 0 0 1 .638-1.338c-2.225-.25-4.55-1.112-4.55-4.937a3.892 3.892 0 0 1 1.025-2.688a3.594 3.594 0 0 1 .1-2.65s.837-.262 2.75 1.025a9.427 9.427 0 0 1 5 0c1.912-1.3 2.75-1.025 2.75-1.025a3.593 3.593 0 0 1 .1 2.65a3.869 3.869 0 0 1 1.025 2.688c0 3.837-2.338 4.687-4.563 4.937a2.368 2.368 0 0 1 .675 1.85c0 1.338-.012 2.413-.012 2.75c0 .263.187.575.687.475A10.005 10.005 0 0 0 12 2.247z" fill="currentColor" /></svg></a>
<a href="https://twitter.com/nuxt_js" target="_blank"><svg
class="w-6 h-6 text-gray-600 hover:text-gray-800"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
width="32"
height="32"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
><path d="M22.46 6c-.77.35-1.6.58-2.46.69c.88-.53 1.56-1.37 1.88-2.38c-.83.5-1.75.85-2.72 1.05C18.37 4.5 17.26 4 16 4c-2.35 0-4.27 1.92-4.27 4.29c0 .34.04.67.11.98C8.28 9.09 5.11 7.38 3 4.79c-.37.63-.58 1.37-.58 2.15c0 1.49.75 2.81 1.91 3.56c-.71 0-1.37-.2-1.95-.5v.03c0 2.08 1.48 3.82 3.44 4.21a4.22 4.22 0 0 1-1.93.07a4.28 4.28 0 0 0 4 2.98a8.521 8.521 0 0 1-5.33 1.84c-.34 0-.68-.02-1.02-.06C3.44 20.29 5.7 21 8.12 21C16 21 20.33 14.46 20.33 8.79c0-.19 0-.37-.01-.56c.84-.6 1.56-1.36 2.14-2.23z" fill="currentColor" /></svg></a>
</div>
</div>
</div>
</template>

View file

@ -1,22 +0,0 @@
<template>
<img
class="VuetifyLogo"
alt="Vuetify Logo"
src="/vuetify-logo.svg"
>
</template>
<style>
.VuetifyLogo {
height: 180px;
width: 180px;
transform: rotateY(560deg);
animation: turn 3.5s ease-out forwards 1s;
}
@keyframes turn {
100% {
transform: rotateY(0deg);
}
}
</style>

View file

@ -0,0 +1,269 @@
<template>
<v-card :color="color" :dark="dark" flat :width="width" class="my-2">
<v-row>
<v-col v-for="(inputField, index) in items" :key="index" class="py-0" cols="12" sm="12">
<v-divider v-if="inputField.section" class="my-2" />
<v-card-title v-if="inputField.section" class="pl-0">
{{ inputField.section }}
</v-card-title>
<v-card-text v-if="inputField.sectionDetails" class="pl-0 mt-0 pt-0">
{{ inputField.sectionDetails }}
</v-card-text>
<!-- Check Box -->
<v-checkbox
v-if="inputField.type === fieldTypes.BOOLEAN"
v-model="value[inputField.varName]"
class="my-0 py-0"
:label="inputField.label"
:name="inputField.varName"
:hint="inputField.hint || ''"
@change="emitBlur"
/>
<!-- Text Field -->
<v-text-field
v-else-if="inputField.type === fieldTypes.TEXT"
v-model="value[inputField.varName]"
:readonly="inputField.fixed && updateMode"
filled
rounded
class="rounded-lg"
dense
:label="inputField.label"
:name="inputField.varName"
:hint="inputField.hint || ''"
:rules="[...rulesByKey(inputField.rules), ...defaultRules]"
lazy-validation
@blur="emitBlur"
/>
<!-- Text Area -->
<v-textarea
v-else-if="inputField.type === fieldTypes.TEXT_AREA"
v-model="value[inputField.varName]"
:readonly="inputField.fixed && updateMode"
filled
rounded
class="rounded-lg"
rows="3"
auto-grow
dense
:label="inputField.label"
:name="inputField.varName"
:hint="inputField.hint || ''"
:rules="[...rulesByKey(inputField.rules), ...defaultRules]"
lazy-validation
@blur="emitBlur"
/>
<!-- Option Select -->
<v-select
v-else-if="inputField.type === fieldTypes.SELECT"
v-model="value[inputField.varName]"
:readonly="inputField.fixed && updateMode"
filled
rounded
class="rounded-lg"
:prepend-icon="inputField.icons ? value[inputField.varName] : null"
:label="inputField.label"
:name="inputField.varName"
:items="inputField.options"
:return-object="false"
lazy-validation
@blur="emitBlur"
>
<template #item="{ item }">
<v-list-item-content>
<v-list-item-title>{{ item.text }}</v-list-item-title>
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
</v-list-item-content>
</template>
</v-select>
<!-- Color Picker -->
<div v-else-if="inputField.type === fieldTypes.COLOR" class="d-flex" style="width: 100%">
<v-menu offset-y>
<template #activator="{ on }">
<v-btn class="my-2 ml-auto" style="min-width: 200px" :color="value[inputField.varName]" dark v-on="on">
{{ inputField.label }}
</v-btn>
</template>
<v-color-picker
v-model="value[inputField.varName]"
value="#7417BE"
hide-canvas
hide-inputs
show-swatches
class="mx-auto"
@input="emitBlur"
/>
</v-menu>
</div>
<div v-else-if="inputField.type === fieldTypes.OBJECT">
<base-auto-form
v-model="value[inputField.varName]"
:color="color"
:items="inputField.items"
@blur="emitBlur"
/>
</div>
<!-- List Type -->
<div v-else-if="inputField.type === fieldTypes.LIST">
<div v-for="(item, idx) in value[inputField.varName]" :key="idx">
<p>
{{ inputField.label }} {{ idx + 1 }}
<span>
<BaseButton class="ml-5" x-small delete @click="removeByIndex(value[inputField.varName], idx)" />
</span>
</p>
<v-divider class="mb-5 mx-2" />
<base-auto-form
v-model="value[inputField.varName][idx]"
:color="color"
:items="inputField.items"
@blur="emitBlur"
/>
</div>
<v-card-actions>
<v-spacer />
<BaseButton small @click="value[inputField.varName].push(getTemplate(inputField.items))"> New </BaseButton>
</v-card-actions>
</div>
</v-col>
</v-row>
</v-card>
</template>
<script>
import { validators } from "@/composables/use-validators";
import { fieldTypes } from "@/composables/forms";
import { ref } from "@vue/composition-api";
const BLUR_EVENT = "blur";
export default {
name: "BaseAutoForm",
props: {
value: {
default: null,
type: [Object, Array],
},
updateMode: {
default: false,
type: Boolean,
},
items: {
default: null,
type: Array,
},
width: {
type: [Number, String],
default: "max",
},
globalRules: {
default: null,
type: Array,
},
color: {
default: null,
type: String,
},
dark: {
default: false,
type: Boolean,
},
},
setup() {
const menu = ref({});
return {
menu,
fieldTypes,
validators,
};
},
computed: {
defaultRules() {
return this.rulesByKey(this.globalRules);
},
},
watch: {
items: {
immediate: true,
handler(val) {
// Initialize Value Object to Obtain all keys
if (!val) {
return;
}
for (let i = 0; i < val.length; i++) {
try {
if (this.value[val[i].varName]) {
continue;
}
} catch {}
if (val[i].type === "text" || val[i].type === "textarea") {
this.$set(this.value, val[i].varName, "");
} else if (val[i].type === "select") {
if (!val[i].options[0]) {
continue;
}
this.$set(this.value, val[i].varName, val[i].options[0].value);
} else if (val[i].type === "list") {
this.$set(this.value, val[i].varName, []);
} else if (val[i].type === "object") {
this.$set(this.value, val[i].varName, {});
} else if (val[i].type === "color") {
this.$set(this.value, val[i].varName, "");
this.$set(this.menu, val[i].varName, false);
}
}
},
},
},
methods: {
removeByIndex(list, index) {
// Removes the item at the index
list.splice(index, 1);
},
getTemplate(item) {
const obj = {};
item.forEach((field) => {
obj[field.varName] = "";
});
return obj;
},
rulesByKey(keys) {
const list = [];
if (keys === undefined) {
return list;
}
if (keys === null) {
return list;
}
if (keys === list) {
return list;
}
keys.forEach((key) => {
if (key in this.validators) {
list.push(this.validators[key]);
}
});
return list;
},
emitBlur() {
this.$emit(BLUR_EVENT, this.value);
},
},
};
</script>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,164 @@
<template>
<v-btn
:color="btnAttrs.color"
:small="small"
:x-small="xSmall"
:loading="loading"
:disabled="disabled"
:outlined="btnStyle.outlined"
:text="btnStyle.text"
:to="to"
@click="$emit('click')"
>
<v-icon left>
<slot name="icon">
{{ btnAttrs.icon }}
</slot>
</v-icon>
<slot name="default">
{{ btnAttrs.text }}
</slot>
</v-btn>
</template>
<script>
export default {
name: "BaseButton",
props: {
// Types
cancel: {
type: Boolean,
default: false,
},
create: {
type: Boolean,
default: false,
},
update: {
type: Boolean,
default: false,
},
edit: {
type: Boolean,
default: false,
},
delete: {
type: Boolean,
default: false,
},
// Property
loading: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
// Styles
small: {
type: Boolean,
default: false,
},
xSmall: {
type: Boolean,
default: false,
},
secondary: {
type: Boolean,
default: false,
},
minor: {
type: Boolean,
default: false,
},
to: {
type: String,
default: null,
},
},
data() {
return {
buttonOptions: {
create: {
text: "Create",
icon: "mdi-plus",
color: "success",
},
update: {
text: "Update",
icon: "mdi-edit",
color: "success",
},
save: {
text: "Save",
icon: "mdi-save",
color: "success",
},
edit: {
text: "Edit",
icon: "mdi-square-edit-outline",
color: "info",
},
delete: {
text: "Delete",
icon: "mdi-delete",
color: "error",
},
cancel: {
text: "Cancel",
icon: "mdi-close",
color: "grey",
},
},
buttonStyles: {
defaults: {
text: false,
outlined: false,
},
secondary: {
text: false,
outlined: true,
},
minor: {
outlined: false,
text: true,
},
},
};
},
computed: {
btnAttrs() {
if (this.delete) {
return this.buttonOptions.delete;
} else if (this.update) {
return this.buttonOptions.update;
} else if (this.edit) {
return this.buttonOptions.edit;
} else if (this.cancel) {
this.setMinor();
return this.buttonOptions.cancel;
} else if (this.save) {
return this.buttonOptions.save;
}
return this.buttonOptions.create;
},
btnStyle() {
if (this.secondary) {
return this.buttonStyles.secondary;
} else if (this.minor) {
return this.buttonStyles.minor;
}
return this.buttonStyles.defaults;
},
},
methods: {
setMinor() {
this.buttonStyles.defaults = this.buttonStyles.minor;
},
setSecondary() {
this.buttonStyles.defaults = this.buttonStyles.secondary;
},
},
};
</script>

View file

@ -0,0 +1,19 @@
<template>
<v-card flat class="pb-2">
<h2>{{ title }}</h2>
<p class="pb-0 mb-0">
<slot />
</p>
</v-card>
</template>
<script>
export default {
props: {
title: {
type: String,
default: "Place Holder"
}
}
}
</script>

View file

@ -0,0 +1,22 @@
<template>
<v-divider :width="width" class="mx-auto" :class="color" :style="`border-width: ${thickness} !important`" />
</template>
<script>
export default {
props: {
width: {
type: String,
default: "100px"
},
thickness: {
type: String,
default: "2px"
},
color: {
type: String,
default: "accent"
}
}
}
</script>

View file

@ -0,0 +1 @@
export { fieldTypes } from "./use-field-types";

View file

@ -0,0 +1,11 @@
export const fieldTypes = {
TEXT: "text",
TEXT_AREA: "textarea",
LIST: "list",
SELECT: "select",
OBJECT: "object",
BOOLEAN: "boolean",
COLOR: "color",
};

View file

@ -0,0 +1,8 @@
const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
export const validators = {
required: (v: string) => !!v || "This Field is Required",
email: (v: string) => !v || EMAIL_REGEX.test(v) || "Email Must Be Valid",
whitespace: (v: string) => !v || v.split(" ").length <= 1 || "No Whitespace Allowed"
}

View file

@ -0,0 +1,17 @@
<template>
<v-app dark> </v-app>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -17,6 +17,7 @@
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/pwa": "^3.3.5",
"@vueuse/core": "^5.2.0",
"core-js": "^3.15.1",
"nuxt": "^2.15.7",
"vuetify": "^2.5.5"
@ -25,6 +26,7 @@
"@babel/eslint-parser": "^7.14.7",
"@nuxt/types": "^2.15.7",
"@nuxt/typescript-build": "^2.1.0",
"@nuxtjs/composition-api": "^0.26.0",
"@nuxtjs/eslint-config-typescript": "^6.0.1",
"@nuxtjs/eslint-module": "^3.0.2",
"@nuxtjs/vuetify": "^1.12.1",
@ -36,4 +38,4 @@
"lint-staged": "^10.5.4",
"prettier": "^2.3.2"
}
}
}

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -1,19 +0,0 @@
<template>
<v-row>
<v-col class="text-center">
<img
src="/v.png"
alt="Vuetify.js"
class="mb-5"
>
<blockquote class="blockquote">
&#8220;First, solve the problem. Then, write the code.&#8221;
<footer>
<small>
<em>&mdash;John Johnson</em>
</small>
</footer>
</blockquote>
</v-col>
</v-row>
</template>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -0,0 +1,16 @@
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
return {}
}
})
</script>
<style scoped>
</style>

View file

@ -3,11 +3,7 @@
"target": "ES2018",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ESNext",
"ESNext.AsyncIterable",
"DOM"
],
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
@ -16,22 +12,10 @@
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
"~/*": ["./*"],
"@/*": ["./*"]
},
"types": [
"@nuxt/types",
"@nuxtjs/axios",
"@types/node"
]
"types": ["@nuxt/types", "@nuxtjs/axios", "@nuxt/content", "@types/node"]
},
"exclude": [
"node_modules",
".nuxt",
"dist"
]
"exclude": ["node_modules", ".nuxt", "dist"]
}

View file

@ -1345,6 +1345,19 @@
consola "^2.15.3"
defu "^5.0.0"
"@nuxtjs/composition-api@^0.26.0":
version "0.26.0"
resolved "https://registry.yarnpkg.com/@nuxtjs/composition-api/-/composition-api-0.26.0.tgz#0fbda4fc942ca1e346b7c6d55a1fb331ff931a2a"
integrity sha512-+4L9YDEN5h/vBY6xbBKPMIhgxbPv7psE4IVgKlF+QIekou6oN8m0T+QR2JLE0dHKwzicUbZLCr1v2Qw5T7L48A==
dependencies:
"@vue/composition-api" "^1.0.4"
defu "^5.0.0"
estree-walker "^2.0.2"
fs-extra "^9.1.0"
magic-string "^0.25.7"
ufo "^0.7.7"
upath "^2.0.1"
"@nuxtjs/eslint-config-typescript@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/eslint-config-typescript/-/eslint-config-typescript-6.0.1.tgz#11e91a5e25aca6855ec7525080da694c4b3cd4d4"
@ -1934,6 +1947,28 @@
optionalDependencies:
prettier "^1.18.2"
"@vue/composition-api@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@vue/composition-api/-/composition-api-1.0.4.tgz#6162b3ba306a8995afdce969a66e168e6743718d"
integrity sha512-3OzvW8RS7/7kxE+SDXm8zSLZRy9GtCYwyLYdnGtIsqXRSt5nWkA7zu80Cw8Vg+67jglrpMdVBBy7AUXAB34ZSw==
dependencies:
tslib "^2.3.0"
"@vueuse/core@^5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-5.2.0.tgz#c2c0b89d9506fdfca41cd3056e38478226bc5e31"
integrity sha512-IS2QnVZ4lTlRUtgPHJ4ttTatXGWRZuvj/GJDVp8FRlUjzKXD0YjZtx0280FCg7y3YejXV2QGhY+/YrXybMjpPw==
dependencies:
"@vueuse/shared" "5.2.0"
vue-demi "*"
"@vueuse/shared@5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-5.2.0.tgz#894921cf5e5fc7f9afa7d7870800b0c9cd265e0c"
integrity sha512-PD2PBaEcdl+pXXeKjhU+LZqQuA9xX/6mtVGIYG29xsjH7W3IjU164nwCAfDe2Lsy8rMaYmVqVSB6PHtiZZyDSg==
dependencies:
vue-demi "*"
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@ -4160,6 +4195,11 @@ estraverse@^5.1.0, estraverse@^5.2.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@ -5803,6 +5843,13 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
magic-string@^0.25.7:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
dependencies:
sourcemap-codec "^1.4.4"
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
@ -8316,6 +8363,11 @@ source-map@^0.7.3, source-map@~0.7.2:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.4:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
spdx-correct@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
@ -8843,7 +8895,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3:
tslib@^2.0.3, tslib@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
@ -8902,7 +8954,7 @@ ua-parser-js@^0.7.28:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
ufo@^0.7.4:
ufo@^0.7.4, ufo@^0.7.7:
version "0.7.7"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.7.7.tgz#0062f9e5e790819b0fb23ca24d7c63a4011c036a"
integrity sha512-N25aY3HBkJBnahm+2l4JRBBrX5I+JPakF/tDHYDTjd3wUR7iFLdyiPhj8mBwBz21v728BKwM9L9tgBfCntgdlw==
@ -9141,6 +9193,11 @@ vue-client-only@^2.0.0:
resolved "https://registry.yarnpkg.com/vue-client-only/-/vue-client-only-2.1.0.tgz#1a67a47b8ecacfa86d75830173fffee3bf8a4ee3"
integrity sha512-vKl1skEKn8EK9f8P2ZzhRnuaRHLHrlt1sbRmazlvsx6EiC3A8oWF8YCBrMJzoN+W3OnElwIGbVjsx6/xelY1AA==
vue-demi@*:
version "0.11.2"
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.11.2.tgz#faa06da53887c493a695b997f4fcb4784a667990"
integrity sha512-J+X8Au6BhQdcej6LY4O986634hZLu55L0ewU2j8my7WIKlu8cK0dqmdUxqVHHMd/cMrKKZ9SywB/id6aLhwCtA==
vue-eslint-parser@^7.1.1, vue-eslint-parser@^7.10.0:
version "7.10.0"
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.10.0.tgz#ea4e4b10fd10aa35c8a79ac783488d8abcd29be8"