Skip to content

Commit 4443b3f

Browse files
Benoit NgoNgob
authored andcommitted
fix(user): simplifying user management
1 parent d634a37 commit 4443b3f

File tree

14 files changed

+257
-132
lines changed

14 files changed

+257
-132
lines changed

apps/back/src/Dto/Request/UpdateUserDto.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public function __construct(
1313
private string $email,
1414
#[Assert\AtLeastOneOf([
1515
new Assert\IsNull(),
16+
new Assert\Blank(),
1617
new Assert\PasswordStrength(['minScore' => Assert\PasswordStrength::STRENGTH_WEAK]),
1718
], message: 'The password strength is too low', includeInternalMessages: false)]
1819
private string|null $password,

apps/front/nuxt.config.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ export default defineNuxtConfig({
2626
},
2727
},
2828
css: [
29-
"@/assets/styles/main.scss",
30-
"primevue/resources/themes/lara-light-blue/theme.css",
3129
"primeflex/primeflex.css",
3230
"primeicons/primeicons.css",
31+
"primevue/resources/themes/lara-light-blue/theme.css",
32+
"@/assets/styles/main.scss",
3333
],
3434
vite: {
3535
plugins: [svgLoader()],
@@ -42,13 +42,11 @@ export default defineNuxtConfig({
4242
},
4343
},
4444
},
45-
build: {
46-
transpile: ["primevue"],
47-
},
4845
watch: [
4946
"src/assets/styles/_functions.scss",
5047
"src/assets/styles/_variables.scss",
5148
"src/assets/styles/_mixins.scss",
49+
"src/assets/styles/main.scss",
5250
],
5351
// ssr: false
5452
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Weird issue, probably because the theme is loaded after primevue
2+
// So the divider style did not apply
3+
// This shall be removed after further update of the prime module
4+
.p-divider-solid.p-divider-horizontal:before {
5+
border-top-style: solid;
6+
}
7+
.p-divider-dashed.p-divider-horizontal:before {
8+
border-top-style: dashed;
9+
}
10+
.p-divider-solid.p-divider-vertical:before {
11+
border-left-style: solid;
12+
}
13+
.p-divider-dashed.p-divider-vertical:before {
14+
border-left-style: dashed;
15+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
<span class="p-float-label">
3+
<Password
4+
toggle-mask
5+
:input-id="inputId"
6+
medium-regex="^(?=(?:[^A-Z]*[A-Z]){2,})(?=(?:[^a-z]*[a-z]){2,})(?=(?:[^\d]*\d){2,})(?=(?:[^\W_]*[\W_]){1,}).{8,}$"
7+
strong-regex="^(?=(?:[^A-Z]*[A-Z]){2,})(?=(?:[^a-z]*[a-z]){2,})(?=(?:[^\d]*\d){2,})(?=(?:[^\W_]*[\W_]){2,}).{8,}$"
8+
v-bind="$attrs"
9+
>
10+
<template #header>
11+
<small>
12+
<p class="mt-2">
13+
{{ $t("components.form.registerPasswordInput.suggestions") }}
14+
</p>
15+
<ul class="pl-2 ml-2 mt-0" style="line-height: 1.5">
16+
<li>
17+
{{
18+
$t("components.form.registerPasswordInput.suggestionsLowercase")
19+
}}
20+
</li>
21+
<li>
22+
{{
23+
$t("components.form.registerPasswordInput.suggestionsUppercase")
24+
}}
25+
</li>
26+
<li>
27+
{{
28+
$t("components.form.registerPasswordInput.suggestionsNumber")
29+
}}
30+
</li>
31+
<li>
32+
{{
33+
$t("components.form.registerPasswordInput.suggestionsSymbol")
34+
}}
35+
</li>
36+
<li>
37+
{{ $t("components.form.registerPasswordInput.suggestionsSize") }}
38+
</li>
39+
<li>
40+
{{
41+
$t("components.form.registerPasswordInput.suggestionsUnicity")
42+
}}
43+
</li>
44+
</ul>
45+
</small>
46+
<Divider />
47+
</template>
48+
</Password>
49+
<label :for="inputId">
50+
<slot>
51+
{{ $t("components.form.registerPasswordInput.password") }}
52+
</slot>
53+
</label>
54+
</span>
55+
</template>
56+
<script lang="ts" setup>
57+
defineOptions({
58+
inheritAttrs: false,
59+
});
60+
const props = defineProps<{
61+
inputId: string;
62+
}>();
63+
</script>
Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,33 @@
11
<template>
22
<h1 v-t="{ path: 'components.user.createForm.title' }"></h1>
3-
<form @submit.prevent.stop="registerUser">
4-
<UserForm
5-
v-model:email="email"
6-
v-model:password="password"
7-
v-model:password-confirm="passwordConfirm"
8-
:is-password-confirmed="isPasswordConfirmed"
9-
/>
10-
{{ errorMessage }}
11-
<button :disabled="!isPasswordConfirmed">
12-
{{ $t("components.user.createForm.ok") }}
13-
</button>
14-
</form>
3+
<UserForm class="card" @submit="submit" @cancel="navigateToList">
4+
<template #buttons="{ isValid, cancel }">
5+
<Button type="button" severity="danger" class="mr-2 mb-2" @click="cancel">
6+
{{ $t("components.user.createForm.buttonCancel") }}
7+
</Button>
8+
<Button type="submit" :disabled="!isValid" class="mr-2 mb-2">
9+
{{ $t("components.user.createForm.ok") }}
10+
</Button>
11+
</template>
12+
</UserForm>
13+
{{ errorMessage }}
1514
</template>
1615
<script setup lang="ts">
1716
import useCreateUser from "~/composables/api/user/useCreateUser";
18-
import useUser from "~/composables/user/useUser";
19-
17+
import type { UserInput } from "~/types/UserInput";
2018
const { createUser, errorMessage } = useCreateUser();
21-
const {
22-
email,
23-
password,
24-
passwordConfirm,
25-
isPasswordConfirmed,
26-
securedPassword,
27-
} = useUser();
2819
29-
const registerUser = async () => {
20+
const submit = async (state: UserInput) => {
3021
try {
31-
await createUser(email.value, securedPassword.value);
22+
await createUser(state);
3223
await navigateTo("/users");
3324
} catch (e) {
3425
logger.info(e);
3526
}
3627
};
28+
const navigateToList = () => {
29+
return navigateTo("/users/");
30+
};
3731
</script>
3832

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

apps/front/src/components/user/UserForm.vue

Lines changed: 87 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,99 @@
11
<template>
2-
<div>
3-
<label for="email">{{ $t("components.user.form.email") }}</label>
4-
<input
5-
type="text"
6-
:value="email"
7-
@input="$emit('update:email', clearInput($event))"
8-
/>
9-
</div>
10-
<div>
11-
<label for="password">{{ $t("components.user.form.password") }}</label>
12-
<input
13-
name="password"
14-
type="password"
15-
:value="password"
16-
@input="$emit('update:password', clearInput($event))"
17-
/>
18-
</div>
19-
<div>
20-
<label for="passwordConfirm">{{
21-
$t("components.user.form.passwordConfirm")
22-
}}</label>
23-
<input
24-
name="passwordConfirm"
25-
type="password"
26-
:value="passwordConfirm"
27-
@input="$emit('update:passwordConfirm', clearInput($event))"
28-
/>
29-
<span v-if="!isPasswordConfirmed" class="text-danger">
30-
{{ $t("components.user.form.errorPasswordConfirm") }}
31-
</span>
2+
<div class="grid">
3+
<div class="col-12">
4+
<form @submit.prevent.stop="submit">
5+
<div class="field col-12">
6+
<span class="p-float-label">
7+
<InputText
8+
id="user-email"
9+
v-model="state.email"
10+
type="text"
11+
:placeholder="$t('components.user.form.email')"
12+
/>
13+
<label for="user-email">{{
14+
$t("components.user.form.email")
15+
}}</label>
16+
</span>
17+
</div>
18+
<div class="field col-12">
19+
<FormRegisterPasswordInput
20+
v-model="password"
21+
input-id="user-password"
22+
/>
23+
</div>
24+
<div class="field col-12">
25+
<FormRegisterPasswordInput
26+
v-model="passwordConfirm"
27+
input-id="user-passwordConfirm"
28+
>
29+
{{ $t("components.user.form.passwordConfirm") }}
30+
</FormRegisterPasswordInput>
31+
</div>
32+
<div v-show="!isPasswordConfirmed">
33+
{{ $t("components.user.form.errorPasswordConfirm") }}
34+
</div>
35+
<div>
36+
<slot name="buttons" :is-valid="isValid" :cancel="cancel">
37+
<Button
38+
type="button"
39+
severity="danger"
40+
class="mr-2 mb-2"
41+
@click="cancel"
42+
>
43+
{{ $t("components.user.form.buttonCancel") }}
44+
</Button>
45+
<Button type="submit" :disabled="!isValid" class="mr-2 mb-2">
46+
{{ $t("components.user.form.ok") }}
47+
</Button>
48+
</slot>
49+
</div>
50+
</form>
51+
</div>
3252
</div>
3353
</template>
3454
<script setup lang="ts">
3555
import type { User } from "~/types/User";
3656
37-
defineProps<
38-
Omit<User, "id"> & {
39-
isPasswordConfirmed: boolean;
40-
password: string;
41-
passwordConfirm: string;
42-
}
43-
>();
44-
interface EventEmitter {
45-
(e: "update:email", email: string): void;
46-
(e: "update:password", password: string): void;
47-
(e: "update:passwordConfirm", passwordConfirm: string): void;
57+
interface Props {
58+
defaultValue: Omit<User, "id">;
4859
}
60+
const props = withDefaults(defineProps<Props>(), {
61+
defaultValue() {
62+
return {
63+
email: "",
64+
};
65+
},
66+
});
67+
const state = reactive({ ...props.defaultValue });
68+
const password = ref("");
69+
const passwordConfirm = ref("");
70+
71+
const isPasswordConfirmed = computed(
72+
() => password.value === passwordConfirm.value
73+
);
4974
50-
defineEmits<EventEmitter>();
75+
const isPasswordEmpty = computed(() => !password.value);
76+
const securedPassword = computed(() =>
77+
isPasswordConfirmed && isPasswordEmpty ? password.value : ""
78+
);
79+
const isValid = isPasswordConfirmed;
5180
52-
const clearInput = (inputEvent: Event) => {
53-
return (inputEvent.target as HTMLInputElement)?.value || "";
81+
interface EventEmitter {
82+
(
83+
e: "submit",
84+
value: Omit<User, "id"> & {
85+
password: string;
86+
}
87+
): void;
88+
(e: "cancel"): void;
89+
}
90+
91+
const emits = defineEmits<EventEmitter>();
92+
const submit = () => {
93+
emits("submit", { ...state, password: securedPassword.value });
94+
};
95+
const cancel = () => {
96+
emits("cancel");
5497
};
5598
</script>
5699

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
<template>
22
<div
3-
v-show="userPending"
3+
v-show="pendingData"
44
v-t="{ path: 'components.user.updateForm.pending' }"
55
></div>
6-
<form v-if="data" @submit.prevent.stop="updateUser">
7-
<h1 v-t="{ path: 'components.user.updateForm.title', args: data }"></h1>
8-
<UserForm
9-
v-model:email="email"
10-
v-model:password="password"
11-
v-model:password-confirm="passwordConfirm"
12-
:is-password-confirmed="isPasswordConfirmed"
13-
/>
14-
<Button type="submit" :disabled="!isPasswordConfirmed">{{
15-
$t("components.user.updateForm.ok")
16-
}}</Button>
17-
</form>
6+
<h1 v-t="{ path: 'components.user.updateForm.title', args: data }"></h1>
7+
<UserForm :default-value="data" @submit="submit" @cancel="navigateToList">
8+
</UserForm>
189
<div>
1910
{{ errorMessage }}
2011
{{ error }}
@@ -24,39 +15,30 @@
2415
<script setup lang="ts">
2516
import useGetUser from "~/composables/api/user/useGetUser";
2617
import useUpdateUser from "~/composables/api/user/useUpdateUser";
27-
import useUser from "~/composables/user/useUser";
18+
import type { UserInput } from "~/types/UserInput";
2819
29-
const props = defineProps<{
20+
interface Props {
3021
userId: string;
31-
}>();
22+
}
23+
24+
const props = defineProps<Props>();
3225
3326
const { errorMessage, updateUser: updateUserApi } = useUpdateUser();
3427
3528
const {
3629
data,
3730
error,
38-
pending: userPending,
39-
refresh: userRefresh,
31+
pending: pendingData,
4032
} = await useGetUser(props.userId as string);
4133
42-
const {
43-
email,
44-
password,
45-
passwordConfirm,
46-
isPasswordConfirmed,
47-
securedPassword,
48-
} = useUser(data);
49-
const updateUser = async () => {
50-
await updateUserApi(
51-
data.value.id,
52-
{
53-
email: email.value,
54-
},
55-
securedPassword.value
56-
);
57-
userRefresh();
58-
await navigateTo("/users");
34+
const submit = async (value: UserInput) => {
35+
// If you need to copy the value, create a clone so it does not track reactivity
36+
//data.value = {...data.value, ...value};
37+
await updateUserApi(parseInt(props.userId), value);
38+
return navigateTo("/users/");
39+
};
40+
const navigateToList = () => {
41+
return navigateTo("/users/");
5942
};
6043
</script>
61-
6244
<style scoped lang="scss"></style>

0 commit comments

Comments
 (0)