Browse Source

be able to change name, email

pull/1645/head
Bernd Storath 6 months ago
parent
commit
86a7329332
  1. 19
      src/app/components/form/PasswordField.vue
  2. 2
      src/app/components/form/TextField.vue
  3. 8
      src/app/pages/clients/[id].vue
  4. 168
      src/app/pages/me.vue
  5. 13
      src/server/api/me/index.post.ts
  6. 12
      src/server/database/repositories/user/service.ts
  7. 20
      src/server/database/repositories/user/types.ts

19
src/app/components/form/PasswordField.vue

@ -0,0 +1,19 @@
<template>
<Label :for="id" class="font-semibold md:align-middle md:leading-10">
{{ label }}
</Label>
<input
:id="id"
v-model.trim="data"
:name="id"
type="password"
:autocomplete="autocomplete"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
/>
</template>
<script lang="ts" setup>
defineProps<{ id: string; label: string; autocomplete: string }>();
const data = defineModel<string>();
</script>

2
src/app/components/form/TextField.vue

@ -4,7 +4,7 @@
</Label> </Label>
<input <input
:id="id" :id="id"
v-model="data" v-model.trim="data"
:name="id" :name="id"
type="text" type="text"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"

8
src/app/pages/clients/[id].vue

@ -10,7 +10,7 @@
<FormHeading> <FormHeading>
{{ $t('me.sectionGeneral') }} {{ $t('me.sectionGeneral') }}
</FormHeading> </FormHeading>
<FormTextField id="name" v-model.trim="data.name" label="Name" /> <FormTextField id="name" v-model="data.name" label="Name" />
<FormSwitchField <FormSwitchField
id="enabled" id="enabled"
v-model="data.enabled" v-model="data.enabled"
@ -18,7 +18,7 @@
/> />
<FormDateField <FormDateField
id="expiresAt" id="expiresAt"
v-model.trim="data.expiresAt" v-model="data.expiresAt"
label="Expire Date" label="Expire Date"
/> />
</FormGroup> </FormGroup>
@ -26,12 +26,12 @@
<FormHeading>Address</FormHeading> <FormHeading>Address</FormHeading>
<FormTextField <FormTextField
id="ipv4Address" id="ipv4Address"
v-model.trim="data.ipv4Address" v-model="data.ipv4Address"
label="IPv4" label="IPv4"
/> />
<FormTextField <FormTextField
id="ipv6Address" id="ipv6Address"
v-model.trim="data.ipv6Address" v-model="data.ipv6Address"
label="IPv6" label="IPv6"
/> />
</FormGroup> </FormGroup>

168
src/app/pages/me.vue

