Skip to content

Commit

Permalink
fix(user): simplifying user management
Browse files Browse the repository at this point in the history
  • Loading branch information
Benoit Ngo authored and Ngob committed Nov 2, 2023
1 parent d634a37 commit 4443b3f
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 132 deletions.
1 change: 1 addition & 0 deletions apps/back/src/Dto/Request/UpdateUserDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public function __construct(
private string $email,
#[Assert\AtLeastOneOf([
new Assert\IsNull(),
new Assert\Blank(),
new Assert\PasswordStrength(['minScore' => Assert\PasswordStrength::STRENGTH_WEAK]),
], message: 'The password strength is too low', includeInternalMessages: false)]
private string|null $password,
Expand Down
8 changes: 3 additions & 5 deletions apps/front/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ export default defineNuxtConfig({
},
},
css: [
"@/assets/styles/main.scss",
"primevue/resources/themes/lara-light-blue/theme.css",
"primeflex/primeflex.css",
"primeicons/primeicons.css",
"primevue/resources/themes/lara-light-blue/theme.css",
"@/assets/styles/main.scss",
],
vite: {
plugins: [svgLoader()],
Expand All @@ -42,13 +42,11 @@ export default defineNuxtConfig({
},
},
},
build: {
transpile: ["primevue"],
},
watch: [
"src/assets/styles/_functions.scss",
"src/assets/styles/_variables.scss",
"src/assets/styles/_mixins.scss",
"src/assets/styles/main.scss",
],
// ssr: false
});
15 changes: 15 additions & 0 deletions apps/front/src/assets/styles/main.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Weird issue, probably because the theme is loaded after primevue
// So the divider style did not apply
// This shall be removed after further update of the prime module
.p-divider-solid.p-divider-horizontal:before {
border-top-style: solid;
}
.p-divider-dashed.p-divider-horizontal:before {
border-top-style: dashed;
}
.p-divider-solid.p-divider-vertical:before {
border-left-style: solid;
}
.p-divider-dashed.p-divider-vertical:before {
border-left-style: dashed;
}
63 changes: 63 additions & 0 deletions apps/front/src/components/form/RegisterPasswordInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<template>
<span class="p-float-label">
<Password
toggle-mask
:input-id="inputId"
medium-regex="^(?=(?:[^A-Z]*[A-Z]){2,})(?=(?:[^a-z]*[a-z]){2,})(?=(?:[^\d]*\d){2,})(?=(?:[^\W_]*[\W_]){1,}).{8,}$"
strong-regex="^(?=(?:[^A-Z]*[A-Z]){2,})(?=(?:[^a-z]*[a-z]){2,})(?=(?:[^\d]*\d){2,})(?=(?:[^\W_]*[\W_]){2,}).{8,}$"
v-bind="$attrs"
>
<template #header>
<small>
<p class="mt-2">
{{ $t("components.form.registerPasswordInput.suggestions") }}
</p>
<ul class="pl-2 ml-2 mt-0" style="line-height: 1.5">
<li>
{{
$t("components.form.registerPasswordInput.suggestionsLowercase")
}}
</li>
<li>
{{
$t("components.form.registerPasswordInput.suggestionsUppercase")
}}
</li>
<li>
{{
$t("components.form.registerPasswordInput.suggestionsNumber")
}}
</li>
<li>
{{
$t("components.form.registerPasswordInput.suggestionsSymbol")
}}
</li>
<li>
{{ $t("components.form.registerPasswordInput.suggestionsSize") }}
</li>
<li>
{{
$t("components.form.registerPasswordInput.suggestionsUnicity")
}}
</li>
</ul>
</small>
<Divider />
</template>
</Password>
<label :for="inputId">
<slot>
{{ $t("components.form.registerPasswordInput.password") }}
</slot>
</label>
</span>
</template>
<script lang="ts" setup>
defineOptions({
inheritAttrs: false,
});
const props = defineProps<{
inputId: string;
}>();
</script>
40 changes: 17 additions & 23 deletions apps/front/src/components/user/UserCreateForm.vue
Original file line number Diff line number Diff line change
@@ -1,39 +1,33 @@
<template>
<h1 v-t="{ path: 'components.user.createForm.title' }"></h1>
<form @submit.prevent.stop="registerUser">
<UserForm
v-model:email="email"
v-model:password="password"
v-model:password-confirm="passwordConfirm"
:is-password-confirmed="isPasswordConfirmed"
/>
{{ errorMessage }}
<button :disabled="!isPasswordConfirmed">
{{ $t("components.user.createForm.ok") }}
</button>
</form>
<UserForm class="card" @submit="submit" @cancel="navigateToList">
<template #buttons="{ isValid, cancel }">
<Button type="button" severity="danger" class="mr-2 mb-2" @click="cancel">
{{ $t("components.user.createForm.buttonCancel") }}
</Button>
<Button type="submit" :disabled="!isValid" class="mr-2 mb-2">
{{ $t("components.user.createForm.ok") }}
</Button>
</template>
</UserForm>
{{ errorMessage }}
</template>
<script setup lang="ts">
import useCreateUser from "~/composables/api/user/useCreateUser";
import useUser from "~/composables/user/useUser";
import type { UserInput } from "~/types/UserInput";
const { createUser, errorMessage } = useCreateUser();
const {
email,
password,
passwordConfirm,
isPasswordConfirmed,
securedPassword,
} = useUser();
const registerUser = async () => {
const submit = async (state: UserInput) => {
try {
await createUser(email.value, securedPassword.value);
await createUser(state);
await navigateTo("/users");
} catch (e) {
logger.info(e);
}
};
const navigateToList = () => {
return navigateTo("/users/");
};
</script>

<style scoped lang="scss"></style>
131 changes: 87 additions & 44 deletions apps/front/src/components/user/UserForm.vue
Original file line number Diff line number Diff line change
@@ -1,56 +1,99 @@
<template>
<div>
<label for="email">{{ $t("components.user.form.email") }}</label>
<input
type="text"
:value="email"
@input="$emit('update:email', clearInput($event))"
/>
</div>
<div>
<label for="password">{{ $t("components.user.form.password") }}</label>
<input
name="password"
type="password"
:value="password"
@input="$emit('update:password', clearInput($event))"
/>
</div>
<div>
<label for="passwordConfirm">{{
$t("components.user.form.passwordConfirm")
}}</label>
<input
name="passwordConfirm"
type="password"
:value="passwordConfirm"
@input="$emit('update:passwordConfirm', clearInput($event))"
/>
<span v-if="!isPasswordConfirmed" class="text-danger">
{{ $t("components.user.form.errorPasswordConfirm") }}
</span>
<div class="grid">
<div class="col-12">
<form @submit.prevent.stop="submit">
<div class="field col-12">
<span class="p-float-label">
<InputText
id="user-email"
v-model="state.email"
type="text"
:placeholder="$t('components.user.form.email')"
/>
<label for="user-email">{{
$t("components.user.form.email")
}}</label>
</span>
</div>
<div class="field col-12">
<FormRegisterPasswordInput
v-model="password"
input-id="user-password"
/>
</div>
<div class="field col-12">
<FormRegisterPasswordInput
v-model="passwordConfirm"
input-id="user-passwordConfirm"
>
{{ $t("components.user.form.passwordConfirm") }}
</FormRegisterPasswordInput>
</div>
<div v-show="!isPasswordConfirmed">
{{ $t("components.user.form.errorPasswordConfirm") }}
</div>
<div>
<slot name="buttons" :is-valid="isValid" :cancel="cancel">
<Button
type="button"
severity="danger"
class="mr-2 mb-2"
@click="cancel"
>
{{ $t("components.user.form.buttonCancel") }}
</Button>
<Button type="submit" :disabled="!isValid" class="mr-2 mb-2">
{{ $t("components.user.form.ok") }}
</Button>
</slot>
</div>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import type { User } from "~/types/User";
defineProps<
Omit<User, "id"> & {
isPasswordConfirmed: boolean;
password: string;
passwordConfirm: string;
}
>();
interface EventEmitter {
(e: "update:email", email: string): void;
(e: "update:password", password: string): void;
(e: "update:passwordConfirm", passwordConfirm: string): void;
interface Props {
defaultValue: Omit<User, "id">;
}
const props = withDefaults(defineProps<Props>(), {
defaultValue() {
return {
email: "",
};
},
});
const state = reactive({ ...props.defaultValue });
const password = ref("");
const passwordConfirm = ref("");
const isPasswordConfirmed = computed(
() => password.value === passwordConfirm.value
);
defineEmits<EventEmitter>();
const isPasswordEmpty = computed(() => !password.value);
const securedPassword = computed(() =>
isPasswordConfirmed && isPasswordEmpty ? password.value : ""
);
const isValid = isPasswordConfirmed;
const clearInput = (inputEvent: Event) => {
return (inputEvent.target as HTMLInputElement)?.value || "";
interface EventEmitter {
(
e: "submit",
value: Omit<User, "id"> & {
password: string;
}
): void;
(e: "cancel"): void;
}
const emits = defineEmits<EventEmitter>();
const submit = () => {
emits("submit", { ...state, password: securedPassword.value });
};
const cancel = () => {
emits("cancel");
};
</script>

Expand Down
54 changes: 18 additions & 36 deletions apps/front/src/components/user/UserUpdateForm.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
<template>
<div
v-show="userPending"
v-show="pendingData"
v-t="{ path: 'components.user.updateForm.pending' }"
></div>
<form v-if="data" @submit.prevent.stop="updateUser">
<h1 v-t="{ path: 'components.user.updateForm.title', args: data }"></h1>
<UserForm
v-model:email="email"
v-model:password="password"
v-model:password-confirm="passwordConfirm"
:is-password-confirmed="isPasswordConfirmed"
/>
<Button type="submit" :disabled="!isPasswordConfirmed">{{
$t("components.user.updateForm.ok")
}}</Button>
</form>
<h1 v-t="{ path: 'components.user.updateForm.title', args: data }"></h1>
<UserForm :default-value="data" @submit="submit" @cancel="navigateToList">
</UserForm>
<div>
{{ errorMessage }}
{{ error }}
Expand All @@ -24,39 +15,30 @@
<script setup lang="ts">
import useGetUser from "~/composables/api/user/useGetUser";
import useUpdateUser from "~/composables/api/user/useUpdateUser";
import useUser from "~/composables/user/useUser";
import type { UserInput } from "~/types/UserInput";
const props = defineProps<{
interface Props {
userId: string;
}>();
}
const props = defineProps<Props>();
const { errorMessage, updateUser: updateUserApi } = useUpdateUser();
const {
data,
error,
pending: userPending,
refresh: userRefresh,
pending: pendingData,
} = await useGetUser(props.userId as string);
const {
email,
password,
passwordConfirm,
isPasswordConfirmed,
securedPassword,
} = useUser(data);
const updateUser = async () => {
await updateUserApi(
data.value.id,
{
email: email.value,
},
securedPassword.value
);
userRefresh();
await navigateTo("/users");
const submit = async (value: UserInput) => {
// If you need to copy the value, create a clone so it does not track reactivity
//data.value = {...data.value, ...value};
await updateUserApi(parseInt(props.userId), value);
return navigateTo("/users/");
};
const navigateToList = () => {
return navigateTo("/users/");
};
</script>

<style scoped lang="scss"></style>
Loading

0 comments on commit 4443b3f

Please sign in to comment.