mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
186 lines
6.7 KiB
Vue
186 lines
6.7 KiB
Vue
|
|
<template>
|
||
|
|
<form v-if="identity" class="grid gap-6" @submit="save">
|
||
|
|
<Transition name="slide-up">
|
||
|
|
<Alert v-if="dirty" layout="button" class="absolute bottom-2 z-10 inset-x-2 w-[calc(100%-1rem)]">
|
||
|
|
<SaveOff class="size-4" />
|
||
|
|
<AlertTitle>Unsaved changes</AlertTitle>
|
||
|
|
<AlertDescription>
|
||
|
|
Click "apply" to save your changes.
|
||
|
|
</AlertDescription>
|
||
|
|
<Button variant="secondary" class="w-full" typ="submit" :disabled="submitting">Apply</Button>
|
||
|
|
</Alert>
|
||
|
|
</Transition>
|
||
|
|
|
||
|
|
<FormField v-slot="{ handleChange, handleBlur }" name="banner">
|
||
|
|
<TextInput :title="m.bright_late_osprey_renew()" :description="m.great_level_lamb_sway()">
|
||
|
|
<Input type="file" accept="image/*" @change="handleChange" @blur="handleBlur" />
|
||
|
|
</TextInput>
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ setValue }" name="avatar">
|
||
|
|
<TextInput :title="m.safe_icy_bulldog_quell()">
|
||
|
|
<ImageUploader v-model:image="identity.account.avatar" @submit-file="(file) => setValue(file)"
|
||
|
|
@submit-url="(url) => setValue(url)" />
|
||
|
|
</TextInput>
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ componentField }" name="name">
|
||
|
|
<TextInput :title="m.mild_known_mallard_jolt()" :description="m.lime_dry_skunk_loop()">
|
||
|
|
<Input v-bind="componentField" />
|
||
|
|
</TextInput>
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ componentField }" name="username">
|
||
|
|
<TextInput :title="m.neat_silly_dog_prosper()" :description="m.petty_plane_tadpole_earn()">
|
||
|
|
<Input v-bind="componentField" />
|
||
|
|
</TextInput>
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ componentField }" name="bio">
|
||
|
|
<TextInput :title="m.next_caring_ladybug_hack()" :description="m.stale_just_anaconda_earn()">
|
||
|
|
<Textarea rows="10" v-bind="componentField" />
|
||
|
|
</TextInput>
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ value, handleChange }" name="fields">
|
||
|
|
<Fields :title="m.aqua_mealy_toucan_pride()" :value="value" @update:value="handleChange" />
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ value, handleChange }" name="bot" as-child>
|
||
|
|
<SwitchInput :title="m.gaudy_each_opossum_play()" :description="m.grassy_acidic_gadfly_cure()">
|
||
|
|
<Switch :model-value="value" @update:model-value="handleChange" />
|
||
|
|
</SwitchInput>
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ value, handleChange }" name="locked" as-child>
|
||
|
|
<SwitchInput :title="m.dirty_moving_shark_emerge()" :description="m.bright_fun_mouse_boil()">
|
||
|
|
<Switch :model-value="value" @update:model-value="handleChange" />
|
||
|
|
</SwitchInput>
|
||
|
|
</FormField>
|
||
|
|
|
||
|
|
<FormField v-slot="{ value, handleChange }" name="discoverable" as-child>
|
||
|
|
<SwitchInput :title="m.red_vivid_cuckoo_spark()" :description="m.plain_zany_donkey_dart()">
|
||
|
|
<Switch :model-value="value" @update:model-value="handleChange" />
|
||
|
|
</SwitchInput>
|
||
|
|
</FormField>
|
||
|
|
</form>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script lang="ts" setup>
|
||
|
|
import { SaveOff } from "lucide-vue-next";
|
||
|
|
import { useForm } from "vee-validate";
|
||
|
|
import { toast } from "vue-sonner";
|
||
|
|
import SwitchInput from "~/components/form/switch.vue";
|
||
|
|
import TextInput from "~/components/form/text.vue";
|
||
|
|
import * as m from "~/paraglide/messages.js";
|
||
|
|
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
||
|
|
import { Button } from "../ui/button";
|
||
|
|
import { FormField } from "../ui/form";
|
||
|
|
import { Input } from "../ui/input";
|
||
|
|
import { Switch } from "../ui/switch";
|
||
|
|
import { Textarea } from "../ui/textarea";
|
||
|
|
import { formSchema } from "./profile";
|
||
|
|
import Fields from "./profile/fields.vue";
|
||
|
|
import ImageUploader from "./profile/image-uploader.vue";
|
||
|
|
|
||
|
|
const dirty = computed(() => form.meta.value.dirty);
|
||
|
|
const submitting = ref(false);
|
||
|
|
|
||
|
|
if (!identity.value) {
|
||
|
|
throw new Error("Identity not found.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const account = computed(() => identity.value?.account as Identity["account"]);
|
||
|
|
const schema = formSchema(identity.value);
|
||
|
|
|
||
|
|
const form = useForm({
|
||
|
|
validationSchema: schema,
|
||
|
|
initialValues: {
|
||
|
|
bio: account.value.source?.note ?? "",
|
||
|
|
bot: account.value.bot ?? false,
|
||
|
|
locked: account.value.locked ?? false,
|
||
|
|
discoverable: account.value.discoverable ?? true,
|
||
|
|
username: account.value.username,
|
||
|
|
name: account.value.display_name,
|
||
|
|
fields:
|
||
|
|
account.value.source?.fields.map((f) => ({
|
||
|
|
name: f.name,
|
||
|
|
value: f.value,
|
||
|
|
})) ?? [],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const save = form.handleSubmit(async (values) => {
|
||
|
|
if (submitting.value) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
submitting.value = true;
|
||
|
|
const id = toast.loading(m.jolly_noble_sloth_breathe());
|
||
|
|
|
||
|
|
const changedData = {
|
||
|
|
display_name:
|
||
|
|
values.name === account.value.display_name
|
||
|
|
? undefined
|
||
|
|
: values.name,
|
||
|
|
username:
|
||
|
|
values.username === account.value.username
|
||
|
|
? undefined
|
||
|
|
: values.username,
|
||
|
|
note:
|
||
|
|
values.bio === account.value.source?.note ? undefined : values.bio,
|
||
|
|
bot: values.bot === account.value.bot ? undefined : values.bot,
|
||
|
|
locked:
|
||
|
|
values.locked === account.value.locked ? undefined : values.locked,
|
||
|
|
discoverable:
|
||
|
|
values.discoverable === account.value.discoverable
|
||
|
|
? undefined
|
||
|
|
: values.discoverable,
|
||
|
|
// Can't compare two arrays directly in JS, so we need to check if all fields are the same
|
||
|
|
fields_attributes: values.fields.every((field) =>
|
||
|
|
account.value.source?.fields?.some(
|
||
|
|
(f) => f.name === field.name && f.value === field.value,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
? undefined
|
||
|
|
: values.fields,
|
||
|
|
header: values.banner ? values.banner : undefined,
|
||
|
|
avatar: values.avatar ? values.avatar : undefined,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (
|
||
|
|
Object.values(changedData).filter((v) => v !== undefined).length === 0
|
||
|
|
) {
|
||
|
|
toast.dismiss(id);
|
||
|
|
toast.error(m.tough_alive_niklas_promise());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const { data } = await client.value.updateCredentials(
|
||
|
|
Object.fromEntries(
|
||
|
|
Object.entries(changedData).filter(([, v]) => v !== undefined),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
|
||
|
|
toast.dismiss(id);
|
||
|
|
toast.success(m.spry_honest_kestrel_arrive());
|
||
|
|
|
||
|
|
if (identity.value) {
|
||
|
|
identity.value.account = data;
|
||
|
|
}
|
||
|
|
|
||
|
|
form.resetForm({
|
||
|
|
values: {
|
||
|
|
...form.values,
|
||
|
|
...values,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
} catch (e) {
|
||
|
|
toast.dismiss(id);
|
||
|
|
}
|
||
|
|
|
||
|
|
submitting.value = false;
|
||
|
|
});
|
||
|
|
</script>
|