move to nuxt

This commit is contained in:
Hayden 2022-09-01 14:32:03 -08:00
parent 890eb55d27
commit 26ecb5a9d4
93 changed files with 5273 additions and 4749 deletions

View file

@ -0,0 +1,139 @@
<script lang="ts" setup>
import { useAuthStore } from '~~/stores/auth';
const authStore = useAuthStore();
const api = useUserApi();
async function logout() {
const { error } = await authStore.logout(api);
if (error) {
return;
}
navigateTo('/');
}
const links = [
{
name: 'Home',
href: '/home',
},
{
name: 'Logout',
action: logout,
last: true,
},
];
const dropdown = [
{
name: 'Location',
action: () => {
modal.value = true;
},
},
{
name: 'Item / Asset',
action: () => {},
},
{
name: 'Label',
action: () => {},
},
];
// ----------------------------
// Location Stuff
// Should move to own component
const locationLoading = ref(false);
const locationForm = reactive({
name: '',
description: '',
});
const locationNameRef = ref(null);
const triggerFocus = ref(false);
const modal = ref(false);
whenever(
() => modal.value,
() => {
triggerFocus.value = true;
}
);
async function createLocation() {
locationLoading.value = true;
const { data } = await api.locations.create(locationForm);
if (data) {
navigateTo(`/location/${data.id}`);
}
locationLoading.value = false;
modal.value = false;
locationForm.name = '';
locationForm.description = '';
triggerFocus.value = false;
}
</script>
<template>
<ModalConfirm />
<BaseModal v-model="modal">
<template #title> Create Location </template>
<form @submit.prevent="createLocation">
<FormTextField
:trigger-focus="triggerFocus"
ref="locationNameRef"
:autofocus="true"
label="Location Name"
v-model="locationForm.name"
/>
<FormTextField label="Location Description" v-model="locationForm.description" />
<div class="modal-action">
<BaseButton type="submit" :loading="locationLoading"> Create </BaseButton>
</div>
</form>
</BaseModal>
<BaseContainer is="header" class="py-6">
<h2 class="mt-1 text-4xl font-bold tracking-tight text-base-content sm:text-5xl lg:text-6xl">Homebox</h2>
<div class="ml-1 mt-2 text-lg text-base-content/50 space-x-2">
<template v-for="link in links">
<NuxtLink
v-if="!link.action"
class="hover:text-base-content transition-color duration-200 italic"
:to="link.href"
>
{{ link.name }}
</NuxtLink>
<button
for="location-form-modal"
v-else
@click="link.action"
class="hover:text-base-content transition-color duration-200 italic"
>
{{ link.name }}
</button>
<span v-if="!link.last"> / </span>
</template>
</div>
<div class="flex mt-6">
<div class="dropdown">
<label tabindex="0" class="btn btn-sm">
<span>
<Icon name="mdi-plus" class="w-5 h-5 mr-2" />
</span>
Create
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
<li v-for="btn in dropdown">
<button @click="btn.action">
{{ btn.name }}
</button>
</li>
</ul>
</div>
</div>
</BaseContainer>
</template>

View file

@ -0,0 +1,58 @@
<template>
<div class="force-above fixed top-2 right-2 w-[300px]">
<TransitionGroup name="notify" tag="div">
<div
v-for="(notify, index) in notifications.slice(0, 4)"
:key="notify.id"
class="my-2 w-[300px] rounded-md p-3 text-sm text-white opacity-75"
:class="{
'bg-primary': notify.type === 'info',
'bg-red-600': notify.type === 'error',
'bg-green-600': notify.type === 'success',
}"
@click="dropNotification(index)"
>
<div class="flex gap-1">
<template v-if="notify.type == 'success'">
<Icon name="heroicons-check" class="h-5 w-5" />
</template>
<template v-if="notify.type == 'info'">
<Icon name="heroicons-information-circle" class="h-5 w-5" />
</template>
<template v-if="notify.type == 'error'">
<Icon name="heroicons-bell-alert" class="h-5 w-5" />
</template>
{{ notify.message }}
</div>
</div>
</TransitionGroup>
</div>
</template>
<script setup lang="ts">
import { useNotifications } from '@/composables/use-notifier';
const { notifications, dropNotification } = useNotifications();
</script>
<style scoped>
.force-above {
z-index: 9999;
}
.notify-move,
.notify-enter-active,
.notify-leave-active {
transition: all 0.5s ease;
}
.notify-enter-from,
.notify-leave-to {
opacity: 0;
transform: translateY(-30px);
}
.notify-leave-active {
position: absolute;
transform: translateY(30px);
}
</style>

View file

@ -0,0 +1,21 @@
<template>
<div class="relative">
<div class="absolute inset-0 flex items-center" aria-hidden="true">
<div class="w-full border-t border-primary" />
</div>
<div class="relative flex justify-center">
<span class="isolate inline-flex -space-x-px rounded-md shadow-sm">
<div class="btn-group">
<button @click="$emit('edit')" name="options" class="btn btn-sm btn-primary">
<Icon name="heroicons-pencil" class="h-5 w-5 mr-1" aria-hidden="true" />
<span> Edit </span>
</button>
<button @click="$emit('delete')" name="options" class="btn btn-sm btn-primary">
<Icon name="heroicons-trash" class="h-5 w-5 mr-1" aria-hidden="true" />
<span> Delete </span>
</button>
</div>
</span>
</div>
</div>
</template>

View file

@ -0,0 +1,24 @@
<template>
<button
:disabled="disabled || loading"
class="btn"
:class="{
loading: loading,
}"
>
<slot />
</button>
</template>
<script setup lang="ts">
defineProps({
loading: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
});
</script>

View file

@ -0,0 +1,14 @@
<script lang="ts" setup>
defineProps({
is: {
type: String,
default: 'div',
},
});
</script>
<template>
<component :is="is" class="container max-w-6xl mx-auto px-4">
<slot />
</component>
</template>

View file

@ -0,0 +1,37 @@
<template>
<div class="overflow-hidden card bg-base-100 shadow-xl sm:rounded-lg">
<div class="px-4 py-5 sm:px-6 bg-neutral">
<h3 class="text-lg font-medium leading-6 text-neutral-content">
<slot name="title"></slot>
</h3>
<p v-if="$slots.subtitle" class="mt-1 max-w-2xl text-sm text-gray-500">
<slot name="subtitle"></slot>
</p>
</div>
<div class="border-t border-gray-300 px-4 py-5 sm:p-0">
<dl class="sm:divide-y sm:divide-gray-300">
<div v-for="(dValue, dKey) in details" class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
{{ dKey }}
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ dValue }}
</dd>
</div>
</dl>
</div>
</div>
</template>
<script setup lang="ts">
type StringLike = string | number | boolean;
defineProps({
details: {
type: Object as () => Record<string, StringLike>,
required: true,
},
});
</script>
<style scoped></style>

View file

@ -0,0 +1,45 @@
<template>
<div class="z-[9999]">
<input type="checkbox" :id="modalId" class="modal-toggle" v-model="modal" />
<div class="modal">
<div class="modal-box relative">
<button @click="close" :for="modalId" class="btn btn-sm btn-circle absolute right-2 top-2"></button>
<h3 class="font-bold text-lg">
<slot name="title"></slot>
</h3>
<slot> </slot>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['cancel', 'update:modelValue']);
const props = defineProps({
modelValue: {
type: Boolean,
required: true,
},
/**
* in readonly mode the modal only `emits` a "cancel" event to indicate
* that the modal was closed via the "x" button. The parent component is
* responsible for closing the modal.
*/
readonly: {
type: Boolean,
default: false,
},
});
function close() {
if (props.readonly) {
emit('cancel');
return;
}
modal.value = false;
}
const modalId = useId();
const modal = useVModel(props, 'modelValue', emit);
</script>

View file

@ -0,0 +1,10 @@
<template>
<div class="border-b border-base-200 pb-3">
<h3 class="text-xl font-medium leading-4 text-base-content">
<slot />
</h3>
<p v-if="$slots.description" class="mt-2 max-w-4xl text-sm text-gray-500">
<slot name="description" />
</p>
</div>
</template>

View file

@ -0,0 +1,42 @@
<template>
<div class="form-control w-full">
<label class="label">
<span class="label-text">{{ label }}</span>
</label>
<input ref="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',
},
triggerFocus: {
type: Boolean,
default: null,
},
});
const input = ref<HTMLElement | null>(null);
whenever(
() => props.triggerFocus,
() => {
if (input.value) {
input.value.focus();
}
}
);
const value = useVModel(props, 'modelValue');
</script>

View file

@ -0,0 +1,31 @@
<script setup lang="ts">
import type { Ref } from 'vue';
import type { IconifyIcon } from '@iconify/vue';
import { Icon as Iconify, loadIcon } from '@iconify/vue';
const nuxtApp = useNuxtApp();
const props = defineProps({
name: {
type: String,
required: true,
},
});
const icon: Ref<IconifyIcon | null> = ref(null);
const component = computed(() => nuxtApp.vueApp.component(props.name));
icon.value = await loadIcon(props.name).catch(_ => null);
watch(
() => props.name,
async () => {
icon.value = await loadIcon(props.name).catch(_ => null);
}
);
</script>
<template>
<Iconify v-if="icon" :icon="icon" class="inline-block w-5 h-5" />
<Component :is="component" v-else-if="component" />
<span v-else>{{ name }}</span>
</template>

View file

@ -0,0 +1,15 @@
<template>
<BaseModal @cancel="cancel(false)" v-model="isRevealed" readonly>
<template #title> Confirm </template>
<div>
<p>{{ text }}</p>
</div>
<div class="modal-action">
<BaseButton type="submit" @click="confirm(true)"> Confirm </BaseButton>
</div>
</BaseModal>
</template>
<script setup lang="ts">
const { text, isRevealed, confirm, cancel } = useConfirm();
</script>