Skip to content

Commit

Permalink
Add external links
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurentTreguier committed Oct 7, 2024
1 parent ebab2ea commit 7d887d8
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 74 deletions.
5 changes: 3 additions & 2 deletions src/lib/components/inputs/image-picker.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { t } from 'i18next';
export let title: string;
const dispatch = createEventDispatcher();
let input: HTMLInputElement;
Expand Down Expand Up @@ -29,7 +30,7 @@
</script>

<label
title={t('components.image-picker')}
{title}
class="image-picker"
on:dragover|preventDefault={onDragOver}
on:drop|preventDefault={onDrop}
Expand Down
71 changes: 47 additions & 24 deletions src/lib/components/list.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
</script>

<table class="list" class:borderless>
<thead>
<slot name="header" />
</thead>
<tbody>
<slot />
<slot name="body" />
</tbody>
</table>

Expand Down Expand Up @@ -34,43 +37,63 @@
border-radius: 1em;
border-spacing: 0;
&:not(.borderless) {
@include borders;
}
@include expanded-width {
width: unset;
min-width: 600px;
@include borders;
}
:global(tr:not(:last-child) td) {
border-bottom: 2px solid var(--color-border);
}
thead {
font-weight: bold;
:global(tr:first-child td:first-child) {
border-top-left-radius: 1em;
:global(td) {
padding: 1em 1em;
}
}
:global(tr:first-child td:last-child) {
border-top-right-radius: 1em;
}
&.borderless thead :global(td) {
border-bottom: 1px solid var(--color-border);
:global(tr:last-child td:first-child) {
border-bottom-left-radius: 1em;
@include expanded-width {
border-bottom: unset;
}
}
:global(tr:last-child td:last-child) {
border-bottom-right-radius: 1em;
&:not(.borderless) tbody {
@include borders;
}
:global(td) {
padding: 1em;
}
tbody {
@include expanded-width {
@include borders;
}
:global(tr:not(:last-child) td) {
border-bottom: 1px solid var(--color-border);
}
:global(tr:first-child td:first-child) {
border-top-left-radius: 1em;
}
:global(tr:first-child td:last-child) {
border-top-right-radius: 1em;
}
:global(tr:last-child td:first-child) {
border-bottom-left-radius: 1em;
}
:global(tr:last-child td:last-child) {
border-bottom-right-radius: 1em;
}
:global(td) {
padding: 1em;
}
:global(td:last-child) {
text-align: right;
:global(td:last-child:not(:first-child)) {
text-align: right;
}
}
}
</style>
25 changes: 17 additions & 8 deletions src/lib/data/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,23 @@
"register": "Sign up"
},
"settings": {
"avatar": {
"change": "Change avatar",
"remove": "Remove avatar"
"profile": {
"header": "Profile",
"avatar": {
"change": "Change avatar",
"remove": "Remove avatar"
},
"username": "Username",
"dateJoined": "Date joined",
"logout": "Logout"
},
"about": {
"header": "About",
"website": "Website",
"termsOfService": "Terms of service",
"privacyPolicy": "Privacy policy",
"sourceCode": "Source code"
},
"username": "Username",
"dateJoined": "Date joined",
"logout": "Logout",
"errors": {
"413": {
"title": "File too large",
Expand Down Expand Up @@ -128,8 +138,7 @@
}
},
"components": {
"avatar": "Avatar",
"image-picker": "Image picker"
"avatar": "Avatar"
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/data/urls.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"info": {
"website": "https://fyreplace.net",
"termsOfService": "https://fyreplace.net/terms-of-service",
"privacyPolicy": "https://fyreplace.net/privacy-policy"
"privacyPolicy": "https://fyreplace.net/privacy-policy",
"sourceCode": "https://github.com/fyreplace"
}
}
112 changes: 78 additions & 34 deletions src/routes/settings/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
import { t } from 'i18next';
import { info } from '$lib/data/urls.json';
import { navigate, Destination } from '$lib/destinations';
import { DisplayableError } from '$lib/events';
import { call, getUsersClient } from '$lib/openapi';
Expand Down Expand Up @@ -91,40 +92,77 @@
{#if $token}
<div class="destination">
<List borderless>
<tr>
<td>
<div class="avatar-wrapper">
<EditableAvatar user={currentUser} on:file={updateAvatar} />
</div>
</td>
<td>
<Button
type="button"
disabled={!currentUser?.avatar}
loading={isAvatarLoading}
on:click={removeAvatar}
>
{t('settings.avatar.remove')}
</Button>
</td>
</tr>
<tr>
<td>{t('settings.username')}</td>
<td title={t('settings.username')} class="username">
{currentUser?.username ?? t('loading')}
</td>
</tr>
<tr>
<td>{t('settings.dateJoined')}</td>
<td title={t('settings.dateJoined')}>
{currentUser?.dateCreated.toLocaleString() ?? t('loading')}
</td>
</tr>
<tr>
<td colspan="2">
<Button type="button" on:click={logout}>{t('settings.logout')}</Button>
</td>
</tr>
<svelte:fragment slot="header">
<tr>
<td colspan="2">{t('settings.profile.header')}</td>
</tr>
</svelte:fragment>
<svelte:fragment slot="body">
<tr>
<td>
<div class="avatar-wrapper">
<EditableAvatar user={currentUser} on:file={updateAvatar} />
</div>
</td>
<td>
<Button
type="button"
disabled={!currentUser?.avatar}
loading={isAvatarLoading}
on:click={removeAvatar}
>
{t('settings.profile.avatar.remove')}
</Button>
</td>
</tr>
<tr>
<td>{t('settings.profile.username')}</td>
<td title={t('settings.profile.username')} class="username">
{currentUser?.username ?? t('loading')}
</td>
</tr>
<tr>
<td>{t('settings.profile.dateJoined')}</td>
<td title={t('settings.profile.dateJoined')}>
{currentUser?.dateCreated.toLocaleString() ?? t('loading')}
</td>
</tr>
<tr>
<td colspan="2" class="logout">
<Button type="button" on:click={logout}>{t('settings.profile.logout')}</Button>
</td>
</tr>
</svelte:fragment>
</List>

<List borderless>
<svelte:fragment slot="header">
<tr>
<td colspan="2">{t('settings.about.header')}</td>
</tr>
</svelte:fragment>
<svelte:fragment slot="body">
<tr>
<td>
<a href={info.website} target="_blank">{t('settings.about.website')}</a>
</td>
</tr>
<tr>
<td>
<a href={info.termsOfService} target="_blank">{t('settings.about.termsOfService')}</a>
</td>
</tr>
<tr>
<td>
<a href={info.privacyPolicy} target="_blank">{t('settings.about.privacyPolicy')}</a>
</td>
</tr>
<tr>
<td>
<a href={info.sourceCode} target="_blank">{t('settings.about.sourceCode')}</a>
</td>
</tr>
</svelte:fragment>
</List>
</div>
{/if}
Expand All @@ -136,8 +174,10 @@
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2em;
@include expanded-width {
padding: 2em;
Expand All @@ -155,4 +195,8 @@
.username {
font-weight: bold;
}
.logout {
text-align: center;
}
</style>
3 changes: 2 additions & 1 deletion src/routes/settings/editable-avatar.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { t } from 'i18next';
import type { User } from '$lib/openapi/generated';
import Avatar from '$lib/components/avatar.svelte';
import ImagePicker from '$lib/components/inputs/image-picker.svelte';
Expand All @@ -8,7 +9,7 @@
export let user: User | null = null;
</script>

<ImagePicker on:file>
<ImagePicker title={t('settings.profile.avatar.change')} on:file>
<Avatar {user} size={100} />
<span class="edit-icon">
<Icon size="32px"><EditIcon /></Icon>
Expand Down
8 changes: 4 additions & 4 deletions src/routes/settings/page.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test('Updating avatar with too large image produces a failure', async () => {
const user = userEvent.setup();
const bus = eventBus as StoringEventBus;
render(Page);
const imagePicker = screen.getByTitle('Image picker');
const imagePicker = screen.getByTitle('Change avatar');

await user.upload(imagePicker, FakeUsersEndpointApi.largeImageFile);
const avatar = screen.queryByTitle<HTMLImageElement>('Avatar');
Expand All @@ -38,7 +38,7 @@ test('Updating avatar with invalid image produces a failure', async () => {
const user = userEvent.setup();
const bus = eventBus as StoringEventBus;
render(Page);
const imagePicker = screen.getByTitle('Image picker');
const imagePicker = screen.getByTitle('Change avatar');

await user.upload(imagePicker, FakeUsersEndpointApi.notImageFile);
const avatar = screen.queryByTitle<HTMLImageElement>('Avatar');
Expand All @@ -49,7 +49,7 @@ test('Updating avatar with invalid image produces a failure', async () => {
test('Updating avatar with valid image produces no failures', async () => {
const user = userEvent.setup();
render(Page);
const imagePicker = screen.getByTitle('Image picker');
const imagePicker = screen.getByTitle('Change avatar');

await user.upload(imagePicker, FakeUsersEndpointApi.normalImageFile);
const avatar = screen.getByTitle<HTMLImageElement>('Avatar');
Expand All @@ -59,7 +59,7 @@ test('Updating avatar with valid image produces no failures', async () => {
test('Removing avatar produces no failures', async () => {
const user = userEvent.setup();
render(Page);
const imagePicker = screen.getByTitle('Image picker');
const imagePicker = screen.getByTitle('Change avatar');
const remove = screen.getByRole('button', { name: 'Remove avatar' });
await user.upload(imagePicker, FakeUsersEndpointApi.normalImageFile);

Expand Down

0 comments on commit 7d887d8

Please sign in to comment.