use eslint for formatting

This commit is contained in:
Hayden 2022-09-09 14:38:45 -08:00
parent beefb88367
commit a8780c942a
55 changed files with 456 additions and 457 deletions

51
frontend/.eslintrc.js Normal file
View file

@ -0,0 +1,51 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:vue/essential",
"plugin:@typescript-eslint/recommended",
"@nuxtjs/eslint-config-typescript",
"plugin:vue/vue3-recommended",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: "latest",
parser: "@typescript-eslint/parser",
sourceType: "module",
},
plugins: ["vue", "@typescript-eslint"],
rules: {
"vue/multi-word-component-names": "off",
"vue/no-setup-props-destructure": 0,
"vue/no-multiple-template-root": 0,
"no-console": 0,
"vue/no-v-model-argument": 0,
"@typescript-eslint/ban-ts-comment": 0,
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
ignoreRestSiblings: true,
destructuredArrayIgnorePattern: "_",
caughtErrors: "none",
},
],
"prettier/prettier": [
"warn",
{
arrowParens: "avoid",
semi: true,
tabWidth: 2,
useTabs: false,
vueIndentScriptAndStyle: true,
singleQuote: false,
trailingComma: "es5",
printWidth: 120,
},
],
},
};

View file

@ -1,42 +0,0 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:vue/essential",
"plugin:@typescript-eslint/recommended",
"@nuxtjs/eslint-config-typescript",
"plugin:vue/vue3-recommended",
"plugin:prettier/recommended"
],
"parserOptions": {
"ecmaVersion": "latest",
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"rules": {
"vue/multi-word-component-names": "off",
"vue/no-setup-props-destructure": 0,
"vue/no-multiple-template-root": 0,
"no-console": 1,
"vue/no-v-model-argument": 0,
"@typescript-eslint/ban-ts-comment": 0,
"prettier/prettier": [
"warn",
{
"arrowParens": "avoid",
"semi": true,
"tabWidth": 2,
"useTabs": false,
"vueIndentScriptAndStyle": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 120
}
]
}
}

View file

@ -1,10 +0,0 @@
{
"arrowParens": "avoid",
"semi": true,
"tabWidth": 2,
"useTabs": false,
"vueIndentScriptAndStyle": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 120
}

