Refactor project structure and update dependencies

This commit is contained in:
2025-05-11 16:17:40 +02:00
parent 81d692a19b
commit 813f7b18d8
73 changed files with 10156 additions and 7756 deletions

View File

@@ -0,0 +1,109 @@
<script setup lang="ts">
import type { TableColumn } from '#ui/components/Table.vue'
import type { Device, IDevicesResponse } from '~/types/device'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
const UDropdownMenu = resolveComponent('UDropdownMenu')
const dayjs = useDayjs()
const auth = useAuthStore()
const loading = ref(false)
const devices = ref<Device[]>([])
async function fetchData() {
loading.value = true
const response = await $fetch<IDevicesResponse>('devices')
if (response.ok) {
devices.value = response.devices
}
loading.value = false
}
const columns: TableColumn<Device>[] = [
{
accessorKey: 'name',
header: 'Device',
cell: ({ row }) => {
return h('div', { class: 'font-semibold' }, [
row.original.name,
row.original.is_current && h(UBadge, {
label: 'active',
color: 'primary',
variant: 'soft',
size: 'sm',
class: 'ms-1',
}),
h('div', { class: 'font-medium text-sm' }, `IP: ${row.original.ip}`),
])
},
},
{
accessorKey: 'last_used_at',
header: 'Last used at',
cell: ({ row }) => {
return dayjs(row.original.last_used_at).fromNow()
},
},
{
accessorKey: 'actions',
header: 'Actions',
cell: ({ row }) => {
return h('div', { class: 'flex justify-end' },
h(UDropdownMenu, {
items: items(row.original),
}, () => h(UButton, {
icon: 'i-heroicons-ellipsis-vertical-20-solid',
variant: 'ghost',
color: 'neutral',
})),
)
},
},
]
const items = (row: Device) => [
[
{
label: 'Delete',
icon: 'i-heroicons-trash-20-solid',
onSelect: async () => {
await $fetch<never>('devices/disconnect', {
method: 'POST',
body: {
hash: row.hash,
},
async onResponse({ response }) {
if (response._data?.ok) {
await fetchData()
await auth.fetchUser()
}
},
})
},
},
],
]
if (import.meta.client) {
fetchData()
}
</script>
<template>
<ClientOnly>
<UTable
:data="devices"
:columns="columns"
size="lg"
:loading="loading"
/>
</ClientOnly>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,125 @@
<script lang="ts" setup>
import type { IAccountChangePasswordResponse, IVerificationNotificationResponse } from '~/types/account'
const form = ref()
const auth = useAuthStore()
const state = reactive({
current_password: '',
password: '',
password_confirmation: '',
})
const { refresh: onSubmit, status: accountPasswordStatus } = useFetch<IAccountChangePasswordResponse>('account/password', {
method: 'POST',
body: state,
immediate: false,
watch: false,
async onResponse({ response }) {
if (response?.status === 422) {
form.value.setErrors(response._data?.errors)
}
else if (response._data?.ok) {
useToast().add({
icon: 'i-heroicons-check-circle-20-solid',
title: 'The password was successfully updated.',
color: 'primary',
})
state.current_password = ''
state.password = ''
state.password_confirmation = ''
}
},
})
const { refresh: sendResetPasswordEmail, status: resetPasswordEmailStatus } = useFetch<IVerificationNotificationResponse>('verification-notification', {
method: 'POST',
body: { email: auth.user.email },
immediate: false,
watch: false,
onResponse({ response }) {
if (response._data?.ok) {
useToast().add({
icon: 'i-heroicons-check-circle-20-solid',
title: 'A link to reset your password has been sent to your email.',
color: 'primary',
})
}
},
})
</script>
<template>
<div>
<UForm
v-if="auth.user.has_password"
ref="form"
:state="state"
class="space-y-4"
@submit="onSubmit"
>
<UFormField
label="Current Password"
name="current_password"
required
>
<UInput
v-model="state.current_password"
type="password"
autocomplete="off"
/>
</UFormField>
<UFormField
label="New Password"
name="password"
hint="min 8 characters"
:ui="{ hint: 'text-xs text-gray-500 dark:text-gray-400' }"
required
>
<UInput
v-model="state.password"
type="password"
autocomplete="off"
/>
</UFormField>
<UFormField
label="Repeat Password"
name="password_confirmation"
required
>
<UInput
v-model="state.password_confirmation"
type="password"
autocomplete="off"
/>
</UFormField>
<div class="pt-2">
<UButton
type="submit"
label="Save"
:loading="accountPasswordStatus === 'pending'"
/>
</div>
</UForm>
<UAlert
v-else
icon="i-heroicons-information-circle-20-solid"
title="Send a link to your email to reset your password."
description="To create a password for your account, you must go through the password recovery process."
:actions="[
{
label: 'Send link to Email',
variant: 'solid',
color: 'neutral',
loading: resetPasswordEmailStatus === 'pending',
onClick: () => sendResetPasswordEmail(),
},
]"
/>
</div>
</template>

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import type { IAccountUpdateResponse, IVerificationNotificationResponse } from '~/types/account'
const form = ref()
const auth = useAuthStore()
const state = reactive({
...{
email: auth.user.email,
name: auth.user.name,
avatar: auth.user.avatar,
},
})
const { refresh: sendEmailVerification, status: resendEmailStatus } = useFetch<IVerificationNotificationResponse>('verification-notification', {
method: 'POST',
body: { email: state.email },
immediate: false,
watch: false,
onResponse({ response }) {
if (response._data?.ok) {
useToast().add({
icon: 'i-heroicons-check-circle-20-solid',
title: response._data.message,
color: 'primary',
})
}
},
})
const { refresh: onSubmit, status: accountUpdateStatus } = useFetch<IAccountUpdateResponse>('account/update', {
method: 'POST',
body: state,
immediate: false,
watch: false,
async onResponse({ response }) {
if (response?.status === 422) {
form.value.setErrors(response._data?.errors)
}
else if (response._data?.ok) {
useToast().add({
icon: 'i-heroicons-check-circle-20-solid',
title: 'Account details have been successfully updated.',
color: 'primary',
})
await auth.fetchUser()
state.name = auth.user.name
state.email = auth.user.email
state.avatar = auth.user.avatar
}
},
})
</script>
<template>
<UForm
ref="form"
:state="state"
class="space-y-4"
@submit="onSubmit"
>
<UFormField
label=""
name="avatar"
class="flex"
>
<InputUploadAvatar
v-model="state.avatar"
accept=".png, .jpg, .jpeg, .webp"
entity="avatars"
max-size="2"
/>
</UFormField>
<UFormField
label="Name"
name="name"
required
>
<UInput
v-model="state.name"
type="text"
/>
</UFormField>
<UFormField
label="Email"
name="email"
required
>
<UInput
v-model="state.email"
placeholder="you@example.com"
icon="i-heroicons-envelope"
trailing
type="email"
/>
</UFormField>
<UAlert
v-if="auth.user.must_verify_email"
variant="subtle"
color="warning"
icon="i-heroicons-information-circle-20-solid"
title="Please confirm your email address."
description="A confirmation email has been sent to your email address. Please click on the confirmation link in the email to verify your email address."
:actions="[
{
label: 'Resend verification email',
variant: 'solid',
color: 'neutral',
loading: resendEmailStatus === 'pending',
onClick: () => sendEmailVerification(),
},
]"
/>
<div class="pt-2">
<UButton
type="submit"
label="Save"
:loading="accountUpdateStatus === 'pending'"
/>
</div>
</UForm>
</template>