@ -5,116 +5,106 @@
<PanelHeadTitle :text="$t('pages.me')" /> <PanelHeadTitle :text="$t('pages.me')" />
</PanelHead> </PanelHead>
<PanelBody class="dark:text-neutral-200"> <PanelBody class="dark:text-neutral-200">
<section class="grid grid-cols-1 gap-4 md:grid-cols-2"> <FormElement @submit.prevent="submit">
<h4 class="col-span-full py-6 text-2xl"> <FormGroup>
{{ $t('me.sectionGeneral') }} <FormHeading>{{ $t('me.sectionGeneral') }}</FormHeading>
</h4> <FormTextField id="name" v-model="name" :label="$t('name')" />
<Label <FormTextField id="email" v-model="email" :label="$t('email')" />
for="username" <FormActionField type="submit" :label="$t('save')" />
class="font-semibold md:align-middle md:leading-10" </FormGroup>
> </FormElement>
{{ $t('username') }} <FormElement @submit.prevent="updatePassword">
</Label> <FormGroup>
<input <FormHeading>{{ $t('me.sectionPassword') }}</FormHeading>
id="username" <FormPasswordField
v-model.trim="username"
type="text"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
/>
<Label for="name" class="font-semibold md:align-middle md:leading-10">
{{ $t('name') }}
</Label>
<input
id="name"
v-model.trim="name"
type="text"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
/>
<Label
for="email"
class="font-semibold md:align-middle md:leading-10"
>
{{ $t('email') }}
</Label>
<input
id="email"
v-model.trim="email"
type="email"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
/>
<div class="col-span-full">
<BaseButton @click="submit">{{ $t('save') }}</BaseButton>
</div>
</section>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
<h4 class="col-span-full py-6 text-2xl">
{{ $t('me.sectionPassword') }}
</h4>
<Label
for="current-password"
class="font-semibold md:align-middle md:leading-10"
>
{{ $t('currentPassword') }}
</Label>
<input
id="current-password" id="current-password"
v-model.trim="currentPassword" v-model="currentPassword"
type="password"
autocomplete="current-password" autocomplete="current-password"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" :label="$t('currentPassword')"
/> />
<Label <FormPasswordField
for="new-password"
class="font-semibold md:align-middle md:leading-10"
>
{{ $t('setup.newPassword') }}
</Label>
<input
id="new-password" id="new-password"
v-model.trim="newPassword" v-model="newPassword"
type="password"
autocomplete="new-password" autocomplete="new-password"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" :label="$t('setup.newPassword')"
/> />
<Label <FormPasswordField
for="confirm-password"
class="font-semibold md:align-middle md:leading-10"
>
{{ $t('confirmPassword') }}
</Label>
<input
id="confirm-password" id="confirm-password"
v-model.trim="confirmPassword" v-model="confirmPassword"
type="password"
autocomplete="new-password" autocomplete="new-password"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" :label="$t('confirmPassword')"
/> />
<div class="col-span-full"> <FormActionField type="submit" :label="$t('updatePassword')" />
<BaseButton @click="updatePassword">{{ </FormGroup>
$t('updatePassword') </FormElement>
}}</BaseButton>
</div>
</section>
</PanelBody> </PanelBody>
</Panel> </Panel>
</main> </main>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { FetchError } from 'ofetch';
const authStore = useAuthStore(); const authStore = useAuthStore();
authStore.update(); authStore.update();
const toast = useToast();
const username = ref(authStore.userData?.username);
const name = ref(authStore.userData?.name); const name = ref(authStore.userData?.name);
const email = ref(authStore.userData?.email);
// TODO: handle update password const rawEmail = ref(authStore.userData?.email);
const email = computed({
get: () => rawEmail.value ?? undefined,
set: (value) => {
const temp = value?.trim() ?? null;
if (temp === '') {
rawEmail.value = null;
return;
}
rawEmail.value = temp;
return;
},
});
const currentPassword = ref(authStore.userData?.email); async function submit() {
const newPassword = ref(authStore.userData?.email); try {
const confirmPassword = ref(authStore.userData?.email); const res = await $fetch(`/api/me`, {
method: 'post',
body: {
name: name.value,
email: rawEmail.value,
},
});
toast.showToast({
type: 'success',
title: 'Success',
message: 'Saved',
});
if (!res.success) {
throw new Error('Failed to save');
}
await refreshNuxtData();
} catch (e) {
if (e instanceof FetchError) {
toast.showToast({
type: 'error',
title: 'Error',
message: e.data.message,
});
}
}
}
function submit() {} // TODO: handle update password
const currentPassword = ref('');
const newPassword = ref('');
const confirmPassword = ref('');
function updatePassword() {} function updatePassword() {
if (newPassword.value !== confirmPassword.value) {
toast.showToast({
type: 'error',
title: 'Error',
message: 'Passwords do not match',
});
}
}
</script> </script>

13
src/server/api/me/index.post.ts

@ -0,0 +1,13 @@
import { UserUpdateSchema } from '#db/repositories/user/types';
export default definePermissionEventHandler(
actions.CLIENT,
async ({ event, user }) => {
const { name, email } = await readValidatedBody(
event,
validateZod(UserUpdateSchema)
);
await Database.users.update(user.id, name, email);
return { success: true };
}
);

12
src/server/database/repositories/user/service.ts

@ -14,6 +14,14 @@ function createPreparedStatement(db: DBType) {
where: eq(user.username, sql.placeholder('username')), where: eq(user.username, sql.placeholder('username')),
}) })
.prepare(), .prepare(),
update: db
.update(user)
.set({
name: sql.placeholder('name') as never as string,
email: sql.placeholder('email') as never as string,
})
.where(eq(user.id, sql.placeholder('id')))
.prepare(),
}; };
} }
@ -60,4 +68,8 @@ export class UserService {
}); });
}); });
} }
async update(id: ID, name: string, email: string | null) {
return this.#statements.update.execute({ id, name, email });
}
} }

20
src/server/database/repositories/user/types.ts

@ -20,6 +20,18 @@ const password = z
const remember = z.boolean({ message: 'zod.user.remember' }); const remember = z.boolean({ message: 'zod.user.remember' });
const name = z
.string({ message: 'zod.user.name' })
.min(1, 'zod.user.nameMin')
.pipe(safeStringRefine);
const email = z
.string({ message: 'zod.user.email' })
.min(5, 'zod.user.emailMin')
.email({ message: 'zod.user.emailInvalid' })
.pipe(safeStringRefine)
.nullable();
export const UserLoginSchema = z.object( export const UserLoginSchema = z.object(
{ {
username: username, username: username,
@ -41,3 +53,11 @@ export const UserSetupType = z.object(
}, },
{ message: objectMessage } { message: objectMessage }
); );
export const UserUpdateSchema = z.object(
{
name: name,
email: email,
},
{ message: objectMessage }
);

Loading…
Cancel
Save