feat: paginate member list, add create member button

This commit is contained in:
Sam 2023-03-14 16:43:31 +01:00
parent 9bfabcc1f1
commit 3678f5a3e8
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
6 changed files with 120 additions and 34 deletions

View 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>

View file

@ -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>

View file

@ -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...

View file

@ -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}

View file

@ -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>

View file

@ -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>