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,15 @@
<script setup lang="ts">
useHead({
title: "404. Not Found",
});
definePageMeta({
layout: "404",
});
</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"> Page Not Found </span>
</h1>
</template>

39
frontend/pages/home.vue Normal file
View file

@ -0,0 +1,39 @@
<script setup lang="ts">
import { type Location } from '~~/lib/api/classes/locations';
definePageMeta({
layout: 'home',
});
useHead({
title: 'Homebox | Home',
});
const api = useUserApi();
const locations = ref<Location[]>([]);
onMounted(async () => {
const { data } = await api.locations.getAll();
if (data) {
locations.value = data.items;
}
});
</script>
<template>
<BaseContainer>
<BaseSectionHeader class="mb-5"> Storage Locations </BaseSectionHeader>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<NuxtLink
:to="`/location/${l.id}`"
class="card bg-primary text-primary-content hover:-translate-y-1 focus:-translate-y-1 transition duration-300"
v-for="l in locations"
>
<div class="card-body p-4">
<h2 class="flex items-center gap-2">
<Icon name="heroicons-map-pin" class="h-5 w-5 text-white" height="25" />
{{ l.name }}
<!-- <span class="badge badge-accent badge-lg ml-auto text-accent-content text-lg">0</span> -->
</h2>
</div>
</NuxtLink>
</div>
</BaseContainer>
</template>

190
frontend/pages/index.vue Normal file
View file

@ -0,0 +1,190 @@
<script setup lang="ts">
import TextField from '@/components/Form/TextField.vue';
import { useNotifier } from '@/composables/use-notifier';
import { usePublicApi } from '@/composables/use-api';
import { useAuthStore } from '~~/stores/auth';
useHead({
title: 'Homebox | Organize and Tag Your Stuff',
});
definePageMeta({
layout: 'empty',
});
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',
},
];
const api = usePublicApi();
async function registerUser() {
loading.value = true;
// Print Values of registerFields
const { data, error } = await api.register({
user: {
name: registerFields[0].value,
email: registerFields[1].value,
password: registerFields[3].value,
},
groupName: registerFields[2].value,
});
if (error) {
toast.error('Problem registering user');
} else {
toast.success('User registered');
}
loading.value = false;
}
const loginFields = [
{
label: 'Email',
value: '',
},
{
label: 'Password',
value: '',
type: 'password',
},
];
const authStore = useAuthStore();
const toast = useNotifier();
const loading = ref(false);
async function login() {
loading.value = true;
const { data, error } = await api.login(loginFields[0].value, loginFields[1].value);
if (error) {
toast.error('Invalid email or password');
} else {
toast.success('Logged in successfully');
authStore.$patch({
token: data.token,
expires: data.expiresAt,
});
navigateTo('/home');
}
loading.value = false;
}
const registerForm = ref(false);
function toggleLogin() {
registerForm.value = !registerForm.value;
}
</script>
<template>
<div>
<header class="sm:px-6 py-2 lg:p-14 sm:py-6">
<h2 class="mt-1 text-4xl font-bold tracking-tight text-base-content sm:text-5xl lg:text-6xl">Homebox</h2>
<p class="ml-1 text-lg text-base-content/50">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"
:class="loading ? 'loading' : ''"
:disabled="loading"
>
Register
</button>
</div>
</div>
</div>
</form>
<form v-else @submit.prevent="login">
<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 type="submit" class="btn btn-primary" :class="loading ? 'loading' : ''" :disabled="loading">
Login
</button>
</div>
</div>
</div>
</form>
</Transition>
<div class="text-center mt-2">
<button @click="toggleLogin">
{{ registerForm ? 'Already a User? Login' : 'Not a User? Register' }}
</button>
</div>
</div>
<div class="min-w-full absolute bottom-0 z-[-1]">
<svg class="fill-primary" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none" viewBox="0 0 1440 320">
<path
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-primary flex-col flex min-h-[32vh]">
<div class="mt-auto mx-auto mb-8">
<p class="text-center text-gray-200">&copy; 2022 Contents. All Rights Reserved. Haybytes LLC</p>
</div>
</div>
</div>
</div>
</template>
<style lang="css" scoped>
.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>

View file

@ -0,0 +1,136 @@
<script setup lang="ts">
import { Location } from '~~/lib/api/classes/locations';
import ActionsDivider from '../../components/Base/ActionsDivider.vue';
definePageMeta({
layout: 'home',
});
const route = useRoute();
const api = useUserApi();
const toast = useNotifier();
const preferences = useLocationViewPreferences();
const location = ref<Location | null>(null);
const locationId = computed<string>(() => route.params.id as string);
function maybeTimeAgo(date?: string): string {
if (!date) {
return '??';
}
const time = new Date(date);
return `${useTimeAgo(time).value} (${useDateFormat(time, 'MM-DD-YYYY').value})`;
}
const details = computed(() => {
const dt = {
Name: location.value?.name || '',
Description: location.value?.description || '',
};
if (preferences.value.showDetails) {
dt['Created At'] = maybeTimeAgo(location.value?.createdAt);
dt['Updated At'] = maybeTimeAgo(location.value?.updatedAt);
dt['Database ID'] = location.value?.id || '';
dt['Group Id'] = location.value?.groupId || '';
}
return dt;
});
onMounted(async () => {
const { data, error } = await api.locations.get(locationId.value);
if (error) {
toast.error('Failed to load location');
navigateTo('/home');
return;
}
location.value = data;
});
const { reveal } = useConfirm();
async function confirmDelete() {
const { isCanceled } = await reveal('Are you sure you want to delete this location? This action cannot be undone.');
if (isCanceled) {
return;
}
const { error } = await api.locations.delete(locationId.value);
if (error) {
toast.error('Failed to delete location');
return;
}
toast.success('Location deleted');
navigateTo('/home');
}
const updateModal = ref(false);
const updating = ref(false);
const updateData = reactive({
name: '',
description: '',
});
function openUpdate() {
updateData.name = location.value?.name || '';
updateData.description = location.value?.description || '';
updateModal.value = true;
}
async function update() {
updating.value = true;
const { error, data } = await api.locations.update(locationId.value, updateData);
if (error) {
toast.error('Failed to update location');
return;
}
toast.success('Location updated');
console.log(data);
location.value = data;
updateModal.value = false;
updating.value = false;
}
</script>
<template>
<BaseContainer>
<BaseModal v-model="updateModal">
<template #title> Update Location </template>
<form v-if="location" @submit.prevent="update">
<FormTextField :autofocus="true" label="Location Name" v-model="updateData.name" />
<FormTextField label="Location Description" v-model="updateData.description" />
<div class="modal-action">
<BaseButton type="submit" :loading="updating"> Update </BaseButton>
</div>
</form>
</BaseModal>
<section>
<BaseSectionHeader class="mb-5">
{{ location ? location.name : '' }}
</BaseSectionHeader>
<BaseDetails class="mb-2" :details="details">
<template #title> Location Details </template>
</BaseDetails>
<div class="form-control ml-auto mr-2 max-w-[130px]">
<label class="label cursor-pointer">
<input type="checkbox" v-model.checked="preferences.showDetails" class="checkbox" />
<span class="label-text"> Detailed View </span>
</label>
</div>
<ActionsDivider @delete="confirmDelete" @edit="openUpdate" />
</section>
<!-- <section>
<BaseSectionHeader> Items </BaseSectionHeader>
</section> -->
</BaseContainer>
</template>