View file

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useAuthStore } from '~~/stores/auth'; import { useAuthStore } from "~~/stores/auth";
const authStore = useAuthStore(); const authStore = useAuthStore();
const api = useUserApi(); const api = useUserApi();
@ -10,16 +10,16 @@
return; return;
} }
navigateTo('/'); navigateTo("/");
} }
const links = [ const links = [
{ {
name: 'Home', name: "Home",
href: '/home', href: "/home",
}, },
{ {
name: 'Logout', name: "Logout",
action: logout, action: logout,
last: true, last: true,
}, },
@ -33,19 +33,19 @@
const dropdown = [ const dropdown = [
{ {
name: 'Item / Asset', name: "Item / Asset",
action: () => { action: () => {
modals.item = true; modals.item = true;
}, },
}, },
{ {
name: 'Location', name: "Location",
action: () => { action: () => {
modals.location = true; modals.location = true;
}, },
}, },
{ {
name: 'Label', name: "Label",
action: () => { action: () => {
modals.label = true; modals.label = true;
}, },

View file

@ -31,7 +31,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useNotifications } from '@/composables/use-notifier'; import { useNotifications } from "@/composables/use-notifier";
const { notifications, dropNotification } = useNotifications(); const { notifications, dropNotification } = useNotifications();
</script> </script>

View file

@ -35,7 +35,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
type Sizes = 'sm' | 'md' | 'lg'; type Sizes = "sm" | "md" | "lg";
const props = defineProps({ const props = defineProps({
loading: { loading: {
@ -48,7 +48,7 @@
}, },
size: { size: {
type: String as () => Sizes, type: String as () => Sizes,
default: 'md', default: "md",
}, },
to: { to: {
type: String as () => string | null, type: String as () => string | null,

View file

@ -2,7 +2,7 @@
defineProps({ defineProps({
cmp: { cmp: {
type: String, type: String,
default: 'div', default: "div",
}, },
}); });
</script> </script>

View file

@ -15,7 +15,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const emit = defineEmits(['cancel', 'update:modelValue']); const emit = defineEmits(["cancel", "update:modelValue"]);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Boolean, type: Boolean,
@ -34,12 +34,12 @@
function close() { function close() {
if (props.readonly) { if (props.readonly) {
emit('cancel'); emit("cancel");
return; return;
} }
modal.value = false; modal.value = false;
} }
const modalId = useId(); const modalId = useId();
const modal = useVModel(props, 'modelValue', emit); const modal = useVModel(props, "modelValue", emit);
</script> </script>

View file

@ -36,7 +36,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const emit = defineEmits(['update:modelValue', 'update:text']); const emit = defineEmits(["update:modelValue", "update:text"]);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@ -50,12 +50,12 @@
}, },
}); });
const selected = useVModel(props, 'modelValue', emit); const selected = useVModel(props, "modelValue", emit);
const dateText = computed(() => { const dateText = computed(() => {
if (selected.value) { if (selected.value) {
return selected.value.toLocaleDateString(); return selected.value.toLocaleDateString();
} }
return ''; return "";
}); });
const time = ref(new Date()); const time = ref(new Date());
@ -69,7 +69,7 @@
}); });
const month = computed(() => { const month = computed(() => {
return time.value.toLocaleString('default', { month: 'long' }); return time.value.toLocaleString("default", { month: "long" });
}); });
const year = computed(() => { const year = computed(() => {
@ -87,7 +87,7 @@
} }
const daysIdx = computed(() => { const daysIdx = computed(() => {
return ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; return ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
}); });
function select(e: MouseEvent, day: Date) { function select(e: MouseEvent, day: Date) {
@ -117,7 +117,7 @@
for (let i = 0; i < firstDay; i++) { for (let i = 0; i < firstDay; i++) {
days.push({ days.push({
number: '', number: "",
date: new Date(), date: new Date(),
}); });
} }

View file

@ -6,7 +6,7 @@
<div class="dropdown dropdown-top sm:dropdown-end"> <div class="dropdown dropdown-top sm:dropdown-end">
<div tabindex="0" class="w-full min-h-[48px] flex gap-2 p-4 flex-wrap border border-gray-400 rounded-lg"> <div tabindex="0" class="w-full min-h-[48px] flex gap-2 p-4 flex-wrap border border-gray-400 rounded-lg">
<span v-for="itm in value" :key="name != '' ? itm[name] : itm" class="badge"> <span v-for="itm in value" :key="name != '' ? itm[name] : itm" class="badge">
{{ name != '' ? itm[name] : itm }} {{ name != "" ? itm[name] : itm }}
</span> </span>
</div> </div>
<ul <ul
@ -21,7 +21,7 @@
}" }"
> >
<button type="button" @click="toggle(idx)"> <button type="button" @click="toggle(idx)">
{{ name != '' ? obj[name] : obj }} {{ name != "" ? obj[name] : obj }}
</button> </button>
</li> </li>
</ul> </ul>
@ -30,11 +30,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(["update:modelValue"]);
const props = defineProps({ const props = defineProps({
label: { label: {
type: String, type: String,
default: '', default: "",
}, },
modelValue: { modelValue: {
type: Array as () => any[], type: Array as () => any[],
@ -46,7 +46,7 @@
}, },
name: { name: {
type: String, type: String,
default: 'name', default: "name",
}, },
selectFirst: { selectFirst: {
type: Boolean, type: Boolean,
@ -77,5 +77,5 @@
} }
); );
const value = useVModel(props, 'modelValue', emit); const value = useVModel(props, "modelValue", emit);
</script> </script>

View file

@ -6,7 +6,7 @@
<select v-model="value" class="select select-bordered"> <select v-model="value" class="select select-bordered">
<option disabled selected>Pick one</option> <option disabled selected>Pick one</option>
<option v-for="obj in items" :key="name != '' ? obj[name] : obj" :value="obj"> <option v-for="obj in items" :key="name != '' ? obj[name] : obj" :value="obj">
{{ name != '' ? obj[name] : obj }} {{ name != "" ? obj[name] : obj }}
</option> </option>
</select> </select>
<!-- <label class="label"> <!-- <label class="label">
@ -17,11 +17,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(["update:modelValue"]);
const props = defineProps({ const props = defineProps({
label: { label: {
type: String, type: String,
default: '', default: "",
}, },
modelValue: { modelValue: {
type: Object as any, type: Object as any,
@ -33,7 +33,7 @@
}, },
name: { name: {
type: String, type: String,
default: 'name', default: "name",
}, },
selectFirst: { selectFirst: {
type: Boolean, type: Boolean,
@ -50,5 +50,5 @@
} }
); );
const value = useVModel(props, 'modelValue', emit); const value = useVModel(props, "modelValue", emit);
</script> </script>

View file

@ -23,7 +23,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(["update:modelValue"]);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: [String], type: [String],
@ -35,7 +35,7 @@
}, },
type: { type: {
type: String, type: String,
default: 'text', default: "text",
}, },
limit: { limit: {
type: [Number, String], type: [Number, String],
@ -43,7 +43,7 @@
}, },
placeholder: { placeholder: {
type: String, type: String,
default: '', default: "",
}, },
inline: { inline: {
type: Boolean, type: Boolean,
@ -51,7 +51,7 @@
}, },
}); });
const value = useVModel(props, 'modelValue', emit); const value = useVModel(props, "modelValue", emit);
const valueLen = computed(() => { const valueLen = computed(() => {
return value.value ? value.value.length : 0; return value.value ? value.value.length : 0;
}); });

View file

@ -17,7 +17,7 @@
const props = defineProps({ const props = defineProps({
label: { label: {
type: String, type: String,
default: '', default: "",
}, },
modelValue: { modelValue: {
type: [String, Number], type: [String, Number],
@ -25,7 +25,7 @@
}, },
type: { type: {
type: String, type: String,
default: 'text', default: "text",
}, },
triggerFocus: { triggerFocus: {
type: Boolean, type: Boolean,
@ -48,5 +48,5 @@
} }
); );
const value = useVModel(props, 'modelValue'); const value = useVModel(props, "modelValue");
</script> </script>

View file

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Ref } from 'vue'; import type { Ref } from "vue";
import type { IconifyIcon } from '@iconify/vue'; import type { IconifyIcon } from "@iconify/vue";
import { Icon as Iconify, loadIcon } from '@iconify/vue'; import { Icon as Iconify, loadIcon } from "@iconify/vue";
const nuxtApp = useNuxtApp(); const nuxtApp = useNuxtApp();
const props = defineProps({ const props = defineProps({
@ -14,12 +14,12 @@
const icon: Ref<IconifyIcon | null> = ref(null); const icon: Ref<IconifyIcon | null> = ref(null);
const component = computed(() => nuxtApp.vueApp.component(props.name)); const component = computed(() => nuxtApp.vueApp.component(props.name));
icon.value = await loadIcon(props.name).catch(_ => null); icon.value = await loadIcon(props.name).catch(() => null);
watch( watch(
() => props.name, () => props.name,
async () => { async () => {
icon.value = await loadIcon(props.name).catch(_ => null); icon.value = await loadIcon(props.name).catch(() => null);
} }
); );
</script> </script>

View file

@ -22,7 +22,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Item } from '~~/lib/api/classes/items'; import { Item } from "~~/lib/api/classes/items";
const props = defineProps({ const props = defineProps({
item: { item: {

View file

@ -26,7 +26,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { type Location } from '~~/lib/api/classes/locations'; import { type Location } from "~~/lib/api/classes/locations";
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Boolean, type: Boolean,
@ -36,21 +36,21 @@
const submitBtn = ref(null); const submitBtn = ref(null);
const modal = useVModel(props, 'modelValue'); const modal = useVModel(props, "modelValue");
const loading = ref(false); const loading = ref(false);
const focused = ref(false); const focused = ref(false);
const form = reactive({ const form = reactive({
location: {} as Location, location: {} as Location,
name: '', name: "",
description: '', description: "",
color: '', // Future! color: "", // Future!
labels: [], labels: [],
}); });
function reset() { function reset() {
form.name = ''; form.name = "";
form.description = ''; form.description = "";
form.color = ''; form.color = "";
focused.value = false; focused.value = false;
modal.value = false; modal.value = false;
loading.value = false; loading.value = false;
@ -93,7 +93,7 @@
return; return;
} }
toast.success('Item created'); toast.success("Item created");
reset(); reset();
} }
</script> </script>

View file

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Label } from '~~/lib/api/classes/labels'; import { Label } from "~~/lib/api/classes/labels";
export type sizes = 'sm' | 'md' | 'lg'; export type sizes = "sm" | "md" | "lg";
defineProps({ defineProps({
label: { label: {
type: Object as () => Label, type: Object as () => Label,
@ -9,7 +9,7 @@
}, },
size: { size: {
type: String as () => sizes, type: String as () => sizes,
default: 'md', default: "md",
}, },
}); });

View file

@ -25,19 +25,19 @@
}, },
}); });
const modal = useVModel(props, 'modelValue'); const modal = useVModel(props, "modelValue");
const loading = ref(false); const loading = ref(false);
const focused = ref(false); const focused = ref(false);
const form = reactive({ const form = reactive({
name: '', name: "",
description: '', description: "",
color: '', // Future! color: "", // Future!
}); });
function reset() { function reset() {
form.name = ''; form.name = "";
form.description = ''; form.description = "";
form.color = ''; form.color = "";
focused.value = false; focused.value = false;
modal.value = false; modal.value = false;
loading.value = false; loading.value = false;
@ -60,7 +60,7 @@
return; return;
} }
toast.success('Label created'); toast.success("Label created");
reset(); reset();
} }
</script> </script>

View file

@ -26,7 +26,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Location } from '~~/lib/api/classes/locations'; import { Location } from "~~/lib/api/classes/locations";
defineProps({ defineProps({
location: { location: {

View file

@ -25,12 +25,12 @@
}, },
}); });
const modal = useVModel(props, 'modelValue'); const modal = useVModel(props, "modelValue");
const loading = ref(false); const loading = ref(false);
const focused = ref(false); const focused = ref(false);
const form = reactive({ const form = reactive({
name: '', name: "",
description: '', description: "",
}); });
whenever( whenever(
@ -41,8 +41,8 @@
); );
function reset() { function reset() {
form.name = ''; form.name = "";
form.description = ''; form.description = "";
focused.value = false; focused.value = false;
modal.value = false; modal.value = false;
loading.value = false; loading.value = false;
@ -61,7 +61,7 @@
} }
if (data) { if (data) {
toast.success('Location created'); toast.success("Location created");
navigateTo(`/location/${data.id}`); navigateTo(`/location/${data.id}`);
} }

View file

