feat: paginate member list, add create member button
This commit is contained in:
parent
9bfabcc1f1
commit
3678f5a3e8
6 changed files with 120 additions and 34 deletions
15
frontend/src/lib/components/ErrorAlert.svelte
Normal file
15
frontend/src/lib/components/ErrorAlert.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { APIError } from "$lib/api/entities";
|
||||
import { Alert } from "sveltestrap";
|
||||
|
||||
export let error: APIError;
|
||||
</script>
|
||||
|
||||
<Alert color="danger" fade={false}>
|
||||
<h4 class="alert-heading">An error occurred</h4>
|
||||
<b>{error.code}:</b>
|
||||
{error.message}
|
||||
{#if error.details}
|
||||
({error.details})
|
||||
{/if}
|
||||
</Alert>
|
|
@ -4,15 +4,34 @@
|
|||
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
import { Alert, Icon } from "sveltestrap";
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Icon,
|
||||
Input,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
} from "sveltestrap";
|
||||
import FieldCard from "$lib/components/FieldCard.svelte";
|
||||
import StatusIcon from "$lib/components/StatusIcon.svelte";
|
||||
import PronounLink from "$lib/components/PronounLink.svelte";
|
||||
import PartialMemberCard from "$lib/components/PartialMemberCard.svelte";
|
||||
import FallbackImage from "$lib/components/FallbackImage.svelte";
|
||||
import { userStore } from "$lib/store";
|
||||
import { pronounDisplay, userAvatars, WordStatus, type Member } from "$lib/api/entities";
|
||||
import {
|
||||
MAX_MEMBERS,
|
||||
pronounDisplay,
|
||||
userAvatars,
|
||||
WordStatus,
|
||||
type APIError,
|
||||
type Member,
|
||||
type PartialMember,
|
||||
} from "$lib/api/entities";
|
||||
import { PUBLIC_BASE_URL } from "$env/static/public";
|
||||
import { apiFetchClient } from "$lib/api/fetch";
|
||||
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
|
@ -20,8 +39,44 @@
|
|||
$: bio = data.bio ? sanitizeHtml(marked.parse(data.bio)) : null;
|
||||
|
||||
let memberPage: number = 0;
|
||||
let memberSlice: Member[] = [];
|
||||
$: member = data.members.slice(memberPage * 20, memberPage + 1 * 20);
|
||||
let memberSlice: PartialMember[] = [];
|
||||
$: memberSlice = data.members.slice(memberPage * 20, (memberPage + 1) * 20);
|
||||
const totalPages = Math.floor(data.members.length / 20) + 1;
|
||||
|
||||
const prevPage = () => {
|
||||
if (memberPage === 0) {
|
||||
return;
|
||||
}
|
||||
memberPage = memberPage - 1;
|
||||
};
|
||||
|
||||
const nextPage = () => {
|
||||
if ((memberPage + 1) * 20 > data.members.length) {
|
||||
return;
|
||||
}
|
||||
memberPage = memberPage + 1;
|
||||
};
|
||||
|
||||
let modalOpen = false;
|
||||
let toggleModal = () => (modalOpen = !modalOpen);
|
||||
let newMemberName = "";
|
||||
let newMemberError: APIError | null = null;
|
||||
|
||||
const createMember = async () => {
|
||||
try {
|
||||
const member = await apiFetchClient<Member>("/members", "POST", {
|
||||
name: newMemberName,
|
||||
});
|
||||
|
||||
newMemberName = "";
|
||||
newMemberError = null;
|
||||
data.members = [...data.members, member];
|
||||
|
||||
toggleModal();
|
||||
} catch (e) {
|
||||
newMemberError = e as APIError;
|
||||
}
|
||||
};
|
||||
|
||||
const favNames = data.names.filter((entry) => entry.status === WordStatus.Favourite);
|
||||
const favPronouns = data.pronouns.filter((entry) => entry.status === WordStatus.Favourite);
|
||||
|
@ -97,15 +152,49 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<hr />
|
||||
<h2>Members</h2>
|
||||
<h2>
|
||||
Members
|
||||
{#if $userStore && $userStore.id === data.id}
|
||||
<Button
|
||||
color="success"
|
||||
disabled={data.members.length >= MAX_MEMBERS}
|
||||
on:click={toggleModal}><Icon name="person-plus-fill" /> Create member</Button
|
||||
>
|
||||
{/if}
|
||||
{#if totalPages > 1}
|
||||
<ButtonGroup>
|
||||
<Button on:click={prevPage} disabled={memberPage === 0}
|
||||
><Icon name="chevron-left" /> Previous page</Button
|
||||
>
|
||||
<Button disabled>Page {memberPage + 1}/{totalPages}</Button>
|
||||
<Button on:click={nextPage} disabled={memberPage === totalPages - 1}
|
||||
>Next page <Icon name="chevron-right" /></Button
|
||||
>
|
||||
</ButtonGroup>
|
||||
{/if}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-4 text-center">
|
||||
{#each data.members as member}
|
||||
{#each memberSlice as member}
|
||||
<PartialMemberCard user={data} {member} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<Modal header="Create member" isOpen={modalOpen} toggle={toggleModal}>
|
||||
<ModalBody>
|
||||
<Input bind:value={newMemberName} />
|
||||
{#if newMemberError}
|
||||
<ErrorAlert error={newMemberError} />
|
||||
{/if}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="primary" on:click={createMember} disabled={newMemberName.length === 0}
|
||||
>Create member</Button
|
||||
>
|
||||
<Button color="secondary" on:click={toggleModal}>Cancel</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { apiFetch } from "$lib/api/fetch";
|
||||
import { userStore } from "$lib/store";
|
||||
import type { PageData } from "./$types";
|
||||
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||
|
||||
interface SignupResponse {
|
||||
user: MeUser;
|
||||
|
@ -74,11 +75,7 @@
|
|||
<h1>Log in with Discord</h1>
|
||||
|
||||
{#if data.error}
|
||||
<Alert color="danger" fade={false}>
|
||||
<h4 class="alert-heading">An error occurred</h4>
|
||||
<b>{data.error.code}:</b>
|
||||
{data.error.message}
|
||||
</Alert>
|
||||
<ErrorAlert error={data.error} />
|
||||
{/if}
|
||||
{#if data.ticket}
|
||||
<form on:submit|preventDefault={signupForm}>
|
||||
|
@ -126,10 +123,7 @@
|
|||
</Alert>
|
||||
{/if}
|
||||
{#if deleteError}
|
||||
<Alert color="danger" fade={false}>
|
||||
<h4 class="alert-heading">An error occurred</h4>
|
||||
<b>{deleteError.code}</b>: {deleteError.message}
|
||||
</Alert>
|
||||
<ErrorAlert error={deleteError} />
|
||||
{/if}
|
||||
{:else}
|
||||
Loading...
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
import EditableField from "../EditableField.svelte";
|
||||
import EditableName from "../EditableName.svelte";
|
||||
import EditablePronouns from "../EditablePronouns.svelte";
|
||||
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||
|
||||
const MAX_AVATAR_BYTES = 1_000_000;
|
||||
|
||||
|
@ -250,13 +251,7 @@
|
|||
</h1>
|
||||
|
||||
{#if error}
|
||||
<Alert color="danger" fade={false}>
|
||||
<h4 class="alert-header">An error occurred</h4>
|
||||
<p>
|
||||
<b>{error.code}</b>: {error.message}
|
||||
{#if error.details}{error.details}{/if}
|
||||
</p>
|
||||
</Alert>
|
||||
<ErrorAlert {error} />
|
||||
{/if}
|
||||
|
||||
{#if !$userStore}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { goto } from "$app/navigation";
|
||||
import { type MeUser, userAvatars, type APIError, MAX_MEMBERS } from "$lib/api/entities";
|
||||
import { apiFetchClient } from "$lib/api/fetch";
|
||||
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||
import FallbackImage from "$lib/components/FallbackImage.svelte";
|
||||
import { userStore } from "$lib/store";
|
||||
import {
|
||||
|
@ -77,10 +78,7 @@
|
|||
</p>
|
||||
{/if}
|
||||
{#if error}
|
||||
<Alert color="danger" fade={false}>
|
||||
<h5 class="alert-heading">An error occurred</h5>
|
||||
<b>{error.code}</b>: {error.message}
|
||||
</Alert>
|
||||
<ErrorAlert {error} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
|
@ -137,10 +135,7 @@
|
|||
<input type="text" class="form-control" bind:value={deleteUsername} />
|
||||
</p>
|
||||
{#if deleteError}
|
||||
<Alert color="danger" fade={false}>
|
||||
<h5 class="alert-heading">An error occurred</h5>
|
||||
<b>{deleteError.code}</b>: {deleteError.message}
|
||||
</Alert>
|
||||
<ErrorAlert error={deleteError} />
|
||||
{/if}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { APIError, Invite } from "$lib/api/entities";
|
||||
import { apiFetchClient } from "$lib/api/fetch";
|
||||
import ErrorAlert from "$lib/components/ErrorAlert.svelte";
|
||||
import { Alert, Button, Modal, Table } from "sveltestrap";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
|
@ -54,10 +55,7 @@
|
|||
</div>
|
||||
<div class="col-md">
|
||||
{#if error}
|
||||
<Alert color="danger" fade={false}>
|
||||
<h4 class="alert-heading">An error occurred</h4>
|
||||
<b>{error.code}</b>: {error.message}
|
||||
</Alert>
|
||||
<ErrorAlert {error} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue