feat(KAN-49): rebuild user administration
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
<script setup lang="ts">
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { KeyRound, UserRound } from 'lucide-vue-next';
|
||||
import { computed, ref } from 'vue';
|
||||
import {
|
||||
save,
|
||||
store,
|
||||
} from '@/actions/App/Http/Controllers/Admin/UserManagementController';
|
||||
import FormFeedback from '@/components/FormFeedback.vue';
|
||||
import InputError from '@/components/InputError.vue';
|
||||
import PasswordInput from '@/components/PasswordInput.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useFormProtection } from '@/composables/useFormProtection';
|
||||
import { useLocalization } from '@/composables/useLocalization';
|
||||
import type { UserModel } from '@/types/models/user-model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
mode: 'create' | 'edit';
|
||||
user?: UserModel | null;
|
||||
}>(),
|
||||
{ user: null },
|
||||
);
|
||||
|
||||
const emit = defineEmits<{ cancel: [] }>();
|
||||
const { t } = useLocalization();
|
||||
const isEdit = computed(() => props.mode === 'edit');
|
||||
const confirmationOpen = ref(false);
|
||||
const form = useForm(
|
||||
'ManagedUser:' + props.mode + ':' + (props.user?.id ?? 'new'),
|
||||
{
|
||||
name: props.user?.name ?? '',
|
||||
email: props.user?.email ?? '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
must_change_password_on_next_login:
|
||||
props.user?.must_change_password_on_next_login ?? true,
|
||||
},
|
||||
);
|
||||
const protection = useFormProtection(form);
|
||||
|
||||
function performSubmit(): void {
|
||||
confirmationOpen.value = false;
|
||||
protection.submit(
|
||||
isEdit.value && props.user ? save(props.user.id) : store(),
|
||||
{ preserveScroll: true },
|
||||
);
|
||||
}
|
||||
|
||||
function requestSubmit(): void {
|
||||
if (isEdit.value) {
|
||||
confirmationOpen.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
performSubmit();
|
||||
}
|
||||
|
||||
function updateConfirmation(open: boolean): void {
|
||||
if (!form.processing) {
|
||||
confirmationOpen.value = open;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card class="gap-0 overflow-hidden rounded-xl py-0 shadow-none">
|
||||
<CardHeader class="gap-stack border-b p-5 sm:p-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<span
|
||||
class="bg-accent text-primary flex size-10 shrink-0 items-center justify-center rounded-md"
|
||||
>
|
||||
<UserRound class="size-5" aria-hidden="true" />
|
||||
</span>
|
||||
<div class="gap-stack grid min-w-0">
|
||||
<h2 class="text-title">
|
||||
{{ t('adminUsers.form.detailsTitle') }}
|
||||
</h2>
|
||||
<p class="text-muted-foreground">
|
||||
{{
|
||||
t(
|
||||
isEdit
|
||||
? 'adminUsers.form.editDescription'
|
||||
: 'adminUsers.form.createDescription',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="p-5 sm:p-6">
|
||||
<form
|
||||
class="gap-page grid min-w-0"
|
||||
:aria-busy="form.processing"
|
||||
@submit.prevent="requestSubmit"
|
||||
>
|
||||
<FormFeedback
|
||||
:state="protection.state.value"
|
||||
@retry="performSubmit"
|
||||
/>
|
||||
|
||||
<fieldset
|
||||
class="gap-column grid min-w-0"
|
||||
:disabled="form.processing"
|
||||
>
|
||||
<legend class="sr-only">
|
||||
{{ t('adminUsers.form.detailsTitle') }}
|
||||
</legend>
|
||||
|
||||
<div class="grid gap-5 sm:grid-cols-2">
|
||||
<div class="grid min-w-0 gap-2">
|
||||
<Label for="managed-user-name">{{
|
||||
t('adminUsers.form.name')
|
||||
}}</Label>
|
||||
<Input
|
||||
id="managed-user-name"
|
||||
v-model="form.name"
|
||||
name="name"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="name"
|
||||
:placeholder="
|
||||
t('adminUsers.form.namePlaceholder')
|
||||
"
|
||||
:aria-invalid="!!form.errors.name"
|
||||
aria-describedby="managed-user-name-error"
|
||||
/>
|
||||
<InputError
|
||||
id="managed-user-name-error"
|
||||
:message="form.errors.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid min-w-0 gap-2">
|
||||
<Label for="managed-user-email">{{
|
||||
t('adminUsers.form.email')
|
||||
}}</Label>
|
||||
<Input
|
||||
id="managed-user-email"
|
||||
v-model="form.email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
autocomplete="email"
|
||||
:placeholder="
|
||||
t('adminUsers.form.emailPlaceholder')
|
||||
"
|
||||
:aria-invalid="!!form.errors.email"
|
||||
aria-describedby="managed-user-email-error"
|
||||
/>
|
||||
<InputError
|
||||
id="managed-user-email-error"
|
||||
:message="form.errors.email"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section
|
||||
aria-labelledby="managed-user-password-title"
|
||||
class="gap-column grid min-w-0 border-t pt-6"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<KeyRound
|
||||
class="text-muted-foreground mt-0.5 size-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div class="gap-stack grid min-w-0">
|
||||
<h3
|
||||
id="managed-user-password-title"
|
||||
class="font-semibold"
|
||||
>
|
||||
{{ t('adminUsers.form.passwordTitle') }}
|
||||
</h3>
|
||||
<p class="text-caption text-muted-foreground">
|
||||
{{
|
||||
t(
|
||||
isEdit
|
||||
? 'adminUsers.form.passwordEditHelp'
|
||||
: 'adminUsers.form.passwordCreateHelp',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-5 sm:grid-cols-2">
|
||||
<div class="grid min-w-0 gap-2">
|
||||
<Label for="managed-user-password">{{
|
||||
t(
|
||||
isEdit
|
||||
? 'adminUsers.form.newPassword'
|
||||
: 'adminUsers.form.password',
|
||||
)
|
||||
}}</Label>
|
||||
<PasswordInput
|
||||
id="managed-user-password"
|
||||
v-model="form.password"
|
||||
name="password"
|
||||
:required="!isEdit"
|
||||
autocomplete="new-password"
|
||||
:placeholder="
|
||||
t(
|
||||
isEdit
|
||||
? 'adminUsers.form.passwordEditPlaceholder'
|
||||
: 'adminUsers.form.passwordPlaceholder',
|
||||
)
|
||||
"
|
||||
:aria-invalid="!!form.errors.password"
|
||||
aria-describedby="managed-user-password-error"
|
||||
/>
|
||||
<InputError
|
||||
id="managed-user-password-error"
|
||||
:message="form.errors.password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid min-w-0 gap-2">
|
||||
<Label
|
||||
for="managed-user-password-confirmation"
|
||||
>{{
|
||||
t('adminUsers.form.confirmPassword')
|
||||
}}</Label
|
||||
>
|
||||
<PasswordInput
|
||||
id="managed-user-password-confirmation"
|
||||
v-model="form.password_confirmation"
|
||||
name="password_confirmation"
|
||||
:required="!isEdit"
|
||||
autocomplete="new-password"
|
||||
:placeholder="
|
||||
t(
|
||||
'adminUsers.form.confirmPasswordPlaceholder',
|
||||
)
|
||||
"
|
||||
:aria-invalid="
|
||||
!!form.errors.password_confirmation
|
||||
"
|
||||
aria-describedby="managed-user-password-confirmation-error"
|
||||
/>
|
||||
<InputError
|
||||
id="managed-user-password-confirmation-error"
|
||||
:message="form.errors.password_confirmation"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="grid gap-2 border-t pt-6">
|
||||
<Label
|
||||
for="managed-user-force-password-change"
|
||||
class="bg-muted/35 flex cursor-pointer items-start gap-3 rounded-lg border p-4"
|
||||
>
|
||||
<Checkbox
|
||||
id="managed-user-force-password-change"
|
||||
v-model="
|
||||
form.must_change_password_on_next_login
|
||||
"
|
||||
name="must_change_password_on_next_login"
|
||||
:invalid="
|
||||
!!form.errors
|
||||
.must_change_password_on_next_login
|
||||
"
|
||||
aria-describedby="managed-user-force-password-change-help managed-user-force-password-change-error"
|
||||
/>
|
||||
<span class="grid gap-1">
|
||||
<span>{{
|
||||
t('adminUsers.form.forcePasswordChange')
|
||||
}}</span>
|
||||
<span
|
||||
id="managed-user-force-password-change-help"
|
||||
class="text-caption text-muted-foreground font-normal"
|
||||
>
|
||||
{{
|
||||
t(
|
||||
'adminUsers.form.forcePasswordChangeHelp',
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</span>
|
||||
</Label>
|
||||
<InputError
|
||||
id="managed-user-force-password-change-error"
|
||||
:message="
|
||||
form.errors.must_change_password_on_next_login
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div
|
||||
class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@click="emit('cancel')"
|
||||
>
|
||||
{{ t('common.cancel') }}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
:loading="form.processing"
|
||||
class="sm:min-w-36"
|
||||
data-test="managed-user-submit"
|
||||
>
|
||||
{{
|
||||
t(
|
||||
isEdit
|
||||
? 'adminUsers.form.reviewChanges'
|
||||
: 'adminUsers.form.createAction',
|
||||
)
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog :open="confirmationOpen" @update:open="updateConfirmation">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{
|
||||
t('adminUsers.form.confirmTitle', {
|
||||
name: form.name || props.user?.name || '',
|
||||
})
|
||||
}}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t('adminUsers.form.confirmDescription') }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div
|
||||
class="border-warning/35 bg-warning/8 text-body rounded-md border p-4"
|
||||
>
|
||||
{{ t('adminUsers.form.confirmWarning') }}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
:disabled="form.processing"
|
||||
@click="confirmationOpen = false"
|
||||
>
|
||||
{{ t('common.cancel') }}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
:loading="form.processing"
|
||||
data-test="confirm-managed-user-update"
|
||||
@click="performSubmit"
|
||||
>
|
||||
{{ t('adminUsers.form.confirmAction') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user