add usermamagement page

This commit is contained in:
Alex
2024-10-13 13:38:30 +02:00
parent 47e4e8ce55
commit ab8d143aeb
4 changed files with 278 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
/** @type {import('./$types').LayoutLoad} */
export async function load({ fetch, url, data }) {
const { users } = data;
return {
users: data.users,
user: data.user,
};
}

View File

@@ -0,0 +1,56 @@
import { BASE_API_URI } from "$lib/utils/constants";
import { redirect } from "@sveltejs/kit";
import { userDatesFromRFC3339, refreshCookie } from "$lib/utils/helpers";
/** @type {import('./$types').LayoutServerLoad} */
export async function load({ cookies, fetch, locals }) {
// if (locals.users) {
// return {
// users: locals.users,
// user: locals.user,
// };
// }
const jwt = cookies.get("jwt");
try {
// Fetch user data, subscriptions, and licence categories in parallel
const response = await fetch(`${BASE_API_URI}/backend/users/all`, {
credentials: "include",
headers: {
Cookie: `jwt=${jwt}`,
},
});
if (!response.ok) {
// Clear the invalid JWT cookie
cookies.delete("jwt", { path: "/" });
throw redirect(302, "/auth/login?next=/");
}
const data = await response.json();
// Check if the server sent a new token
const newToken = response.headers.get("Set-Cookie");
refreshCookie(newToken, null);
/** @type {App.Locals['users']}*/
const users = data.users;
users.forEach((user) => {
userDatesFromRFC3339(user);
});
locals.users = users;
return {
subscriptions: locals.subscriptions,
licence_categories: locals.licence_categories,
users: locals.users,
user: locals.user,
};
} catch (error) {
console.error("Error fetching data:", error);
// In case of any error, clear the JWT cookie
cookies.delete("jwt", { path: "/" });
throw redirect(302, "/auth/login?next=/");
}
}

View File

@@ -0,0 +1,122 @@
// - Add authentication check to ensure only admins can access this route.
// - Implement a load function to fetch a list of all users.
// - Create actions for updating user information (similar to the about/[id] route).
import { BASE_API_URI } from "$lib/utils/constants";
import { formatError, userDatesFromRFC3339 } from "$lib/utils/helpers";
import { fail, redirect } from "@sveltejs/kit";
import { toRFC3339 } from "$lib/utils/helpers";
/** @type {import('./$types').PageServerLoad} */
export async function load({ locals, params }) {
// redirect user if not logged in
if (!locals.user) {
throw redirect(302, `/auth/login?next=/auth/users`);
}
}
/** @type {import('./$types').Actions} */
export const actions = {
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
updateUser: async ({ request, fetch, cookies, locals }) => {
let formData = await request.formData();
const licenceCategories = formData
.getAll("licence_categories[]")
.filter((value) => typeof value === "string")
.map((value) => {
try {
return JSON.parse(value);
} catch (e) {
console.error("Failed to parse licence category:", value);
return null;
}
})
.filter(Boolean);
/** @type {Partial<App.Locals['user']>} */
const updateData = {
id: Number(formData.get("id")),
first_name: String(formData.get("first_name")),
last_name: String(formData.get("last_name")),
email: String(formData.get("email")),
phone: String(formData.get("phone")),
notes: String(formData.get("notes")),
address: String(formData.get("address")),
zip_code: String(formData.get("zip_code")),
city: String(formData.get("city")),
date_of_birth: toRFC3339(formData.get("birth_date")),
company: String(formData.get("company")),
profile_picture: String(formData.get("profile_picture")),
membership: {
id: Number(formData.get("membership_id")),
start_date: toRFC3339(formData.get("membership_start_date")),
end_date: toRFC3339(formData.get("membership_end_date")),
status: Number(formData.get("membership_status")),
parent_member_id: Number(formData.get("parent_member_id")),
subscription_model: {
id: Number(formData.get("subscription_model_id")),
name: String(formData.get("subscription_model_name")),
},
},
bank_account: {
id: Number(formData.get("bank_account_id")),
mandate_date_signed: toRFC3339(
String(formData.get("mandate_date_signed"))
),
bank: String(formData.get("bank")),
account_holder_name: String(formData.get("account_holder_name")),
iban: String(formData.get("iban")),
bic: String(formData.get("bic")),
mandate_reference: String(formData.get("mandate_reference")),
},
licence: {
id: Number(formData.get("drivers_licence_id")),
status: Number(formData.get("licence_status")),
licence_number: String(formData.get("licence_number")),
issued_date: toRFC3339(formData.get("issued_date")),
expiration_date: toRFC3339(formData.get("expiration_date")),
country: String(formData.get("country")),
licence_categories: licenceCategories,
},
};
// Remove undefined or null properties
const cleanUpdateData = JSON.parse(
JSON.stringify(updateData),
(key, value) => (value !== null && value !== "" ? value : undefined)
);
console.dir(formData);
console.dir(cleanUpdateData);
const apiURL = `${BASE_API_URI}/backend/users/update/`;
/** @type {RequestInit} */
const requestUpdateOptions = {
method: "PATCH",
credentials: "include",
headers: {
"Content-Type": "application/json",
Cookie: `jwt=${cookies.get("jwt")}`,
},
body: JSON.stringify(cleanUpdateData),
};
const res = await fetch(apiURL, requestUpdateOptions);
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
const response = await res.json();
locals.user = response;
userDatesFromRFC3339(locals.user);
throw redirect(303, `/auth/about/${response.id}`);
},
};

View File

@@ -0,0 +1,92 @@
<!-- - Create a table or list view of all users.
- Implement a search or filter functionality.
- Add a modal component for editing user details (reuse the modal from about/[id]). -->
<script>
import { onMount } from "svelte";
import Modal from "$lib/components/Modal.svelte";
import UserEditForm from "$lib/components/UserEditForm.svelte";
import { t } from "svelte-i18n";
import { page } from "$app/stores";
/** @type {import('./$types').ActionData} */
export let form;
$: ({ user, users, licence_categories, subscriptions } = $page.data);
/** @type(App.Locals['user'] | null) */
let selectedUser = null;
let showModal = false;
/**
* Opens the edit modal for the selected user.
* @param {App.Locals['user']} user The user to edit.
*/
const openEditModal = (user) => {
selectedUser = user;
showModal = true;
};
/**
* Opens the delete modal for the selected user.
* @param {App.Locals['user']} user The user to edit.
*/
const openDelete = (user) => {};
const close = () => {
showModal = false;
selectedUser = null;
if (form) {
form.errors = undefined;
}
};
</script>
<div class="admin-users-page">
<h1>{$t("user.management")}</h1>
<div class="search-filter" />
<table class="user-table">
<thead>
<tr>
<th>{$t("user.id")}</th>
<th>{$t("name")}</th>
<th>{$t("email")}</th>
<th>{$t("status")}</th>
<th>{$t("actions")}</th>
</tr>
</thead>
<tbody>
{#each users as user}
<tr>
<td>{user.id}</td>
<td>{user.first_name} {user.last_name}</td>
<td>{user.email}</td>
<td>{$t("userStatus." + user.status)}</td>
<td>
<button on:click={() => openEditModal(user)}>{$t("edit")}</button>
<button on:click={() => openDelete(user)}>{$t("delete")}</button>
</td>
</tr>{/each}
</tbody>
</table>
<div class="pagination" />
{#if showModal}
<Modal on:close={close}>
<UserEditForm
{form}
user={selectedUser}
{subscriptions}
{licence_categories}
on:cancel={close}
/>
</Modal>
{/if}
</div>
<style>
</style>