forms (#18)
I made a best effort attempt to design the profile page according to the current design specification. Signed-off-by: André Jaenisch <andre.jaenisch@posteo.de> Reviewed-on: https://codeberg.org/Anvil/Anvil/pulls/18 Co-authored-by: André Jaenisch <andre.jaenisch@posteo.de> Co-committed-by: André Jaenisch <andre.jaenisch@posteo.de>
This commit is contained in:
parent
aea63c0b4b
commit
9bb86e84b1
5 changed files with 191 additions and 6 deletions
39
src/lib/server/ap.js
Normal file
39
src/lib/server/ap.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
const loginResponse = {
|
||||
id: '87bcb6de-bb70-11ee-b719-6756da82e80f',
|
||||
username: 'hax0r',
|
||||
acct: 'hax0r',
|
||||
display_name: 'Jane Doe',
|
||||
bot: false,
|
||||
created_at: '2024-01-13T01:23:45.000Z',
|
||||
note: '<p>Hackse for life</p>',
|
||||
url: 'https://example.com/@hax0r',
|
||||
avatar: 'https://avatars.example.com/hax0r',
|
||||
role: { name: 'user' }
|
||||
};
|
||||
|
||||
let username = loginResponse.username;
|
||||
|
||||
export const requests = {
|
||||
get: (url) => {
|
||||
if (url === '/profile') {
|
||||
return Promise.resolve({
|
||||
...loginResponse,
|
||||
username
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Unauthorized'));
|
||||
},
|
||||
post: (url, params) => {
|
||||
if (url === '/login') {
|
||||
username = params.account;
|
||||
|
||||
return Promise.resolve({
|
||||
...loginResponse,
|
||||
username: params.account
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Invalid Login'));
|
||||
}
|
||||
};
|
35
src/routes/account/login/+page.server.js
Normal file
35
src/routes/account/login/+page.server.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { fail, redirect } from '@sveltejs/kit';
|
||||
|
||||
import { requests } from '$lib/server/ap.js';
|
||||
|
||||
/** @type {import('./$types').Actions} */
|
||||
export const actions = {
|
||||
login: async ({ cookies, request, url }) => {
|
||||
const formData = await request.formData();
|
||||
const server = formData.get('server');
|
||||
const account = formData.get('account');
|
||||
const passphrase = formData.get('passphrase');
|
||||
|
||||
if (!account) {
|
||||
return fail(400, { account, missing: true });
|
||||
}
|
||||
|
||||
if (!passphrase) {
|
||||
return fail(400, { account, incorrect: true });
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Consumre response?
|
||||
await requests.post('/login', { account, passphrase });
|
||||
} catch (exc) {
|
||||
console.error(exc);
|
||||
return fail(400, { account, incorrect: true });
|
||||
}
|
||||
|
||||
if (url.searchParams.has('redirectTo')) {
|
||||
redirect(303, url.searchParams.get('redirectTo'));
|
||||
}
|
||||
|
||||
redirect(303, '/profile');
|
||||
}
|
||||
};
|
|
@ -1,11 +1,18 @@
|
|||
<script>
|
||||
/** @type {import('./$types').ActionData} */
|
||||
export let form;
|
||||
</script>
|
||||
|
||||
<div class="w-full max-w-md h-full mx-auto flex justify-center items-center py-10">
|
||||
<div class="space-y-10">
|
||||
<h1 class="h1 font-bold">Log in</h1>
|
||||
<p>To use Anvil with your F2 account, fill in your credentials.</p>
|
||||
<div class="space-y-4">
|
||||
<form class="space-y-4" method="POST" action="?/login">
|
||||
{#if form?.missing}<p class="error">The account field is required.</p>{/if}
|
||||
{#if form?.incorrect}<p class="error">Account or password wrong.</p>{/if}
|
||||
<label class="label">
|
||||
<span>F2 server</span>
|
||||
<select class="select" title="F2 server" value="1">
|
||||
<select class="select" title="F2 server" name="server">
|
||||
<option value="1">fig.fr33domlover.site</option>
|
||||
<option value="2">grape.fr33domlover.site</option>
|
||||
<option value="3">walnut.fr33domlover.site</option>
|
||||
|
@ -13,16 +20,16 @@
|
|||
</label>
|
||||
<label class="label">
|
||||
<span>Account name</span>
|
||||
<input class="input" title="Account name" type="text" placeholder="alice" />
|
||||
<input class="input" name="account" type="text" value={form?.account ?? ''} />
|
||||
</label>
|
||||
<label class="label">
|
||||
<span>Passphrase</span>
|
||||
<input class="input" title="Passphrase" type="password" placeholder="psswrd" />
|
||||
<input class="input" name="passphrase" type="password" placeholder="password" />
|
||||
</label>
|
||||
<div class="text-right">
|
||||
<a href="/" class="btn btn-bg-initial">Reset passphrase</a>
|
||||
</div>
|
||||
<a href="/" class="w-full btn variant-filled-primary">Log in</a>
|
||||
</div>
|
||||
<button type="submit" class="w-full btn variant-filled-primary"> Log in </button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
19
src/routes/profile/+page.server.js
Normal file
19
src/routes/profile/+page.server.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { requests } from '$lib/server/ap.js';
|
||||
|
||||
/** @type {import('./$types').PageServerLoad} */
|
||||
export async function load({ params }) {
|
||||
const locale = 'en-US';
|
||||
const profile = await requests.get('/profile');
|
||||
|
||||
return {
|
||||
server: {
|
||||
name: 'F2'
|
||||
},
|
||||
user: {
|
||||
...profile,
|
||||
created_at_formatted: new Intl.DateTimeFormat(locale).format(new Date(profile.created_at)),
|
||||
created_with: 'Anvil',
|
||||
instance: 'example.com'
|
||||
}
|
||||
};
|
||||
}
|
85
src/routes/profile/+page.svelte
Normal file
85
src/routes/profile/+page.svelte
Normal file
|
@ -0,0 +1,85 @@
|
|||
<script>
|
||||
import { Avatar } from '@skeletonlabs/skeleton';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<section class="w-full mx-auto flex px-8 pt-8">
|
||||
<!-- Profile header -->
|
||||
<div>
|
||||
<Avatar
|
||||
src={data.user.avatar}
|
||||
width="w-32"
|
||||
rounded="rounded-full"
|
||||
initials={data.user.display_name}
|
||||
/>
|
||||
</div>
|
||||
<!-- Board -->
|
||||
<div class="flex flex-1 flex-col pl-8">
|
||||
<div class="self-end">
|
||||
<span
|
||||
>created <date datetime={data.user.created_at}>{@html data.user.created_at_formatted}</date
|
||||
></span
|
||||
>
|
||||
<button type="button" class="underline">block</button>
|
||||
or
|
||||
<button type="button" class="underline">report</button>
|
||||
</div>
|
||||
<!-- Top Row -->
|
||||
<div class="flex mt-2">
|
||||
<!-- Board -->
|
||||
<div class="flex flex-col flex-1 gap-2">
|
||||
<div class="flex items-end">
|
||||
<h1 class="h1 font-semibold text-4xl leading-tight">
|
||||
<span class="sr-only">Profile for </span>
|
||||
{@html data.user.display_name}
|
||||
</h1>
|
||||
<p class="leading-normal my-1">(she/her)</p>
|
||||
</div>
|
||||
<span class="leading-tight">
|
||||
{@html data.user.username}@{@html data.user.instance}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Interaction Links -->
|
||||
<button type="button" class="btn-icon border rounded-none self-start">⭐</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="w-full h-full mx-auto flex mt-8 px-8">
|
||||
<!-- Board -->
|
||||
<div class="flex flex-1 flex-col px-4">
|
||||
<div class="space-y-6">
|
||||
<h2 class="h2 font-semibold leading-tight text-base px-4">Projects</h2>
|
||||
<p class="px-4">
|
||||
No projects added yet.
|
||||
<a class="anchor" href="#">Add a project</a>
|
||||
or
|
||||
<a class="anchor" href="#">import a project</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- History -->
|
||||
<div class="flex flex-1 flex-col px-4">
|
||||
<div class="space-y-6">
|
||||
<h2 class="h2 font-semibold leading-tight text-base">Activities</h2>
|
||||
<ul>
|
||||
<li class="flex flex-col border-l-2 ml-2">
|
||||
<!-- Commit day -->
|
||||
<span>
|
||||
<!-- North Star in surface-900 -->
|
||||
<span class="-ml-2 bg-[#0f161f]">✴️</span>
|
||||
<span class="ml-4">Apr 23, 2023 - account set up</span>
|
||||
</span>
|
||||
<!-- Commits -->
|
||||
<span class="ml-6 mt-8">
|
||||
<span class="border box-decoration-clone px-4 py-2 leading-10">
|
||||
The F2 account @{@html data.user.username}@{@html data.user.instance} was successfully
|
||||
set up within {@html data.user.created_with}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
Loading…
Reference in a new issue