add usermamagement page
This commit is contained in:
8
frontend/src/routes/auth/admin/users/+layout.js
Normal file
8
frontend/src/routes/auth/admin/users/+layout.js
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
56
frontend/src/routes/auth/admin/users/+layout.server.js
Normal file
56
frontend/src/routes/auth/admin/users/+layout.server.js
Normal 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=/");
|
||||||
|
}
|
||||||
|
}
|
||||||
122
frontend/src/routes/auth/admin/users/+page.server.js
Normal file
122
frontend/src/routes/auth/admin/users/+page.server.js
Normal 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}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
92
frontend/src/routes/auth/admin/users/+page.svelte
Normal file
92
frontend/src/routes/auth/admin/users/+page.svelte
Normal 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>
|
||||||
Reference in New Issue
Block a user