@ -1,21 +1,21 @@
import { PublicApi } from '~~/lib/api/public'; import { PublicApi } from "~~/lib/api/public";
import { UserApi } from '~~/lib/api/user'; import { UserApi } from "~~/lib/api/user";
import { Requests } from '~~/lib/requests'; import { Requests } from "~~/lib/requests";
import { useAuthStore } from '~~/stores/auth'; import { useAuthStore } from "~~/stores/auth";
function logger(r: Response) { function logger(r: Response) {
console.log(`${r.status} ${r.url} ${r.statusText}`); console.log(`${r.status} ${r.url} ${r.statusText}`);
} }
export function usePublicApi(): PublicApi { export function usePublicApi(): PublicApi {
const requests = new Requests('', '', {}); const requests = new Requests("", "", {});
return new PublicApi(requests); return new PublicApi(requests);
} }
export function useUserApi(): UserApi { export function useUserApi(): UserApi {
const authStore = useAuthStore(); const authStore = useAuthStore();
const requests = new Requests('', () => authStore.token, {}); const requests = new Requests("", () => authStore.token, {});
requests.addResponseInterceptor(logger); requests.addResponseInterceptor(logger);
requests.addResponseInterceptor(r => { requests.addResponseInterceptor(r => {
if (r.status === 401) { if (r.status === 401) {

View file

@ -1,5 +1,5 @@
import { UseConfirmDialogReturn } from '@vueuse/core'; import { UseConfirmDialogReturn } from "@vueuse/core";
import { Ref } from 'vue'; import { Ref } from "vue";
type Store = UseConfirmDialogReturn<any, boolean, boolean> & { type Store = UseConfirmDialogReturn<any, boolean, boolean> & {
text: Ref<string>; text: Ref<string>;
@ -7,7 +7,7 @@ type Store = UseConfirmDialogReturn<any, boolean, boolean> & {
}; };
const store: Partial<Store> = { const store: Partial<Store> = {
text: ref('Are you sure you want to delete this item? '), text: ref("Are you sure you want to delete this item? "),
setup: false, setup: false,
}; };

View file

@ -2,11 +2,11 @@ function slugify(text: string) {
return text return text
.toString() .toString()
.toLowerCase() .toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with - .replace(/\s+/g, "-") // Replace spaces with -
.replace(/[^\w-]+/g, '') // Remove all non-word chars .replace(/[^\w-]+/g, "") // Remove all non-word chars
.replace(/--+/g, '-') // Replace multiple - with single - .replace(/--+/g, "-") // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text .replace(/^-+/, "") // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text .replace(/-+$/, ""); // Trim - from end of text
} }
function idGenerator(): string { function idGenerator(): string {

View file

@ -1,9 +1,9 @@
import { useId } from './use-ids'; import { useId } from "./use-ids";
interface Notification { interface Notification {
id: string; id: string;
message: string; message: string;
type: 'success' | 'error' | 'info'; type: "success" | "error" | "info";
} }
const notifications = ref<Notification[]>([]); const notifications = ref<Notification[]>([]);
@ -34,21 +34,21 @@ export function useNotifier() {
addNotification({ addNotification({
id: useId(), id: useId(),
message, message,
type: 'success', type: "success",
}); });
}, },
error: (message: string) => { error: (message: string) => {
addNotification({ addNotification({
id: useId(), id: useId(),
message, message,
type: 'error', type: "error",
}); });
}, },
info: (message: string) => { info: (message: string) => {
addNotification({ addNotification({
id: useId(), id: useId(),
message, message,
type: 'info', type: "info",
}); });
}, },
}; };

View file

@ -1,4 +1,4 @@
import { Ref } from 'vue'; import { Ref } from "vue";
export type LocationViewPreferences = { export type LocationViewPreferences = {
showDetails: boolean; showDetails: boolean;
@ -11,7 +11,7 @@ export type LocationViewPreferences = {
*/ */
export function useViewPreferences(): Ref<LocationViewPreferences> { export function useViewPreferences(): Ref<LocationViewPreferences> {
const results = useLocalStorage( const results = useLocalStorage(
'homebox/preferences/location', "homebox/preferences/location",
{ {
showDetails: true, showDetails: true,
showEmpty: true, showEmpty: true,

View file

@ -1,5 +1,5 @@
export function truncate(str: string, length: number) { export function truncate(str: string, length: number) {
return str.length > length ? str.substring(0, length) + '...' : str; return str.length > length ? str.substring(0, length) + "..." : str;
} }
export function capitalize(str: string) { export function capitalize(str: string) {

View file

@ -1,8 +1,8 @@
import { describe, test, expect } from 'vitest'; import { describe, test, expect } from "vitest";
import { client, userClient } from './test-utils'; import { client, userClient } from "./test-utils";
describe('[GET] /api/v1/status', () => { describe("[GET] /api/v1/status", () => {
test('server should respond', async () => { test("server should respond", async () => {
const api = client(); const api = client();
const { response, data } = await api.status(); const { response, data } = await api.status();
expect(response.status).toBe(200); expect(response.status).toBe(200);
@ -10,23 +10,23 @@ describe('[GET] /api/v1/status', () => {
}); });
}); });
describe('first time user workflow (register, login)', () => { describe("first time user workflow (register, login)", () => {
const api = client(); const api = client();
const userData = { const userData = {
groupName: 'test-group', groupName: "test-group",
user: { user: {
email: 'test-user@email.com', email: "test-user@email.com",
name: 'test-user', name: "test-user",
password: 'test-password', password: "test-password",
}, },
}; };
test('user should be able to register', async () => { test("user should be able to register", async () => {
const { response } = await api.register(userData); const { response } = await api.register(userData);
expect(response.status).toBe(204); expect(response.status).toBe(204);
}); });
test('user should be able to login', async () => { test("user should be able to login", async () => {
const { response, data } = await api.login(userData.user.email, userData.user.password); const { response, data } = await api.login(userData.user.email, userData.user.password);
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(data.token).toBeTruthy(); expect(data.token).toBeTruthy();

View file

@ -1,24 +1,24 @@
import { beforeAll, expect } from 'vitest'; import { beforeAll, expect } from "vitest";
import { Requests } from '../../requests'; import { Requests } from "../../requests";
import { overrideParts } from '../base/urls'; import { overrideParts } from "../base/urls";
import { PublicApi } from '../public'; import { PublicApi } from "../public";
import * as config from '../../../test/config'; import * as config from "../../../test/config";
import { UserApi } from '../user'; import { UserApi } from "../user";
export function client() { export function client() {
overrideParts(config.BASE_URL, '/api/v1'); overrideParts(config.BASE_URL, "/api/v1");
const requests = new Requests(''); const requests = new Requests("");
return new PublicApi(requests); return new PublicApi(requests);
} }
export function userClient(token: string) { export function userClient(token: string) {
overrideParts(config.BASE_URL, '/api/v1'); overrideParts(config.BASE_URL, "/api/v1");
const requests = new Requests('', token); const requests = new Requests("", token);
return new UserApi(requests); return new UserApi(requests);
} }
const cache = { const cache = {
token: '', token: "",
}; };
/* /*
@ -30,11 +30,11 @@ export async function sharedUserClient(): Promise<UserApi> {
return userClient(cache.token); return userClient(cache.token);
} }
const testUser = { const testUser = {
groupName: 'test-group', groupName: "test-group",
user: { user: {
email: '__test__@__test__.com', email: "__test__@__test__.com",
name: '__test__', name: "__test__",
password: '__test__', password: "__test__",
}, },
}; };

View file

@ -1,9 +1,9 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from "vitest";
import { Label } from '../../classes/labels'; import { Label } from "../../classes/labels";
import { UserApi } from '../../user'; import { UserApi } from "../../user";
import { sharedUserClient } from '../test-utils'; import { sharedUserClient } from "../test-utils";
describe('locations lifecycle (create, update, delete)', () => { describe("locations lifecycle (create, update, delete)", () => {
let increment = 0; let increment = 0;
/** /**
@ -14,7 +14,7 @@ describe('locations lifecycle (create, update, delete)', () => {
const { response, data } = await api.labels.create({ const { response, data } = await api.labels.create({
name: `__test__.label.name_${increment}`, name: `__test__.label.name_${increment}`,
description: `__test__.label.description_${increment}`, description: `__test__.label.description_${increment}`,
color: '', color: "",
}); });
expect(response.status).toBe(201); expect(response.status).toBe(201);
increment++; increment++;
@ -26,13 +26,13 @@ describe('locations lifecycle (create, update, delete)', () => {
return [data, cleanup]; return [data, cleanup];
} }
test('user should be able to create a label', async () => { test("user should be able to create a label", async () => {
const api = await sharedUserClient(); const api = await sharedUserClient();
const labelData = { const labelData = {
name: 'test-label', name: "test-label",
description: 'test-description', description: "test-description",
color: '', color: "",
}; };
const { response, data } = await api.labels.create(labelData); const { response, data } = await api.labels.create(labelData);
@ -53,14 +53,14 @@ describe('locations lifecycle (create, update, delete)', () => {
expect(deleteResponse.status).toBe(204); expect(deleteResponse.status).toBe(204);
}); });
test('user should be able to update a label', async () => { test("user should be able to update a label", async () => {
const api = await sharedUserClient(); const api = await sharedUserClient();
const [label, cleanup] = await useLabel(api); const [label, cleanup] = await useLabel(api);
const labelData = { const labelData = {
name: 'test-label', name: "test-label",
description: 'test-description', description: "test-description",
color: '', color: "",
}; };
const { response, data } = await api.labels.update(label.id, labelData); const { response, data } = await api.labels.update(label.id, labelData);
@ -78,7 +78,7 @@ describe('locations lifecycle (create, update, delete)', () => {
await cleanup(); await cleanup();
}); });
test('user should be able to delete a label', async () => { test("user should be able to delete a label", async () => {
const api = await sharedUserClient(); const api = await sharedUserClient();
const [label, _] = await useLabel(api); const [label, _] = await useLabel(api);

View file

@ -1,9 +1,9 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from "vitest";
import { Location } from '../../classes/locations'; import { Location } from "../../classes/locations";
import { UserApi } from '../../user'; import { UserApi } from "../../user";
import { sharedUserClient } from '../test-utils'; import { sharedUserClient } from "../test-utils";
describe('locations lifecycle (create, update, delete)', () => { describe("locations lifecycle (create, update, delete)", () => {
let increment = 0; let increment = 0;
/** /**
@ -26,12 +26,12 @@ describe('locations lifecycle (create, update, delete)', () => {
return [data, cleanup]; return [data, cleanup];
} }
test('user should be able to create a location', async () => { test("user should be able to create a location", async () => {
const api = await sharedUserClient(); const api = await sharedUserClient();
const locationData = { const locationData = {
name: 'test-location', name: "test-location",
description: 'test-description', description: "test-description",
}; };
const { response, data } = await api.locations.create(locationData); const { response, data } = await api.locations.create(locationData);
@ -52,13 +52,13 @@ describe('locations lifecycle (create, update, delete)', () => {
expect(deleteResponse.status).toBe(204); expect(deleteResponse.status).toBe(204);
}); });
test('user should be able to update a location', async () => { test("user should be able to update a location", async () => {
const api = await sharedUserClient(); const api = await sharedUserClient();
const [location, cleanup] = await useLocation(api); const [location, cleanup] = await useLocation(api);
const updateData = { const updateData = {
name: 'test-location-updated', name: "test-location-updated",
description: 'test-description-updated', description: "test-description-updated",
}; };
const { response } = await api.locations.update(location.id, updateData); const { response } = await api.locations.update(location.id, updateData);
@ -75,7 +75,7 @@ describe('locations lifecycle (create, update, delete)', () => {
await cleanup(); await cleanup();
}); });
test('user should be able to delete a location', async () => { test("user should be able to delete a location", async () => {
const api = await sharedUserClient(); const api = await sharedUserClient();
const [location, _] = await useLocation(api); const [location, _] = await useLocation(api);

View file

@ -1,4 +1,4 @@
import { Requests } from '../../requests'; import { Requests } from "../../requests";
// < // <
// TGetResult, // TGetResult,
// TPostData, // TPostData,

View file

@ -1,24 +1,24 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from "vitest";
import { route } from '.'; import { route } from ".";
describe('UrlBuilder', () => { describe("UrlBuilder", () => {
it('basic query parameter', () => { it("basic query parameter", () => {
const result = route('/test', { a: 'b' }); const result = route("/test", { a: "b" });
expect(result).toBe('/api/v1/test?a=b'); expect(result).toBe("/api/v1/test?a=b");
}); });
it('multiple query parameters', () => { it("multiple query parameters", () => {
const result = route('/test', { a: 'b', c: 'd' }); const result = route("/test", { a: "b", c: "d" });
expect(result).toBe('/api/v1/test?a=b&c=d'); expect(result).toBe("/api/v1/test?a=b&c=d");
}); });
it('no query parameters', () => { it("no query parameters", () => {
const result = route('/test'); const result = route("/test");
expect(result).toBe('/api/v1/test'); expect(result).toBe("/api/v1/test");
}); });
it('list-like query parameters', () => { it("list-like query parameters", () => {
const result = route('/test', { a: ['b', 'c'] }); const result = route("/test", { a: ["b", "c"] });
expect(result).toBe('/api/v1/test?a=b&a=c'); expect(result).toBe("/api/v1/test?a=b&a=c");
}); });
}); });

View file

@ -1,2 +1,2 @@
export { BaseAPI } from './base-api'; export { BaseAPI } from "./base-api";
export { route } from './urls'; export { route } from "./urls";

View file

@ -1,6 +1,6 @@
const parts = { const parts = {
host: 'http://localhost.com', host: "http://localhost.com",
prefix: '/api/v1', prefix: "/api/v1",
}; };
export function overrideParts(host: string, prefix: string) { export function overrideParts(host: string, prefix: string) {
@ -32,5 +32,5 @@ export function route(rest: string, params: Record<string, QueryValue> = {}): st
} }
} }
return url.toString().replace('http://localhost.com', ''); return url.toString().replace("http://localhost.com", "");
} }

View file

@ -1,7 +1,7 @@
import { BaseAPI, route } from '../base'; import { BaseAPI, route } from "../base";
import { Label } from './labels'; import { Label } from "./labels";
import { Location } from './locations'; import { Location } from "./locations";
import { Results } from './types'; import { Results } from "./types";
export interface ItemCreate { export interface ItemCreate {
name: string; name: string;
@ -36,11 +36,11 @@ export interface Item {
export class ItemsApi extends BaseAPI { export class ItemsApi extends BaseAPI {
getAll() { getAll() {
return this.http.get<Results<Item>>({ url: route('/items') }); return this.http.get<Results<Item>>({ url: route("/items") });
} }
create(item: ItemCreate) { create(item: ItemCreate) {
return this.http.post<ItemCreate, Item>({ url: route('/items'), body: item }); return this.http.post<ItemCreate, Item>({ url: route("/items"), body: item });
} }
async get(id: string) { async get(id: string) {
@ -68,8 +68,8 @@ export class ItemsApi extends BaseAPI {
import(file: File) { import(file: File) {
const formData = new FormData(); const formData = new FormData();
formData.append('csv', file); formData.append("csv", file);
return this.http.post<FormData, void>({ url: route('/items/import'), data: formData }); return this.http.post<FormData, void>({ url: route("/items/import"), data: formData });
} }
} }

View file

@ -1,6 +1,6 @@
import { BaseAPI, route } from '../base'; import { BaseAPI, route } from "../base";
import { Item } from './items'; import { Item } from "./items";
import { Details, OutType, Results } from './types'; import { Details, OutType, Results } from "./types";
export type LabelCreate = Details & { export type LabelCreate = Details & {
color: string; color: string;
@ -16,11 +16,11 @@ export type Label = LabelCreate &
export class LabelsApi extends BaseAPI { export class LabelsApi extends BaseAPI {
getAll() { getAll() {
return this.http.get<Results<Label>>({ url: route('/labels') }); return this.http.get<Results<Label>>({ url: route("/labels") });
} }
create(body: LabelCreate) { create(body: LabelCreate) {
return this.http.post<LabelCreate, Label>({ url: route('/labels'), body }); return this.http.post<LabelCreate, Label>({ url: route("/labels"), body });
} }
get(id: string) { get(id: string) {

View file

@ -1,6 +1,6 @@
import { BaseAPI, route } from '../base'; import { BaseAPI, route } from "../base";
import { Item } from './items'; import { Item } from "./items";
import { Details, OutType, Results } from './types'; import { Details, OutType, Results } from "./types";
export type LocationCreate = Details; export type LocationCreate = Details;
@ -15,11 +15,11 @@ export type LocationUpdate = LocationCreate;
export class LocationsApi extends BaseAPI { export class LocationsApi extends BaseAPI {
getAll() { getAll() {
return this.http.get<Results<Location>>({ url: route('/locations') }); return this.http.get<Results<Location>>({ url: route("/locations") });
} }
create(body: LocationCreate) { create(body: LocationCreate) {
return this.http.post<LocationCreate, Location>({ url: route('/locations'), body }); return this.http.post<LocationCreate, Location>({ url: route("/locations"), body });
} }
get(id: string) { get(id: string) {

View file

@ -1,4 +1,4 @@
import { BaseAPI, route } from './base'; import { BaseAPI, route } from "./base";
export type LoginResult = { export type LoginResult = {
token: string; token: string;
@ -28,12 +28,12 @@ export type StatusResult = {
export class PublicApi extends BaseAPI { export class PublicApi extends BaseAPI {
public status() { public status() {
return this.http.get<StatusResult>({ url: route('/status') }); return this.http.get<StatusResult>({ url: route("/status") });
} }
public login(username: string, password: string) { public login(username: string, password: string) {
return this.http.post<LoginPayload, LoginResult>({ return this.http.post<LoginPayload, LoginResult>({
url: route('/users/login'), url: route("/users/login"),
body: { body: {
username, username,
password, password,
@ -42,6 +42,6 @@ export class PublicApi extends BaseAPI {
} }
public register(body: RegisterPayload) { public register(body: RegisterPayload) {
return this.http.post<RegisterPayload, LoginResult>({ url: route('/users/register'), body }); return this.http.post<RegisterPayload, LoginResult>({ url: route("/users/register"), body });
} }
} }

View file

@ -1,8 +1,8 @@
import { BaseAPI, route } from './base'; import { BaseAPI, route } from "./base";
import { ItemsApi } from './classes/items'; import { ItemsApi } from "./classes/items";
import { LabelsApi } from './classes/labels'; import { LabelsApi } from "./classes/labels";
import { LocationsApi } from './classes/locations'; import { LocationsApi } from "./classes/locations";
import { Requests } from '~~/lib/requests'; import { Requests } from "~~/lib/requests";
export type Result<T> = { export type Result<T> = {
item: T; item: T;
@ -30,14 +30,14 @@ export class UserApi extends BaseAPI {
} }
public self() { public self() {
return this.http.get<Result<User>>({ url: route('/users/self') }); return this.http.get<Result<User>>({ url: route("/users/self") });
} }
public logout() { public logout() {
return this.http.post<object, void>({ url: route('/users/logout') }); return this.http.post<object, void>({ url: route("/users/logout") });
} }
public deleteAccount() { public deleteAccount() {
return this.http.delete<void>({ url: route('/users/self') }); return this.http.delete<void>({ url: route("/users/self") });
} }
} }

View file

@ -1 +1 @@
export { Requests, type TResponse } from './requests'; export { Requests, type TResponse } from "./requests";

View file

@ -1,8 +1,8 @@
export enum Method { export enum Method {
GET = 'GET', GET = "GET",
POST = 'POST', POST = "POST",
PUT = 'PUT', PUT = "PUT",
DELETE = 'DELETE', DELETE = "DELETE",
} }
export type RequestInterceptor = (r: Response) => void; export type RequestInterceptor = (r: Response) => void;
@ -40,9 +40,9 @@ export class Requests {
return this.baseUrl + rest; return this.baseUrl + rest;
} }
constructor(baseUrl: string, token: string | (() => string) = '', headers: Record<string, string> = {}) { constructor(baseUrl: string, token: string | (() => string) = "", headers: Record<string, string> = {}) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.token = typeof token === 'string' ? () => token : token; this.token = typeof token === "string" ? () => token : token;
this.headers = headers; this.headers = headers;
} }
@ -72,19 +72,19 @@ export class Requests {
headers: { headers: {
...rargs.headers, ...rargs.headers,
...this.headers, ...this.headers,
}, } as Record<string, string>,
}; };
const token = this.token(); const token = this.token();
if (token !== '' && payload.headers !== undefined) { if (token !== "" && payload.headers !== undefined) {
payload.headers.Authorization = token; payload.headers["Authorization"] = token; // eslint-disable-line dot-notation
} }
if (this.methodSupportsBody(method)) { if (this.methodSupportsBody(method)) {
if (rargs.data) { if (rargs.data) {
payload.body = rargs.data; payload.body = rargs.data;
} else { } else {
payload.headers['Content-Type'] = 'application/json'; payload.headers["Content-Type"] = "application/json";
payload.body = JSON.stringify(rargs.body); payload.body = JSON.stringify(rargs.body);
} }
} }

View file

@ -1,18 +1,18 @@
import { defineNuxtConfig } from 'nuxt'; import { defineNuxtConfig } from "nuxt";
// https://v3.nuxtjs.org/api/configuration/nuxt.config // https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({ export default defineNuxtConfig({
target: 'static', target: "static",
ssr: false, ssr: false,
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt', '@vueuse/nuxt'], modules: ["@nuxtjs/tailwindcss", "@pinia/nuxt", "@vueuse/nuxt"],
meta: { meta: {
title: 'Homebox', title: "Homebox",
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.svg' }], link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.svg" }],
}, },
vite: { vite: {
server: { server: {
proxy: { proxy: {
'/api': 'http://localhost:7745', "/api": "http://localhost:7745",
}, },
}, },
plugins: [], plugins: [],

View file

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
useHead({ useHead({
title: '404. Not Found', title: "404. Not Found",
}); });
definePageMeta({ definePageMeta({
layout: '404', layout: "404",
}); });
</script> </script>

View file

@ -1,24 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
layout: 'home', layout: "home",
}); });
useHead({ useHead({
title: 'Homebox | Home', title: "Homebox | Home",
}); });
const api = useUserApi(); const api = useUserApi();
const { data: locations } = useAsyncData('locations', async () => { const { data: locations } = useAsyncData("locations", async () => {
const { data } = await api.locations.getAll(); const { data } = await api.locations.getAll();
return data.items; return data.items;
}); });
const { data: labels } = useAsyncData('labels', async () => { const { data: labels } = useAsyncData("labels", async () => {
const { data } = await api.labels.getAll(); const { data } = await api.labels.getAll();
return data.items; return data.items;
}); });
const { data: items } = useAsyncData('items', async () => { const { data: items } = useAsyncData("items", async () => {
const { data } = await api.items.getAll(); const { data } = await api.items.getAll();
return data.items; return data.items;
}); });
@ -29,15 +29,15 @@
const stats = [ const stats = [
{ {
label: 'Locations', label: "Locations",
value: totalLocations, value: totalLocations,
}, },
{ {
label: 'Items', label: "Items",
value: totalItems, value: totalItems,
}, },
{ {
label: 'Labels', label: "Labels",
value: totalLabels, value: totalLabels,
}, },
]; ];
@ -55,7 +55,7 @@
function setFile(e: Event & { target: HTMLInputElement }) { function setFile(e: Event & { target: HTMLInputElement }) {
importCsv.value = e.target.files[0]; importCsv.value = e.target.files[0];
console.log('importCsv.value', importCsv.value); console.log("importCsv.value", importCsv.value);
} }
const toast = useNotifier(); const toast = useNotifier();
@ -74,7 +74,7 @@
const { error } = await api.items.import(importCsv.value); const { error } = await api.items.import(importCsv.value);
if (error) { if (error) {
toast.error('Import failed. Please try again later.'); toast.error("Import failed. Please try again later.");
} }
// Reset // Reset
@ -138,7 +138,7 @@
> >
<div v-for="stat in stats" :key="stat.label" class="px-6 py-5 text-center text-sm font-medium"> <div v-for="stat in stats" :key="stat.label" class="px-6 py-5 text-center text-sm font-medium">
<span class="text-gray-900">{{ stat.value.value }}</span> <span class="text-gray-900">{{ stat.value.value }}</span>
{{ ' ' }} {{ " " }}
<span class="text-gray-600">{{ stat.label }}</span> <span class="text-gray-600">{{ stat.label }}</span>
</div> </div>
</div> </div>

View file

@ -1,43 +1,43 @@
<script setup lang="ts"> <script setup lang="ts">
import TextField from '@/components/Form/TextField.vue'; import TextField from "@/components/Form/TextField.vue";
import { useNotifier } from '@/composables/use-notifier'; import { useNotifier } from "@/composables/use-notifier";
import { usePublicApi } from '@/composables/use-api'; import { usePublicApi } from "@/composables/use-api";
import { useAuthStore } from '~~/stores/auth'; import { useAuthStore } from "~~/stores/auth";
useHead({ useHead({
title: 'Homebox | Organize and Tag Your Stuff', title: "Homebox | Organize and Tag Your Stuff",
}); });
definePageMeta({ definePageMeta({
layout: 'empty', layout: "empty",
}); });
const authStore = useAuthStore(); const authStore = useAuthStore();
if (!authStore.isTokenExpired) { if (!authStore.isTokenExpired) {
navigateTo('/home'); navigateTo("/home");
} }
const registerFields = [ const registerFields = [
{ {
label: "What's your name?", label: "What's your name?",
value: '', value: "",
}, },
{ {
label: "What's your email?", label: "What's your email?",
value: '', value: "",
}, },
{ {
label: 'Name your group', label: "Name your group",
value: '', value: "",
}, },
{ {
label: 'Set your password', label: "Set your password",
value: '', value: "",
type: 'password', type: "password",
}, },
{ {
label: 'Confirm your password', label: "Confirm your password",
value: '', value: "",
type: 'password', type: "password",
}, },
]; ];
@ -57,11 +57,11 @@
}); });
if (error) { if (error) {
toast.error('Problem registering user'); toast.error("Problem registering user");
return; return;
} }
toast.success('User registered'); toast.success("User registered");
loading.value = false; loading.value = false;
loginFields[0].value = registerFields[1].value; loginFields[0].value = registerFields[1].value;
@ -70,13 +70,13 @@
const loginFields = [ const loginFields = [
{ {
label: 'Email', label: "Email",
value: '', value: "",
}, },
{ {
label: 'Password', label: "Password",
value: '', value: "",
type: 'password', type: "password",
}, },
]; ];
@ -88,16 +88,16 @@
const { data, error } = await api.login(loginFields[0].value, loginFields[1].value); const { data, error } = await api.login(loginFields[0].value, loginFields[1].value);
if (error) { if (error) {
toast.error('Invalid email or password'); toast.error("Invalid email or password");
} else { } else {
toast.success('Logged in successfully'); toast.success("Logged in successfully");
authStore.$patch({ authStore.$patch({
token: data.token, token: data.token,
expires: data.expiresAt, expires: data.expiresAt,
}); });
navigateTo('/home'); navigateTo("/home");
} }
loading.value = false; loading.value = false;
} }
@ -207,7 +207,7 @@
class="text-base-content text-lg hover:bg-primary hover:text-primary-content px-3 py-1 rounded-xl transition-colors duration-200" class="text-base-content text-lg hover:bg-primary hover:text-primary-content px-3 py-1 rounded-xl transition-colors duration-200"
@click="toggleLogin" @click="toggleLogin"
> >
{{ registerForm ? 'Already a User? Login' : 'Not a User? Register' }} {{ registerForm ? "Already a User? Login" : "Not a User? Register" }}
</button> </button>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
layout: 'home', layout: "home",
}); });
const route = useRoute(); const route = useRoute();
@ -12,85 +12,85 @@
const { data: item } = useAsyncData(async () => { const { data: item } = useAsyncData(async () => {
const { data, error } = await api.items.get(itemId.value); const { data, error } = await api.items.get(itemId.value);
if (error) { if (error) {
toast.error('Failed to load item'); toast.error("Failed to load item");
navigateTo('/home'); navigateTo("/home");
return; return;
} }
return data; return data;
}); });
type FormField = { type FormField = {
type: 'text' | 'textarea' | 'select' | 'date'; type: "text" | "textarea" | "select" | "date";
label: string; label: string;
ref: string; ref: string;
}; };
const mainFields: FormField[] = [ const mainFields: FormField[] = [
{ {
type: 'text', type: "text",
label: 'Name', label: "Name",
ref: 'name', ref: "name",
}, },
{ {
type: 'textarea', type: "textarea",
label: 'Description', label: "Description",
ref: 'description', ref: "description",
}, },
{ {
type: 'text', type: "text",
label: 'Serial Number', label: "Serial Number",
ref: 'serialNumber', ref: "serialNumber",
}, },
{ {
type: 'text', type: "text",
label: 'Model Number', label: "Model Number",
ref: 'modelNumber', ref: "modelNumber",
}, },
{ {
type: 'text', type: "text",
label: 'Manufacturer', label: "Manufacturer",
ref: 'manufacturer', ref: "manufacturer",
}, },
{ {
type: 'textarea', type: "textarea",
label: 'Notes', label: "Notes",
ref: 'notes', ref: "notes",
}, },
]; ];
const purchaseFields: FormField[] = [ const purchaseFields: FormField[] = [
{ {
type: 'text', type: "text",
label: 'Purchased From', label: "Purchased From",
ref: 'purchaseFrom', ref: "purchaseFrom",
}, },
{ {
type: 'text', type: "text",
label: 'Purchased Price', label: "Purchased Price",
ref: 'purchasePrice', ref: "purchasePrice",
}, },
{ {
type: 'date', type: "date",
label: 'Purchased At', label: "Purchased At",
ref: 'purchaseTime', ref: "purchaseTime",
}, },
]; ];
const soldFields = [ const soldFields = [
{ {
type: 'text', type: "text",
label: 'Sold To', label: "Sold To",
ref: 'soldTo', ref: "soldTo",
}, },
{ {
type: 'text', type: "text",
label: 'Sold Price', label: "Sold Price",
ref: 'soldPrice', ref: "soldPrice",
}, },
{ {
type: 'date', type: "date",
label: 'Sold At', label: "Sold At",
ref: 'soldTime', ref: "soldTime",
}, },
]; ];
</script> </script>

View file

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ definePageMeta({
layout: 'home', layout: "home",
}); });
const route = useRoute(); const route = useRoute();
@ -13,8 +13,8 @@
const { data: item } = useAsyncData(async () => { const { data: item } = useAsyncData(async () => {
const { data, error } = await api.items.get(itemId.value); const { data, error } = await api.items.get(itemId.value);
if (error) { if (error) {
toast.error('Failed to load item'); toast.error("Failed to load item");
navigateTo('/home'); navigateTo("/home");
return; return;
} }
return data; return data;
@ -22,12 +22,12 @@
const itemSummary = computed(() => { const itemSummary = computed(() => {
return { return {
Description: item.value?.description || '', Description: item.value?.description || "",
'Serial Number': item.value?.serialNumber || '', "Serial Number": item.value?.serialNumber || "",
'Model Number': item.value?.modelNumber || '', "Model Number": item.value?.modelNumber || "",
Manufacturer: item.value?.manufacturer || '', Manufacturer: item.value?.manufacturer || "",
Notes: item.value?.notes || '', Notes: item.value?.notes || "",
Attachments: '', // TODO: Attachments Attachments: "", // TODO: Attachments
}; };
}); });
@ -42,12 +42,12 @@
const payload = {}; const payload = {};
if (item.value.lifetimeWarranty) { if (item.value.lifetimeWarranty) {
payload['Lifetime Warranty'] = 'Yes'; payload["Lifetime Warranty"] = "Yes";
} else { } else {
payload['Warranty Expires'] = item.value?.warrantyExpires || ''; payload["Warranty Expires"] = item.value?.warrantyExpires || "";
} }
payload['Warranty Details'] = item.value?.warrantyDetails || ''; payload["Warranty Details"] = item.value?.warrantyDetails || "";
return payload; return payload;
}); });
@ -61,9 +61,9 @@
const purchaseDetails = computed(() => { const purchaseDetails = computed(() => {
return { return {
'Purchased From': item.value?.purchaseFrom || '', "Purchased From": item.value?.purchaseFrom || "",
'Purchased Price': item.value?.purchasePrice || '', "Purchased Price": item.value?.purchasePrice || "",
'Purchased At': item.value?.purchaseTime || '', "Purchased At": item.value?.purchaseTime || "",
}; };
}); });
@ -77,16 +77,16 @@
const soldDetails = computed(() => { const soldDetails = computed(() => {
return { return {
'Sold To': item.value?.soldTo || '', "Sold To": item.value?.soldTo || "",
'Sold Price': item.value?.soldPrice || '', "Sold Price": item.value?.soldPrice || "",
'Sold At': item.value?.soldTime || '', "Sold At": item.value?.soldTime || "",
}; };
}); });
const confirm = useConfirm(); const confirm = useConfirm();
async function deleteItem() { async function deleteItem() {
const confirmed = await confirm.reveal('Are you sure you want to delete this item?'); const confirmed = await confirm.reveal("Are you sure you want to delete this item?");
if (!confirmed.data) { if (!confirmed.data) {
return; return;
@ -94,11 +94,11 @@
const { error } = await api.items.delete(itemId.value); const { error } = await api.items.delete(itemId.value);
if (error) { if (error) {
toast.error('Failed to delete item'); toast.error("Failed to delete item");
return; return;
} }
toast.success('Item deleted'); toast.success("Item deleted");
navigateTo('/home'); navigateTo("/home");
} }
</script> </script>

View file

@ -1,6 +1,6 @@
<script setup> <script setup>
definePageMeta({ definePageMeta({
layout: 'home', layout: "home",
}); });
const show = reactive({ const show = reactive({
@ -11,29 +11,29 @@
}); });
const form = reactive({ const form = reactive({
name: '', name: "",
description: '', description: "",
notes: '', notes: "",
// Item Identification // Item Identification
serialNumber: '', serialNumber: "",
modelNumber: '', modelNumber: "",
manufacturer: '', manufacturer: "",
// Purchase Information // Purchase Information
purchaseTime: '', purchaseTime: "",
purchasePrice: '', purchasePrice: "",
purchaseFrom: '', purchaseFrom: "",
// Sold Information // Sold Information
soldTime: '', soldTime: "",
soldPrice: '', soldPrice: "",
soldTo: '', soldTo: "",
soldNotes: '', soldNotes: "",
}); });
function submit() { function submit() {
console.log('Submitted!'); console.log("Submitted!");
} }
</script> </script>

View file

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import ActionsDivider from '../../components/Base/ActionsDivider.vue'; import ActionsDivider from "../../components/Base/ActionsDivider.vue";
definePageMeta({ definePageMeta({
layout: 'home', layout: "home",
}); });
const route = useRoute(); const route = useRoute();
@ -16,8 +16,8 @@
const { data: label } = useAsyncData(labelId.value, async () => { const { data: label } = useAsyncData(labelId.value, async () => {
const { data, error } = await api.labels.get(labelId.value); const { data, error } = await api.labels.get(labelId.value);
if (error) { if (error) {
toast.error('Failed to load label'); toast.error("Failed to load label");
navigateTo('/home'); navigateTo("/home");
return; return;
} }
return data; return data;
@ -25,25 +25,25 @@
function maybeTimeAgo(date?: string): string { function maybeTimeAgo(date?: string): string {
if (!date) { if (!date) {
return '??'; return "??";
} }
const time = new Date(date); const time = new Date(date);
return `${useTimeAgo(time).value} (${useDateFormat(time, 'MM-DD-YYYY').value})`; return `${useTimeAgo(time).value} (${useDateFormat(time, "MM-DD-YYYY").value})`;
} }
const details = computed(() => { const details = computed(() => {
const dt = { const dt = {
Name: label.value?.name || '', Name: label.value?.name || "",
Description: label.value?.description || '', Description: label.value?.description || "",
}; };
if (preferences.value.showDetails) { if (preferences.value.showDetails) {
dt['Created At'] = maybeTimeAgo(label.value?.createdAt); dt["Created At"] = maybeTimeAgo(label.value?.createdAt);
dt['Updated At'] = maybeTimeAgo(label.value?.updatedAt); dt["Updated At"] = maybeTimeAgo(label.value?.updatedAt);
dt['Database ID'] = label.value?.id || ''; dt["Database ID"] = label.value?.id || "";
dt['Group Id'] = label.value?.groupId || ''; dt["Group Id"] = label.value?.groupId || "";
} }
return dt; return dt;
@ -52,7 +52,7 @@
const { reveal } = useConfirm(); const { reveal } = useConfirm();
async function confirmDelete() { async function confirmDelete() {
const { isCanceled } = await reveal('Are you sure you want to delete this label? This action cannot be undone.'); const { isCanceled } = await reveal("Are you sure you want to delete this label? This action cannot be undone.");
if (isCanceled) { if (isCanceled) {
return; return;
@ -61,24 +61,24 @@
const { error } = await api.labels.delete(labelId.value); const { error } = await api.labels.delete(labelId.value);
if (error) { if (error) {
toast.error('Failed to delete label'); toast.error("Failed to delete label");
return; return;
} }
toast.success('Label deleted'); toast.success("Label deleted");
navigateTo('/home'); navigateTo("/home");
} }
const updateModal = ref(false); const updateModal = ref(false);
const updating = ref(false); const updating = ref(false);
const updateData = reactive({ const updateData = reactive({
name: '', name: "",
description: '', description: "",
color: '', color: "",
}); });
function openUpdate() { function openUpdate() {
updateData.name = label.value?.name || ''; updateData.name = label.value?.name || "";
updateData.description = label.value?.description || ''; updateData.description = label.value?.description || "";
updateModal.value = true; updateModal.value = true;
} }
@ -87,11 +87,11 @@
const { error, data } = await api.labels.update(labelId.value, updateData); const { error, data } = await api.labels.update(labelId.value, updateData);
if (error) { if (error) {
toast.error('Failed to update label'); toast.error("Failed to update label");
return; return;
} }
toast.success('Label updated'); toast.success("Label updated");
label.value = data; label.value = data;
updateModal.value = false; updateModal.value = false;
updating.value = false; updating.value = false;
@ -112,7 +112,7 @@
</BaseModal> </BaseModal>
<section> <section>
<BaseSectionHeader class="mb-5" dark> <BaseSectionHeader class="mb-5" dark>
{{ label ? label.name : '' }} {{ label ? label.name : "" }}
</BaseSectionHeader> </BaseSectionHeader>
<BaseDetails class="mb-2" :details="details"> <BaseDetails class="mb-2" :details="details">
<template #title> Label Details </template> <template #title> Label Details </template>

View file

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import ActionsDivider from '../../components/Base/ActionsDivider.vue'; import ActionsDivider from "../../components/Base/ActionsDivider.vue";
definePageMeta({ definePageMeta({
layout: 'home', layout: "home",
}); });
const route = useRoute(); const route = useRoute();
@ -16,8 +16,8 @@
const { data: location } = useAsyncData(locationId.value, async () => { const { data: location } = useAsyncData(locationId.value, async () => {
const { data, error } = await api.locations.get(locationId.value); const { data, error } = await api.locations.get(locationId.value);
if (error) { if (error) {
toast.error('Failed to load location'); toast.error("Failed to load location");
navigateTo('/home'); navigateTo("/home");
return; return;
} }
return data; return data;
@ -25,25 +25,25 @@
function maybeTimeAgo(date?: string): string { function maybeTimeAgo(date?: string): string {
if (!date) { if (!date) {
return '??'; return "??";
} }
const time = new Date(date); const time = new Date(date);
return `${useTimeAgo(time).value} (${useDateFormat(time, 'MM-DD-YYYY').value})`; return `${useTimeAgo(time).value} (${useDateFormat(time, "MM-DD-YYYY").value})`;
} }
const details = computed(() => { const details = computed(() => {
const dt = { const dt = {
Name: location.value?.name || '', Name: location.value?.name || "",
Description: location.value?.description || '', Description: location.value?.description || "",
}; };
if (preferences.value.showDetails) { if (preferences.value.showDetails) {
dt['Created At'] = maybeTimeAgo(location.value?.createdAt); dt["Created At"] = maybeTimeAgo(location.value?.createdAt);
dt['Updated At'] = maybeTimeAgo(location.value?.updatedAt); dt["Updated At"] = maybeTimeAgo(location.value?.updatedAt);
dt['Database ID'] = location.value?.id || ''; dt["Database ID"] = location.value?.id || "";
dt['Group Id'] = location.value?.groupId || ''; dt["Group Id"] = location.value?.groupId || "";
} }
return dt; return dt;
@ -52,7 +52,7 @@
const { reveal } = useConfirm(); const { reveal } = useConfirm();
async function confirmDelete() { async function confirmDelete() {
const { isCanceled } = await reveal('Are you sure you want to delete this location? This action cannot be undone.'); const { isCanceled } = await reveal("Are you sure you want to delete this location? This action cannot be undone.");
if (isCanceled) { if (isCanceled) {
return; return;
@ -61,23 +61,23 @@
const { error } = await api.locations.delete(locationId.value); const { error } = await api.locations.delete(locationId.value);
if (error) { if (error) {
toast.error('Failed to delete location'); toast.error("Failed to delete location");
return; return;
} }
toast.success('Location deleted'); toast.success("Location deleted");
navigateTo('/home'); navigateTo("/home");
} }
const updateModal = ref(false); const updateModal = ref(false);
const updating = ref(false); const updating = ref(false);
const updateData = reactive({ const updateData = reactive({
name: '', name: "",
description: '', description: "",
}); });
function openUpdate() { function openUpdate() {
updateData.name = location.value?.name || ''; updateData.name = location.value?.name || "";
updateData.description = location.value?.description || ''; updateData.description = location.value?.description || "";
updateModal.value = true; updateModal.value = true;
} }
@ -86,11 +86,11 @@
const { error, data } = await api.locations.update(locationId.value, updateData); const { error, data } = await api.locations.update(locationId.value, updateData);
if (error) { if (error) {
toast.error('Failed to update location'); toast.error("Failed to update location");
return; return;
} }
toast.success('Location updated'); toast.success("Location updated");
location.value = data; location.value = data;
updateModal.value = false; updateModal.value = false;
updating.value = false; updating.value = false;
@ -111,7 +111,7 @@
</BaseModal> </BaseModal>
<section> <section>
<BaseSectionHeader class="mb-5" dark> <BaseSectionHeader class="mb-5" dark>
{{ location ? location.name : '' }} {{ location ? location.name : "" }}
</BaseSectionHeader> </BaseSectionHeader>
<BaseDetails class="mb-2" :details="details"> <BaseDetails class="mb-2" :details="details">
<template #title> Location Details </template> <template #title> Location Details </template>

View file

@ -1,11 +1,11 @@
import { defineStore } from 'pinia'; import { defineStore } from "pinia";
import { useLocalStorage } from '@vueuse/core'; import { useLocalStorage } from "@vueuse/core";
import { UserApi } from '~~/lib/api/user'; import { UserApi } from "~~/lib/api/user";
export const useAuthStore = defineStore('auth', { export const useAuthStore = defineStore("auth", {
state: () => ({ state: () => ({
token: useLocalStorage('pinia/auth/token', ''), token: useLocalStorage("pinia/auth/token", ""),
expires: useLocalStorage('pinia/auth/expires', ''), expires: useLocalStorage("pinia/auth/expires", ""),
}), }),
getters: { getters: {
isTokenExpired: state => { isTokenExpired: state => {
@ -13,7 +13,7 @@ export const useAuthStore = defineStore('auth', {
return true; return true;
} }
if (typeof state.expires === 'string') { if (typeof state.expires === "string") {
return new Date(state.expires) < new Date(); return new Date(state.expires) < new Date();
} }
@ -28,8 +28,8 @@ export const useAuthStore = defineStore('auth', {
return result; return result;
} }
this.token = ''; this.token = "";
this.expires = ''; this.expires = "";
return result; return result;
}, },
@ -38,9 +38,9 @@ export const useAuthStore = defineStore('auth', {
* must clear it's local session, usually when a 401 is received. * must clear it's local session, usually when a 401 is received.
*/ */
clearSession() { clearSession() {
this.token = ''; this.token = "";
this.expires = ''; this.expires = "";
navigateTo('/'); navigateTo("/");
}, },
}, },
}); });

View file

@ -1,11 +1,11 @@
module.exports = { module.exports = {
content: ['./app.vue', './{components,pages,layouts}/**/*.{vue,js,ts,jsx,tsx}'], content: ["./app.vue", "./{components,pages,layouts}/**/*.{vue,js,ts,jsx,tsx}"],
darkMode: 'class', // or 'media' or 'class' darkMode: "class", // or 'media' or 'class'
theme: { theme: {
extend: {}, extend: {},
}, },
variants: { variants: {
extend: {}, extend: {},
}, },
plugins: [require('@tailwindcss/aspect-ratio'), require('@tailwindcss/typography'), require('daisyui')], plugins: [require("@tailwindcss/aspect-ratio"), require("@tailwindcss/typography"), require("daisyui")],
}; };

View file

@ -1,3 +1,3 @@
export const PORT = '7745'; export const PORT = "7745";
export const HOST = 'http://127.0.0.1'; export const HOST = "http://127.0.0.1";
export const BASE_URL = HOST + ':' + PORT; export const BASE_URL = HOST + ":" + PORT;

View file

@ -1,8 +1,8 @@
import { exec } from 'child_process'; import { exec } from "child_process";
import * as config from './config'; import * as config from "./config";
export const setup = () => { export const setup = () => {
console.log('Starting Client Tests'); console.log("Starting Client Tests");
console.log({ console.log({
PORT: config.PORT, PORT: config.PORT,
HOST: config.HOST, HOST: config.HOST,
@ -12,8 +12,8 @@ export const setup = () => {
export const teardown = () => { export const teardown = () => {
if (process.env.TEST_SHUTDOWN_API_SERVER) { if (process.env.TEST_SHUTDOWN_API_SERVER) {
const pc = exec('pkill -SIGTERM api'); // Kill background API process const pc = exec("pkill -SIGTERM api"); // Kill background API process
pc.stdout.on('data', data => { pc.stdout.on("data", data => {
console.log(`stdout: ${data}`); console.log(`stdout: ${data}`);
}); });
} }

View file

@ -1,8 +1,8 @@
/// <reference types="vitest" /> /// <reference types="vitest" />
import { defineConfig } from 'vite'; import { defineConfig } from "vite";
export default defineConfig({ export default defineConfig({
test: { test: {
globalSetup: './test/setup.ts', globalSetup: "./test/setup.ts",
}, },
}); });