Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add update employee password #213

Merged
merged 3 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/src/domain/components/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Size of an icon.
*/
export type IconSize =
| "xx-small"
| "x-small"
| "small"
| "medium"
Expand Down
2 changes: 1 addition & 1 deletion web/src/lib/clients/ecomap/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const middleware: Middleware = {
return request;
},
async onResponse(response) {
let body = await response.text();
let body = (await response.text()) || null;

switch (response.status) {
case 401:
Expand Down
26 changes: 24 additions & 2 deletions web/src/lib/components/FormControl.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import Icon from "./Icon.svelte";

/**
* A space-separated list of the classes of the element.
* @default ""
Expand All @@ -23,11 +25,22 @@
* @default null
*/
export let label: string | null = null;

/**
* The text representing advisory information related to the element it belongs to.
* @default null
*/
export let title: string | null = null;
</script>

<label class={className}>
{#if label}
{label}
<span {title} class="header">
<span class="input-label">{label}</span>
{#if title}
<Icon name="info" size="xx-small" />
{/if}
</span>
{/if}
<slot />
{#if helperText}
Expand All @@ -39,7 +52,16 @@
label {
display: flex;
flex-direction: column;
color: var(--gray-500);
}

.header {
display: flex;
align-items: center;
gap: 0.25rem;

& .input-label {
color: var(--gray-500);
}
}

.helper-text {
Expand Down
52 changes: 52 additions & 0 deletions web/src/lib/components/FormModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script lang="ts">
import type { FormEventHandler } from "svelte/elements";
import Modal from "./Modal.svelte";

/**
* Indicates if modal is open.
*/
export let open: boolean;

/**
* The modal title.
*/
export let title: string;

/**
* Callback fired when the form is submitted.
*/
export let onSubmit: FormEventHandler<HTMLFormElement>;

/**
* Callback fired when open state changes.
* @param open Modal open state.
*/
export let onOpenChange: (open: boolean) => void;

/**
* Indicates whether the modal content is automatically configured with gutters.
* @default false
*/
export let gutters: boolean = false;

/**
* The form element.
* @default null
*/
export let form: HTMLFormElement | null = null;

/**
* Callback fired when backdrop is clicked.
* @default null
*/
export let onBackdropClick: (() => void) | null = null;
</script>

<form novalidate bind:this={form} on:submit|preventDefault={onSubmit}>
<Modal {open} {gutters} {title} {onOpenChange} {onBackdropClick}>
<slot />
<svelte:fragment slot="actions">
<slot name="actions" />
</svelte:fragment>
</Modal>
</form>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from "svelte";
import IconButton from "../components/IconButton.svelte";
import IconButton from "./IconButton.svelte";

/**
* Indicates if modal is open.
Expand Down Expand Up @@ -28,7 +28,7 @@
* Callback fired when backdrop is clicked.
* @default null
*/
export let onClickOutside: (() => void) | null = null;
export let onBackdropClick: (() => void) | null = null;

/**
* Dialog element.
Expand All @@ -51,7 +51,7 @@
// Check if the element that was pressed is the dialog. If it is, it means that the click was performed outside the modal.
if (e.target === dialog) {
closeModal();
onClickOutside?.();
onBackdropClick?.();
}
}

Expand Down
4 changes: 2 additions & 2 deletions web/src/lib/components/SelectLocation.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import OlMap from "ol/Map";
import Modal from "../clients/Modal.svelte";
import Modal from "./Modal.svelte";
import Map from "./map/Map.svelte";
import VectorLayer from "ol/layer/Vector";
import { Feature, MapBrowserEvent } from "ol";
Expand Down Expand Up @@ -256,7 +256,7 @@
<Modal
{open}
{onOpenChange}
onClickOutside={removeSelectedLocation}
onBackdropClick={removeSelectedLocation}
title={$t("selectLocation")}
>
<Map bind:map mapId="select-location-map" --height="32rem" --width="60rem" />
Expand Down
15 changes: 14 additions & 1 deletion web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,18 @@
"employees.notFound.description": "The employee you are looking for does not exist.",
"employees.delete.success": "Employee deleted successfully",
"employees.delete.conflict.title": "Conflict",
"employees.delete.conflict.description": "This employee cannot be deleted because it's associated with a report or a route."
"employees.delete.conflict.description": "This employee cannot be deleted because it's associated with a report or a route.",
"employees.updatePassword.title": "Change password",
"employees.updatePassword.currentPassword.label": "Current password",
"employees.updatePassword.newPassword.label": "New password",
"employees.updatePassword.confirmPassword.label": "Confirm password",
"employees.updatePassword.currentPassword.placeholder": "Enter the current password",
"employees.updatePassword.newPassword.placeholder": "Enter the new password",
"employees.updatePassword.confirmPassword.placeholder": "Repeat the new password",
"employees.updatePassword.success": "Password changed successfully",
"employees.updatePassword.error.title": "Invalid password",
"employees.updatePassword.error.description": "The password must contain at least 14 characters, including one regular character, one special character, and one number",
"employees.error.passwordMismatch": "The password doesn't match the new password",
"employees.passwordConstraints": "Password must contain at least 14 characters, including one regular character, one special character, and one number",
"confirm": "Confirm"
}
15 changes: 14 additions & 1 deletion web/src/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,18 @@
"employees.notFound.description": "O colaborador que procura não existe.",
"employees.delete.success": "Colaborador eliminado com sucesso",
"employees.delete.conflict.title": "Conflito",
"employees.delete.conflict.description": "Este colaborador não pode ser eliminado porque está associado a um reporte ou a uma rota."
"employees.delete.conflict.description": "Este colaborador não pode ser eliminado porque está associado a um reporte ou a uma rota.",
"employees.updatePassword.title": "Alterar palavra-passe",
"employees.updatePassword.currentPassword.label": "Palavra-passe atual",
"employees.updatePassword.newPassword.label": "Nova palavra-passe",
"employees.updatePassword.confirmPassword.label": "Confirmar palavra-passe",
"employees.updatePassword.currentPassword.placeholder": "Introduza a palavra-passe atual",
"employees.updatePassword.newPassword.placeholder": "Introduza a nova palavra-passe",
"employees.updatePassword.confirmPassword.placeholder": "Repita a nova palavra-passe",
"employees.updatePassword.success": "Palavra-passe alterada com sucesso",
"employees.updatePassword.error.title": "Palavra-passe inválida",
"employees.updatePassword.error.description": "A palavra-passe deve conter pelo menos 14 caracteres, incluindo um carácter regular, um carácter especial e um número.",
"employees.error.passwordMismatch": "A palavra-passe não corresponde à nova palavra-passe",
"employees.passwordConstraints": "A palavra-passe deve conter pelo menos 14 caracteres, incluindo um carácter regular, um carácter especial e um número",
"confirm": "Confirmar"
}
39 changes: 39 additions & 0 deletions web/src/locales/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,45 @@
},
"employees.delete.conflict.description": {
"type": "string"
},
"employees.updatePassword.title": {
"type": "string"
},
"employees.updatePassword.currentPassword.label": {
"type": "string"
},
"employees.updatePassword.newPassword.label": {
"type": "string"
},
"employees.updatePassword.confirmPassword.label": {
"type": "string"
},
"employees.updatePassword.currentPassword.placeholder": {
"type": "string"
},
"employees.updatePassword.newPassword.placeholder": {
"type": "string"
},
"employees.updatePassword.confirmPassword.placeholder": {
"type": "string"
},
"employees.updatePassword.success": {
"type": "string"
},
"employees.updatePassword.error.title": {
"type": "string"
},
"employees.updatePassword.error.description": {
"type": "string"
},
"employees.error.passwordMismatch": {
"type": "string"
},
"employees.passwordConstraints": {
"type": "string"
},
"confirm": {
"type": "string"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@
* Handles the submit event of the form.
* @param e Submit event.
*/
function handleSubmit(e: SubmitEvent) {
const formData = new FormData(e.currentTarget as HTMLFormElement);
function handleSubmit(
e: Event & { currentTarget: EventTarget & HTMLFormElement },
) {
const formData = new FormData(e.currentTarget);
const category = formData.get("category") ?? "";
const location = formData.get("location") ?? "";

Expand Down
24 changes: 23 additions & 1 deletion web/src/routes/backOffice/employees/details/Employee.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,24 @@
import { getToastContext } from "../../../../lib/contexts/toast";
import { BackOfficeRoutes } from "../../../constants/routes";
import { isViewingSelf } from "../../../../lib/utils/auth";
import UpdatePasswordModal from "./UpdatePasswordModal.svelte";

/**
* Employee ID.
*/
export let id: string;

/**
* Toast context.
*/
const toast = getToastContext();

/**
* The update password open modal state.
* @default false
*/
let openUpdatePasswordModal = false;

/**
* Fetches employee data.
*/
Expand Down Expand Up @@ -92,7 +102,14 @@
)}

<DetailsHeader to="" title={`${employee.firstName} ${employee.lastName}`}>
{#if !isViewingSelf(employee.id)}
{#if isViewingSelf(employee.id)}
<Button
variant="secondary"
onClick={() => (openUpdatePasswordModal = true)}
>
{$t("employees.updatePassword.title")}
</Button>
{:else}
<Button
startIcon="delete"
actionType="danger"
Expand Down Expand Up @@ -144,6 +161,11 @@
</DetailsFields>
</DetailsSection>
</DetailsContent>
<UpdatePasswordModal
{employee}
open={openUpdatePasswordModal}
onOpenChange={open => (openUpdatePasswordModal = open)}
/>
{:catch}
<div class="employee-not-found">
<h2>{$t("employees.notFound.title")}</h2>
Expand Down
Loading