mirror of https://github.com/wg-easy/wg-easy
committed by
GitHub
15 changed files with 164 additions and 284 deletions
@ -1,17 +1,16 @@ |
|||||
<template> |
<template> |
||||
|
<ClientsQRCodeDialog :qr-code="`./api/client/${client.id}/qrcode.svg`"> |
||||
<button |
<button |
||||
class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white" |
class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white" |
||||
:title="$t('showQR')" |
:title="$t('showQR')" |
||||
@click="modalStore.qrcode = `./api/client/${client.id}/qrcode.svg`" |
|
||||
> |
> |
||||
<IconsQRCode class="w-5" /> |
<IconsQRCode class="w-5" /> |
||||
</button> |
</button> |
||||
|
</ClientsQRCodeDialog> |
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
defineProps<{ |
defineProps<{ |
||||
client: LocalClient; |
client: LocalClient; |
||||
}>(); |
}>(); |
||||
|
|
||||
const modalStore = useModalStore(); |
|
||||
</script> |
</script> |
||||
|
@ -1,132 +1,59 @@ |
|||||
<template> |
<template> |
||||
<!-- Create Dialog --> |
<BaseDialog :trigger-class="triggerClass"> |
||||
<div |
<template #trigger> |
||||
v-if="modalStore.clientCreate" |
<slot /> |
||||
class="fixed inset-0 z-10 overflow-y-auto" |
</template> |
||||
> |
<template #title> |
||||
<div |
|
||||
class="flex min-h-screen items-center justify-center px-4 pb-20 pt-4 text-center sm:block sm:p-0" |
|
||||
> |
|
||||
<!-- |
|
||||
Background overlay, show/hide based on modal state. |
|
||||
|
|
||||
Entering: "ease-out duration-300" |
|
||||
From: "opacity-0" |
|
||||
To: "opacity-100" |
|
||||
Leaving: "ease-in duration-200" |
|
||||
From: "opacity-100" |
|
||||
To: "opacity-0" |
|
||||
--> |
|
||||
<div class="fixed inset-0 transition-opacity" aria-hidden="true"> |
|
||||
<div |
|
||||
class="absolute inset-0 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50" |
|
||||
/> |
|
||||
</div> |
|
||||
|
|
||||
<!-- This element is to trick the browser into centering the modal contents. --> |
|
||||
<span |
|
||||
class="hidden sm:inline-block sm:h-screen sm:align-middle" |
|
||||
aria-hidden="true" |
|
||||
>​</span |
|
||||
> |
|
||||
<!-- |
|
||||
Modal panel, show/hide based on modal state. |
|
||||
|
|
||||
Entering: "ease-out duration-300" |
|
||||
From: "opacity-0 tranneutral-y-4 sm:tranneutral-y-0 sm:scale-95" |
|
||||
To: "opacity-100 tranneutral-y-0 sm:scale-100" |
|
||||
Leaving: "ease-in duration-200" |
|
||||
From: "opacity-100 tranneutral-y-0 sm:scale-100" |
|
||||
To: "opacity-0 tranneutral-y-4 sm:tranneutral-y-0 sm:scale-95" |
|
||||
--> |
|
||||
<div |
|
||||
class="inline-block w-full transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:max-w-lg sm:align-middle dark:bg-neutral-700" |
|
||||
role="dialog" |
|
||||
aria-modal="true" |
|
||||
aria-labelledby="modal-headline" |
|
||||
> |
|
||||
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4 dark:bg-neutral-700"> |
|
||||
<div class="sm:flex sm:items-start"> |
|
||||
<div |
|
||||
class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-800 sm:mx-0 sm:h-10 sm:w-10" |
|
||||
> |
|
||||
<IconsPlus class="h-6 w-6 text-white" /> |
|
||||
</div> |
|
||||
<div |
|
||||
class="mt-3 flex-grow text-center sm:ml-4 sm:mt-0 sm:text-left" |
|
||||
> |
|
||||
<h3 |
|
||||
id="modal-headline" |
|
||||
class="text-lg font-medium leading-6 text-gray-900 dark:text-neutral-200" |
|
||||
> |
|
||||
{{ $t('newClient') }} |
{{ $t('newClient') }} |
||||
</h3> |
</template> |
||||
<div class="mt-2"> |
<template #description> |
||||
<p class="text-sm text-gray-500"> |
<div class="flex flex-col"> |
||||
<input |
<FormTextField id="name" v-model="name" label="Name" /> |
||||
v-model.trim="modalStore.clientCreateName" |
<FormDateField id="expiresAt" v-model="expiresAt" label="Expire Date" /> |
||||
class="w-full rounded border-2 border-gray-100 p-2 outline-none focus:border-gray-200 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400 focus:dark:border-neutral-500" |
</div> |
||||
type="text" |
</template> |
||||
:placeholder="$t('name')" |
<template #actions> |
||||
/> |
<DialogClose as-child> |
||||
</p> |
<BaseButton>{{ $t('cancel') }}</BaseButton> |
||||
</div> |
</DialogClose> |
||||
<div class="mt-2"> |
<DialogClose as-child> |
||||
<p class="text-sm text-gray-500"> |
<BaseButton @click="createClient">{{ $t('create') }}</BaseButton> |
||||
<label |
</DialogClose> |
||||
class="mb-2 block text-sm font-bold text-gray-900 dark:text-neutral-200" |
</template> |
||||
for="expireDate" |
</BaseDialog> |
||||
> |
|
||||
{{ $t('ExpireDate') }} |
|
||||
</label> |
|
||||
<input |
|
||||
v-model.trim="modalStore.clientExpireDate" |
|
||||
class="w-full rounded border-2 border-gray-100 p-2 outline-none focus:border-gray-200 dark:border-neutral-600 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400 focus:dark:border-neutral-500" |
|
||||
type="date" |
|
||||
:placeholder="$t('ExpireDate')" |
|
||||
name="expireDate" |
|
||||
/> |
|
||||
</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div |
|
||||
class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 dark:bg-neutral-700" |
|
||||
> |
|
||||
<button |
|
||||
v-if="modalStore.clientCreateName.length" |
|
||||
type="button" |
|
||||
class="inline-flex w-full justify-center rounded-md border border-transparent bg-red-800 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm" |
|
||||
@click=" |
|
||||
modalStore.createClient(); |
|
||||
modalStore.clientCreate = null; |
|
||||
" |
|
||||
> |
|
||||
{{ $t('create') }} |
|
||||
</button> |
|
||||
<button |
|
||||
v-else |
|
||||
type="button" |
|
||||
class="inline-flex w-full cursor-not-allowed justify-center rounded-md border border-transparent bg-gray-200 px-4 py-2 text-base font-medium text-white shadow-sm sm:ml-3 sm:w-auto sm:text-sm dark:bg-neutral-400 dark:text-neutral-300" |
|
||||
> |
|
||||
{{ $t('create') }} |
|
||||
</button> |
|
||||
<button |
|
||||
type="button" |
|
||||
class="mt-3 inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none sm:ml-3 sm:mt-0 sm:w-auto sm:text-sm dark:border-neutral-500 dark:bg-neutral-500 dark:text-neutral-50 dark:hover:border-neutral-600 dark:hover:bg-neutral-600" |
|
||||
@click="modalStore.clientCreate = null" |
|
||||
> |
|
||||
{{ $t('cancel') }} |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script lang="ts" setup> |
||||
const modalStore = useModalStore(); |
import { FetchError } from 'ofetch'; |
||||
|
|
||||
|
const name = ref<string>(''); |
||||
|
const expiresAt = ref<string | null>(null); |
||||
|
const toast = useToast(); |
||||
|
const clientsStore = useClientsStore(); |
||||
|
|
||||
|
defineProps<{ triggerClass?: string }>(); |
||||
|
|
||||
// TODO: use radix |
async function createClient() { |
||||
|
try { |
||||
|
await $fetch('/api/client', { |
||||
|
method: 'post', |
||||
|
body: { name: name.value, expiresAt: expiresAt.value }, |
||||
|
}); |
||||
|
toast.showToast({ |
||||
|
type: 'success', |
||||
|
title: 'Success', |
||||
|
message: 'Client created', |
||||
|
}); |
||||
|
await clientsStore.refresh(); |
||||
|
} catch (e) { |
||||
|
if (e instanceof FetchError) { |
||||
|
toast.showToast({ |
||||
|
type: 'error', |
||||
|
title: 'Error', |
||||
|
message: e.data.message, |
||||
|
}); |
||||
|
} |
||||
|
// TODO: handle errors better |
||||
|
} |
||||
|
} |
||||
</script> |
</script> |
||||
|
@ -1,21 +1,19 @@ |
|||||
<template> |
<template> |
||||
<div v-if="modalStore.qrcode"> |
<BaseDialog> |
||||
<div |
<template #trigger> |
||||
class="fixed bottom-0 left-0 right-0 top-0 z-20 flex items-center justify-center bg-black bg-opacity-50" |
<slot /> |
||||
> |
</template> |
||||
<div class="relative rounded-md bg-white p-8 shadow-lg"> |
<template #description> |
||||
<button |
<img :src="qrCode" /> |
||||
class="absolute right-4 top-4 text-gray-600 hover:text-gray-800 dark:text-neutral-500 dark:hover:text-neutral-700" |
</template> |
||||
@click="modalStore.qrcode = null" |
<template #actions> |
||||
> |
<DialogClose> |
||||
<IconsClose class="w-8" /> |
<BaseButton>{{ $t('cancel') }}</BaseButton> |
||||
</button> |
</DialogClose> |
||||
<img :src="modalStore.qrcode" /> |
</template> |
||||
</div> |
</BaseDialog> |
||||
</div> |
|
||||
</div> |
|
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||
const modalStore = useModalStore(); |
defineProps<{ qrCode: string }>(); |
||||
</script> |
</script> |
||||
|
@ -0,0 +1,31 @@ |
|||||
|
<template> |
||||
|
<DialogRoot :modal="true"> |
||||
|
<DialogTrigger :class="triggerClass"><slot name="trigger" /></DialogTrigger> |
||||
|
<DialogPortal> |
||||
|
<DialogOverlay |
||||
|
class="data-[state=open]:animate-overlayShow fixed inset-0 z-30 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50" |
||||
|
/> |
||||
|
<DialogContent |
||||
|
class="data-[state=open]:animate-contentShow fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md p-6 shadow-2xl focus:outline-none dark:bg-neutral-700" |
||||
|
> |
||||
|
<DialogTitle |
||||
|
class="m-0 text-lg font-semibold text-gray-900 dark:text-neutral-200" |
||||
|
> |
||||
|
<slot name="title" /> |
||||
|
</DialogTitle> |
||||
|
<DialogDescription |
||||
|
class="mb-5 mt-2 text-sm leading-normal text-gray-500 dark:text-neutral-300" |
||||
|
> |
||||
|
<slot name="description" /> |
||||
|
</DialogDescription> |
||||
|
<div class="mt-6 flex justify-end gap-2"> |
||||
|
<slot name="actions" /> |
||||
|
</div> |
||||
|
</DialogContent> |
||||
|
</DialogPortal> |
||||
|
</DialogRoot> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
defineProps<{ triggerClass?: string }>(); |
||||
|
</script> |
@ -1,30 +0,0 @@ |
|||||
import { defineStore } from 'pinia'; |
|
||||
|
|
||||
export const useModalStore = defineStore('Modal', () => { |
|
||||
const clientsStore = useClientsStore(); |
|
||||
const clientCreate = ref<null | boolean>(null); |
|
||||
const clientCreateName = ref<string>(''); |
|
||||
const clientExpireDate = ref<string>(''); |
|
||||
const qrcode = ref<null | string>(null); |
|
||||
|
|
||||
function createClient() { |
|
||||
const name = clientCreateName.value; |
|
||||
const expiresAt = clientExpireDate.value || null; |
|
||||
if (!name) return; |
|
||||
|
|
||||
$fetch('/api/client', { |
|
||||
method: 'post', |
|
||||
body: { name, expiresAt }, |
|
||||
}) |
|
||||
.catch((err) => alert(err.message || err.toString())) |
|
||||
.finally(() => clientsStore.refresh().catch(console.error)); |
|
||||
} |
|
||||
|
|
||||
return { |
|
||||
clientCreate, |
|
||||
clientCreateName, |
|
||||
clientExpireDate, |
|
||||
qrcode, |
|
||||
createClient, |
|
||||
}; |
|
||||
}); |
|
Loading…
Reference in new issue