Skip to content
Snippets Groups Projects
Verified Commit 142c1579 authored by Miniontoby's avatar Miniontoby :writing_hand_tone1:
Browse files

Added Profile page

- Added profile page
- Added navbar profile dropdown
- Moved themeswitcher to profile dropdown
- Changed the urls to use resolveRoute for supporting running in subfolder...
parent 6de78747
No related branches found
No related tags found
No related merge requests found
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
import Sun from './icons/Sun.svelte'; import Sun from './icons/Sun.svelte';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { resolveRoute } from '$app/paths';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import { sha256 } from 'js-sha256';
/** @type {string|undefined} */ /** @type {string|undefined} */
let currentTheme; let currentTheme;
...@@ -47,39 +49,63 @@ ...@@ -47,39 +49,63 @@
</script> </script>
<nav class="bg-gray-200 dark:bg-opacity-10 dark:bg-gray-200"> <nav class="bg-gray-200 dark:bg-opacity-10 dark:bg-gray-200">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"> <div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a href="/" class="flex items-center space-x-3 rtl:space-x-reverse"> <a href={resolveRoute('/')} class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="/favicon.png" class="h-8" alt="PraiseLink Logo" /> <img src={resolveRoute('/favicon.png')} class="h-8" alt={$_('app.header.logo_alt')} />
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">{$_('app.title')}</span> <span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">{$_('app.title')}</span>
</a> </a>
<button type="button" on:click={() => toggleNavbar('navbar-default')} aria-hidden="true" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"> <div class="flex items-center md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
<span class="sr-only">Open main menu</span> {#if authUser}
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14"> {@const email = sha256(authUser.email)}
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/> <button type="button" on:click={() => toggleNavbar('user-dropdown')} aria-expanded="false" data-dropdown-toggle="user-dropdown" data-dropdown-placement="bottom" class="flex text-sm bg-gray-800 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600" id="user-menu-button">
</svg> <span class="sr-only">{$_('app.header.open_user_menu')}</span>
</button> <img class="w-8 h-8 rounded-full" src={'https://www.gravatar.com/avatar/' + email} alt={$_('app.avatar_alt')}>
</button>
<!-- Dropdown menu -->
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-dropdown" style="position: absolute; right: 0; top: 70px; ">
<div class="px-4 py-3">
<span class="block text-sm text-gray-900 dark:text-white">{authUser.name}</span>
<span class="block text-sm text-gray-500 truncate dark:text-gray-400">{authUser.email}</span>
</div>
<ul class="py-2" aria-labelledby="user-menu-button">
<li>
<a href={resolveRoute('/account')} title={$_('navbar.account_settings')} on:click={() => toggleNavbar('user-dropdown')}>{$_('navbar.account_settings')}</a>
</li>
<li>
{#if currentTheme == "light"}
<a href={"#"} on:click={() => setTheme("dark")} title="Set Darkmode" class="moon">
<Moon />
</a>
{:else}
<a href={"#"} on:click={() => setTheme("light")} title="Set Lightmode" class="sun">
<Sun />
</a>
{/if}
</li>
<li>
<a href={resolveRoute('/logout')} title={$_('navbar.logout')} data-sveltekit-reload={true}>{$_('navbar.logout')}</a>
</li>
</ul>
</div>
{/if}
<button type="button" on:click={() => toggleNavbar('navbar-default')} data-collapse-toggle="navbar-default" aria-controls="navbar-default" aria-expanded="false" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
<span class="sr-only">{$_('app.header.open_main_menu')}</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
</svg>
</button>
</div>
<div class="hidden w-full md:block md:w-auto" id="navbar-default"> <div class="hidden w-full md:block md:w-auto" id="navbar-default">
<ul class="links flex flex-col p-4 md:p-0 mt-4 md:flex-row md:space-x-8 rtl:space-x-reverse"> <ul class="links flex flex-col p-4 md:p-0 mt-4 md:flex-row md:space-x-8 rtl:space-x-reverse">
{#each config.navs as link} {#each config.navs as link}
{#if (!link.ifLoggedOut && !link.hasRole) || (link.ifLoggedOut && !authUser) || (link.hasRole && authUser && (link.hasRole.length == 0 || link.hasRole.includes(authUser.roleId)))} {#if (!link.ifLoggedOut && !link.hasRole) || (link.ifLoggedOut && !authUser) || (link.hasRole && authUser && (link.hasRole.length == 0 || link.hasRole.includes(authUser.roleId)))}
<li class="py-2 px-3"> <li class="py-2 px-3">
<a href={link.href} title={$_('navbar.' + link.title)} <a href={resolveRoute(link.href)} title={$_('navbar.' + link.title)}
class="hover:text-black dark:hover:text-white" class="hover:text-black dark:hover:text-white"
class:active={link.href === "/" ? routeId === "/" : url.includes(link.href)} class:active={link.href === '/' ? routeId === resolveRoute('/') : url.includes(link.href)}
data-sveltekit-reload={link.href == '/logout'}>{$_('navbar.' + link.title)}</a> >{$_('navbar.' + link.title)}</a>
</li> </li>
{/if} {/if}
{/each} {/each}
<li class="relative py-2 px-3">
{#if currentTheme == "light"}
<a href={"#"} title="Set Darkmode" class="moon" on:click={() => setTheme("dark")}>
<Moon />
</a>
{:else}
<a href={"#"} title="Set Lightmode" class="sun" on:click={() => setTheme("light")}>
<Sun />
</a>
{/if}
</li>
</ul> </ul>
</div> </div>
</div> </div>
...@@ -101,4 +127,8 @@ ...@@ -101,4 +127,8 @@
color: var(--fg-1); color: var(--fg-1);
border-bottom: 2px solid; border-bottom: 2px solid;
} }
#user-dropdown > ul > li > a {
@apply block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white;
}
</style> </style>
\ No newline at end of file
...@@ -18,6 +18,11 @@ export const navs = [ ...@@ -18,6 +18,11 @@ export const navs = [
href: "/songs", href: "/songs",
hasRole: [Roles.Admin, Roles.User], hasRole: [Roles.Admin, Roles.User],
}, },
{
title: "musicsheets",
href: "/musicsheets",
hasRole: [Roles.Admin, Roles.User],
},
{ {
title: "playlists", title: "playlists",
href: "/playlists", href: "/playlists",
...@@ -38,9 +43,4 @@ export const navs = [ ...@@ -38,9 +43,4 @@ export const navs = [
href: "/login", href: "/login",
ifLoggedOut: true, ifLoggedOut: true,
}, },
{
title: "logout",
href: "/logout",
hasRole: [],
},
]; ];
<script>
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { slide } from 'svelte/transition';
import { _ } from 'svelte-i18n';
import { sha256 } from 'js-sha256';
import { getContext } from 'svelte';
/** @type {import('./$types').PageData} */
export let data;
const authUser = data.authUser;
const email = sha256(authUser.email);
/** @type {import('svelte/store').Writable<string>} */
getContext('title').set('account.info');
</script>
<div transition:slide>
<h1>{$_('page.account.info.title')}</h1>
<p>{$_('page.account.info.description')}</p>
<button class="btn-action" on:click={() => goto($page.url.pathname + '/edit', { replaceState: false })}>{$_('page.account.info.edit.title')}</button>
<br><br>
<p><strong>{$_('fields.user.username.label')}</strong>: {authUser.name}</p>
<p><strong>{$_('fields.user.email.label')}</strong>: {authUser.email}</p>
<p><strong>{$_('page.account.info.avatar.label')}</strong>: <img class="w-16 h-16 rounded-full" src={"https://www.gravatar.com/avatar/" + email} alt={$_('app.avatar_alt')}></p>
<br>
<p><i>{$_('page.account.info.avatar.question')} <a href="https://gravatar.com/profile">{$_('page.account.info.avatar.answer')}</a></i></p>
</div>
import { error, fail } from '@sveltejs/kit';
import * as User from '$lib/server/models/User';
import { parseAndCheckFormData } from '$lib/utils';
/** @type {import('./$types').Actions} */
export const actions = {
edit: async ({ request, locals }) => {
const localUser = locals.user;
if (!localUser) return fail(401);
const userId = localUser.id;
const data = await request.formData();
const formData = parseAndCheckFormData(data, [
{ name: 'email', required: true, type: 'string' },
{ name: 'name', required: false, type: 'string' },
{ name: 'password', required: false, type: 'string' },
]);
if (!formData.success) return formData.data;
const { email, name } = formData.data;
let origUser = await User.findById(userId);
if (!(origUser))
return fail(404, { not_found: true });
if (origUser.email !== email && await User.validateEmail(email))
return fail(400, { unique: true });
// This should make sure to check if something is changed
if ((origUser.email === email || origUser.name === name || origUser.roleId === roleId) && !(formData.data.password && formData.data.password !== ''))
return { success: true, edited: false, user: origUser };
const user = await User.edit(origUser.id, email, roleId, name);
if (formData.data.password && formData.data.password !== '')
await User.editPassword(origUser.id, formData.data.password);
return { success: true, edited: true, user };
},
}
<script>
import TextField from '$lib/components/inputs/TextField.svelte';
import Error from '$lib/components/alerts/Error.svelte';
import Notice from '$lib/components/alerts/Notice.svelte';
import Success from '$lib/components/alerts/Success.svelte';
import { enhance, applyAction } from '$app/forms';
import { getContext } from 'svelte';
import { slide } from 'svelte/transition';
import { _ } from 'svelte-i18n';
/** @type {import('./$types').PageData} */
export let data;
const authUser = data.authUser;
/** @type {import('./$types').ActionData} */
export let form;
/** @type {string} */ let name = form?.name ?? authUser.name;
/** @type {string} */ let email = form?.email ?? authUser.email;
let working = false;
/** @type {import('svelte/store').Writable<string>} */
getContext('title').set('account.info.edit');
</script>
<div transition:slide>
<h1>{$_('page.account.info.edit.title')}</h1>
{#if working}
<div class="mt-4" transition:slide>
<Notice message={$_('messages.working')} />
</div>
{/if}
{#if !working && form?.success}
<div class="mt-4" transition:slide>
<Success message={$_('messages.successfully.edited')} />
</div>
{/if}
{#if !working && form?.success === false}
<div class="mt-4" transition:slide>
<Success message={$_('messages.not_successfully.edited')} />
</div>
{/if}
{#if !working && form?.required}
<div class="mt-4" transition:slide>
<Error message={$_('messages.required.all')} />
</div>
{/if}
{#if !working && form?.unique}
<div class="mt-4" transition:slide>
<Error message={$_('messages.unique.specific', { values: { attribute: 'email' } })} />
</div>
{/if}
<form class="space-y-4 md:space-y-6" action="?/edit" method="POST" use:enhance={() => { working = true; return async ({ result }) => { working = false; await applyAction(result); }; }}>
<TextField
id="email"
type="text"
placeholder={$_('fields.user.email.placeholder')}
label={true}
labelText={$_('fields.user.email.label')}
required={true}
autocomplete="email"
bind:value={email}
disabled={working}
/>
<TextField
id="name"
type="text"
placeholder={$_('fields.user.username.placeholder')}
label={true}
labelText={$_('fields.user.username.label')}
required={false}
autocomplete="username"
bind:value={name}
disabled={working}
/>
<TextField
id="password"
type="password"
placeholder={$_('fields.user.change_password.placeholder')}
label={true}
labelText={$_('fields.user.change_password.label')}
required={false}
autocomplete="new-password"
value=""
disabled={working}
/>
<p><i>{$_('page.account.info.avatar.question')} <a href="https://gravatar.com/profile">{$_('page.account.info.avatar.answer')}</a></i></p>
<button class="btn-success">{$_('actions.save_changes')}</button>
</form>
</div>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment