Skip to content

Commit

Permalink
Add a profile page
Browse files Browse the repository at this point in the history
  • Loading branch information
Dlurak committed Aug 27, 2024
1 parent 3633f76 commit 1df3fc1
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 9 deletions.
14 changes: 14 additions & 0 deletions src/lib/components/profile/TableRow.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts">
import { Icon, type IconSource } from 'svelte-hero-icons';
export let icon: IconSource;
</script>

<tr>
<td class="px-2">
<Icon src={icon} class="h-6 w-6" small />
</td>
<td>
<slot />
</td>
</tr>
9 changes: 6 additions & 3 deletions src/lib/components/settings/timetable/TimetableCell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import TextInput from '$lib/components/input/Text.svelte';
import { i } from '$lib/i18n/store';
import { asyncRequestAnimationFrame } from '$lib/utils/dom';
import { safeMap } from '$lib/utils/null/safeMap';
import { svocal } from '$lib/utils/store/svocal';
import type { TimetableWeekday } from './types';
import { addRow, countMaxLessons, getLastLessons } from './utils';
Expand All @@ -28,9 +29,11 @@
$timetable[day][lessonIndex] = detail;
}}
on:enter={async () => {
const isOnlyNull = getLastLessons($timetable).every((x) => x === null);
const hasLessons = countMaxLessons($timetable) > 0;
if (!(isOnlyNull && hasLessons)) {
const lastSessions = getLastLessons($timetable);
const lastRowHasContent = lastSessions.some((i) => safeMap(i, (str) => str.trim()));
const lastRowIsFocused = countMaxLessons($timetable) <= lessonIndex + 1;

if (lastRowHasContent && lastRowIsFocused) {
timetable.update(addRow);
await asyncRequestAnimationFrame();
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/settings/timetable/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { fromEntries, objectEntries } from '$lib/utils/objects/entries';
import { self } from '$lib/utils/utils';

export function countMaxLessons(timetable: Timetable) {
const lessonCounts = Object.values(timetable).map((x) => x.length);
const lessonCountsPerDay = Object.values(timetable).map((x) => x.length);

return Math.max(...lessonCounts);
return Math.max(...lessonCountsPerDay);
}

export function addRow(timetable: Timetable): Timetable {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/constants/footer/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export const links: LinkGroup[] = [
name: i('nav.footer.auth.join'),
href: '/join',
icon: RectangleGroup
},
{
name: i('nav.footer.auth.profile'),
href: '/profile',
icon: User
}
]
},
Expand Down
10 changes: 10 additions & 0 deletions src/lib/constants/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ export const launcherItems: LauncherItem[] = [
},
searchTerms: split(i('launcher.register.terms'))
},
{
label: i('launcher.profile'),
description: null,
icon: User,
callback: () => {
goto('/profile');
closeLauncher();
},
searchTerms: split(i('launcher.profile.terms'))
},
{
label: i('launcher.join'),
description: null,
Expand Down
21 changes: 20 additions & 1 deletion src/lib/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const de = {
'launcher.homework.terms': 'Aufgaben\nArbeitsauftrag',
'launcher.register': 'Registrieren',
'launcher.register.terms': 'Account',
'launcher.profile': 'Dein Profil',
'launcher.profile.terms': 'Profil\nDetails\nAccount\nNutzer',
'launcher.join': 'Einer Klasse beitreten',
'launcher.join.terms': 'Klasse\nbeitreten\nKurs\nGruppe\nhinzufügen',
'launcher.mod.own': 'Eigene Anfragen',
Expand Down Expand Up @@ -104,6 +106,7 @@ const de = {
'nav.footer.auth.login': 'Einloggen',
'nav.footer.auth.register': 'Registrieren',
'nav.footer.auth.join': 'Klasse beitreten',
'nav.footer.auth.profile': 'Dein Profil',
'nav.footer.notes': 'Notizen',
'nav.footer.notes.notes': 'Notizen',
'nav.footer.calendar': 'Kalender',
Expand Down Expand Up @@ -683,12 +686,28 @@ Deine bisherigen Einstellungen sind leider nicht mit der neuen Version kompatibe
'keyboardshortcuts.markdown.bold': 'Fett',
'keyboardshortcuts.markdown.italic': 'Kursiv',
'keyboardshortcuts.markdown.link': 'Link',
'keyboardshortcuts.markdown.heading': 'Überscirft',
'keyboardshortcuts.markdown.heading': 'Überschrift',

'markdownEditor.views.edit': 'Bearbeiten',
'markdownEditor.views.preview': 'Vorschau',
'markdownpreview.noPreview': 'Gebe etwas ein, um eine Vorschau sehen zu können.',

'profile.logInRequired': 'Für diese Funktion musst du dich einloggen.',
'profile.logInRequired.link': 'Klicke hier',
'profile.title': 'Dein Profil',
'profile.classes': 'Klassen',
'profile.classes.count': {
counts: {
default: '$count Klassen',
0: 'Keine Klassen',
1: 'Eine Klasse',
2: 'Two Klassen'
}
},
'profile.registered': 'Am $date registriert',
'profile.links.reqs': 'Deine Beitritts-Anfragen',
'profile.links.settings': 'Deine Profil Einstellungen',

literal: '$literal'
} as const satisfies I18nDict;

Expand Down
19 changes: 19 additions & 0 deletions src/lib/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const en = {
'launcher.homework.terms': 'Task\nHomework',
'launcher.register': 'Register',
'launcher.register.terms': 'Account',
'launcher.profile': 'Your Profile',
'launcher.profile.terms': 'Profile\nDetails\nAccount',
'launcher.join': 'Join a class',
'launcher.join.terms': 'Class\nJoin\nCourse\nGroup\nAdd',
'launcher.mod.own': 'Own join requests',
Expand Down Expand Up @@ -101,6 +103,7 @@ const en = {
'nav.footer.auth.login': 'Login',
'nav.footer.auth.register': 'Register',
'nav.footer.auth.join': 'Join class',
'nav.footer.auth.profile': 'Your Profile',
'nav.footer.notes': 'Notes',
'nav.footer.notes.notes': 'Notes',
'nav.footer.calendar': 'Calendar',
Expand Down Expand Up @@ -677,6 +680,22 @@ Your current settings sadly won't be compatible withthe new version. But you can
'markdownEditor.views.preview': 'Preview',
'markdownpreview.noPreview': 'To show a preview some input is required',

'profile.logInRequired': 'For this function you first need to log in.',
'profile.logInRequired.link': 'Click here',
'profile.title': 'Your Profile',
'profile.classes': 'Classes',
'profile.classes.count': {
counts: {
default: '$count classes',
0: 'No classes',
1: 'One class',
2: 'Two classes'
}
},
'profile.registered': 'Registered on the $date',
'profile.links.reqs': 'Your Join-Requests',
'profile.links.settings': 'Your Profile Settings',

literal: '$literal'
} as const satisfies I18nDict;

Expand Down
2 changes: 1 addition & 1 deletion src/lib/utils/url/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('object to query params', () => {
expect(objToQueryParams({ key: 'value', key2: 'value2' })).toBe('key=value&key2=value2');
expect(objToQueryParams({ key: 'value', key2: null })).toBe('key=value');
});
it('converrts a Record<string, Record<string, unknown>>', () => {
it('converts a Record<string, Record<string, unknown>>', () => {
const obj = { key: { key: { key: { key: [4, 2.42, { key: { value: 'hi' } }] } } } };

expect(objToQueryParams({ key: obj, key2: 'value2' })).toBe(
Expand Down
4 changes: 2 additions & 2 deletions src/routes/join/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@
'Joined class successfully!': i('toast.join.joined')
}[res.message];

ownUserInfo().then((d) => {
svocal('dlool.ownUserDetails').set(d.data);
ownUserInfo().then(({ data }) => {
svocal('dlool.ownUserDetails').set(data);
});

sendToast({
Expand Down
120 changes: 120 additions & 0 deletions src/routes/profile/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<script>
import { browser } from '$app/environment';
import TableRow from '$lib/components/profile/TableRow.svelte';
import MetaData from '$lib/components/utils/MetaData.svelte';
import Store from '$lib/components/utils/Store.svelte';
import { ownUserInfo } from '$lib/dlool/userInfo/own';
import { i } from '$lib/i18n/store';
import { currentLang } from '$lib/stores';
import { sortByPredicate } from '$lib/utils/arrays/sort';
import { smartSubject } from '$lib/utils/dlool/smartSubject';
import { getSubjectIcon } from '$lib/utils/icons/subjectIcons';
import { safeMap } from '$lib/utils/null/safeMap';
import { retry } from '$lib/utils/promises/retry';
import { useAuth } from '$lib/utils/store/auth';
import { svocal } from '$lib/utils/store/svocal';
import { onDestroy, onMount } from 'svelte';
import { AcademicCap, BuildingLibrary, Clock, User, Icon } from 'svelte-hero-icons';
const { userDetails, isLoggedIn } = useAuth();
const ownUserDetailsSvocal = svocal('dlool.ownUserDetails');
const refreshData = async () => {
const { data } = await ownUserInfo();
ownUserDetailsSvocal.set(data);
};
onMount(() => {
if (!browser) return;
retry(refreshData, 3).catch(() => {});
});
onDestroy(() => {
if (!browser) return;
retry(refreshData, 3).catch(() => {});
});
</script>

<MetaData title={i('profile.title')} />

{#if $userDetails && $isLoggedIn}
{@const ud = $userDetails}
{@const school = ud.classes.at(0)?.school.name}

<div class="grid w-full grid-cols-1 gap-3 sm:grid-cols-[1fr_2fr] sm:gap-5 md:grid-cols-[1fr_3fr]">
<div class="flex justify-center">
<div class="flex flex-col items-center gap-2 px-3">
<div
class="flex aspect-square items-center justify-center rounded-full bg-zinc-300 p-5 shadow"
>
<Icon src={User} class="w-16" small />
</div>

<h2>{ud.displayname}</h2>
<span>{ud.username}</span>
<hr class="w-full border-zinc-300 dark:border-zinc-700" />
<table>
<tbody>
{#if school}
<TableRow icon={BuildingLibrary}>
{school}
</TableRow>
{/if}
<TableRow icon={AcademicCap}>
<Store
store={i(
'profile.classes.count',
{ count: ud.classes.length },
{ count: ud.classes.length }
)}
/>
</TableRow>
<TableRow icon={Clock}>
<Store
store={i('profile.registered', {
date: new Date(ud.created).toLocaleDateString($currentLang)
})}
/>
</TableRow>
</tbody>
</table>
<hr class="w-full border-zinc-300 dark:border-zinc-700" />
<a href="/moderation/own" class="w-full">
<Store store={i('profile.links.reqs')} />
</a>
<a href="/settings/profile" class="w-full">
<Store store={i('profile.links.settings')} />
</a>
</div>
</div>
<div>
<h3>
<Store store={i('profile.classes')} />
</h3>
<ul class="grid grid-cols-[repeat(auto-fill,minmax(min(16rem,100%),1fr))] gap-2">
{#each sortByPredicate(ud.classes, ({ name }) => name) as { name: className }}
{@const subject = smartSubject(className)}
{@const icon = safeMap(subject, getSubjectIcon)}
<li
class="flex items-center gap-2 rounded px-2 py-1 outline outline-2 outline-zinc-400 dark:outline-zinc-700"
>
{#if icon}
<Icon src={icon} class="h-8 w-8" mini />
<div class="h-full w-0.5 rounded-full bg-zinc-300 dark:bg-zinc-700" />
{/if}
{className}
</li>
{:else}
<span>
<Store store={i('profile.classes.count', { count: 0 }, { count: 0 })} />
</span>
{/each}
</ul>
</div>
</div>
{:else}
<Store store={i('profile.logInRequired')} />
<a href="/login?redirect=/profile">
<Store store={i('profile.logInRequired.link')} />
</a>
{/if}

0 comments on commit 1df3fc1

Please sign in to comment.