Files
warren/frontend/components/actions/ShareDialog.vue
2025-08-29 15:32:23 +02:00

204 lines
6.8 KiB
Vue

<script setup lang="ts">
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import { useShareDialog } from '@/stores';
import { toTypedSchema } from '@vee-validate/yup';
import { useForm } from 'vee-validate';
import { toast } from 'vue-sonner';
import { createShare, listShares } from '~/lib/api/shares';
import { shareSchema } from '~/lib/schemas/share';
import type { Share } from '~/shared/types/shares';
const warrenStore = useWarrenStore();
const dialog = useShareDialog();
const newScreen = ref(false);
const existingShares = ref<Share[]>([]);
useAsyncData('current-file-shares', async () => {
if (warrenStore.current == null || dialog.target == null) {
return [];
}
const result = await listShares(
warrenStore.current.warrenId,
joinPaths(warrenStore.current.path, dialog.target.name)
);
if (!result.success) {
return [];
}
if (result.shares.length < 1) {
newScreen.value = true;
} else {
newScreen.value = false;
}
existingShares.value = result.shares;
});
dialog.$subscribe(async (_, value) => {
if (value.target !== null) {
await refreshNuxtData('current-file-shares');
}
});
function onOpenChange() {
dialog.reset();
}
function onCancel() {
if (newScreen.value && existingShares.value.length > 0) {
newScreen.value = false;
} else {
dialog.reset();
}
}
const form = useForm({
validationSchema: toTypedSchema(shareSchema),
});
const onSubmit = form.handleSubmit(async (values) => {
if (dialog.target == null || warrenStore.current == null) {
return;
}
const result = await createShare(
warrenStore.current.warrenId,
joinPaths(warrenStore.current.path, dialog.target.name),
values.password ?? null,
values.lifetime ?? null
);
if (result.success) {
toast.success('Share', {
description: `Successfully created a share for ${dialog.target.name}`,
});
await refreshNuxtData('current-file-shares');
newScreen.value = false;
} else {
toast.error('Share', {
description: `Failed to create share`,
});
}
});
</script>
<template>
<Dialog :open="dialog.target != null" @update:open="onOpenChange">
<DialogTrigger as-child>
<slot />
</DialogTrigger>
<DialogContent
v-if="dialog.target != null"
class="w-full !max-w-[calc(100vw-8px)] sm:!max-w-[min(98vw,1000px)]"
>
<DialogHeader class="overflow-hidden">
<DialogTitle class="truncate"
>Share {{ dialog.target.name }}</DialogTitle
>
<DialogDescription
>Create a shareable link to this
{{ dialog.target.fileType }}</DialogDescription
>
</DialogHeader>
<SharesTable v-if="!newScreen" :shares="existingShares" />
<form
v-else
id="share-form"
class="grid gap-4"
@submit.prevent="onSubmit"
>
<FormField v-slot="{ componentField }" name="password">
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
v-bind="componentField"
id="password"
type="password"
autocomplete="off"
data-1p-ignore
data-protonpass-ignore
data-bwignore
/>
</FormControl>
<FormMessage />
<FormDescription>
<span
v-if="
form.values.password == null ||
form.values.password.length < 1
"
>
The share will not require a password
</span>
<span v-else>
The share will require the specified password
</span>
</FormDescription>
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="lifetime">
<FormItem>
<FormLabel>Lifetime (seconds)</FormLabel>
<FormControl>
<div
class="flex w-full flex-row justify-between gap-1"
>
<Input
v-bind="componentField"
id="lifetime"
type="number"
class="w-full max-w-full min-w-0"
autocomplete="off"
data-1p-ignore
data-protonpass-ignore
data-bwignore
/>
</div>
</FormControl>
<FormMessage />
<FormDescription>
<span
v-if="
form.values.lifetime != null &&
form.values.lifetime > 0
"
class="w-full"
>
The share will expire in
{{
$dayjs
.duration({
seconds: form.values.lifetime,
})
.humanize()
}}
</span>
<span v-else> The share will be permanent </span>
</FormDescription>
</FormItem>
</FormField>
</form>
<DialogFooter>
<Button variant="ghost" @click="onCancel">Cancel</Button>
<Button v-if="newScreen" type="submit" form="share-form"
>Share</Button
>
<Button v-else @click="() => (newScreen = true)">New</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</template>