2025-07-13 18:22:03 +01:00
|
|
|
<script>
|
|
|
|
import Button from '@cf/ui/Button.svelte';
|
2025-07-13 22:00:33 +01:00
|
|
|
import HomeIcon from '@cf/icons/unlisted.svg';
|
|
|
|
import MoreIcon from '@cf/icons/more.svg';
|
|
|
|
import Lang from '$lib/lang';
|
2025-07-14 00:19:42 +01:00
|
|
|
import * as api from '$lib/api.js';
|
|
|
|
import { server, createServer } from '$lib/client/server.js';
|
|
|
|
import { app } from '$lib/client/app.js';
|
|
|
|
import { parseAccount } from '$lib/account.js';
|
|
|
|
import { account } from '$lib/stores/account';
|
|
|
|
import { goto, afterNavigate } from '$app/navigation';
|
|
|
|
import { base } from '$app/paths';
|
|
|
|
|
|
|
|
export let data;
|
2025-07-13 22:00:33 +01:00
|
|
|
|
|
|
|
const lang = Lang('en_GB');
|
2025-07-14 00:19:42 +01:00
|
|
|
|
|
|
|
let profile = fetchProfile(data.account_handle);
|
|
|
|
let error = false;
|
|
|
|
let previous_page = base;
|
|
|
|
|
|
|
|
afterNavigate(({from}) => {
|
|
|
|
previous_page = from?.url.pathname || previous_page;
|
|
|
|
profile = fetchProfile(data.account_handle);
|
|
|
|
})
|
|
|
|
|
|
|
|
async function fetchProfile(handle) {
|
|
|
|
let token = $app ? $app.token : null;
|
|
|
|
|
|
|
|
if (!$server || $server.host !== data.server_host) {
|
|
|
|
server.set(await createServer(data.server_host));
|
|
|
|
if (!$server) {
|
|
|
|
error = lang.string('error.connection_failed', data.server_host);
|
|
|
|
throw new Error(lang.string('logs.connection_failed', data.server_host));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let profile_data;
|
|
|
|
try {
|
|
|
|
profile_data = await api.lookupUser($server.host, token, handle);
|
|
|
|
console.debug(profile_data);
|
|
|
|
} catch (error) {
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!profile_data || profile_data.error) {
|
|
|
|
error = lang.string('error.profile_fetch_failed_id', handle);
|
|
|
|
throw new Error(lang.string('logs.profile_fetch_failed_id', handle));
|
|
|
|
}
|
|
|
|
let profile = await parseAccount(profile_data, 0);
|
|
|
|
|
|
|
|
return profile;
|
|
|
|
}
|
2025-07-13 18:22:03 +01:00
|
|
|
</script>
|
|
|
|
|
2025-07-14 00:19:42 +01:00
|
|
|
{#await profile}
|
|
|
|
<div class="loading throb">
|
|
|
|
<span>{lang.string('profile.loading')}</span>
|
2025-07-13 18:22:03 +01:00
|
|
|
</div>
|
2025-07-14 00:19:42 +01:00
|
|
|
{:then profile}
|
|
|
|
<header data-banner="{profile.banner_url}">
|
|
|
|
<img src="{profile.banner_url}" class="profile-banner" alt="">
|
|
|
|
<div class="profile-tag">
|
|
|
|
<!-- svelte-ignore a11y-img-redundant-alt -->
|
|
|
|
<img src="{profile.avatar_url}" alt="">
|
|
|
|
<div class="profile-tag-names">
|
|
|
|
<h1>{@html profile.rich_name}</h1>
|
|
|
|
<p>{profile.fqn}</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</header>
|
|
|
|
<div class="profile-info">
|
|
|
|
<p class="profile-bio">{@html profile.bio}</p>
|
|
|
|
<ul class="profile-counts">
|
|
|
|
<li><b>{lang.string('profile.followers')}</b> {profile.followers_count}</li>
|
|
|
|
<li><b>{lang.string('profile.following')}</b> {profile.following_count}</li>
|
|
|
|
<li><b>{lang.string('profile.posts')}</b> {profile.posts_count}</li>
|
|
|
|
</ul>
|
2025-07-13 18:22:03 +01:00
|
|
|
<div class="profile-actions">
|
2025-07-14 00:19:42 +01:00
|
|
|
{#if $account && profile.fqn !== $account.fqn}
|
|
|
|
<Button disabled filled label="{lang.string('profile.follow')} {profile.nickname}" class="profile-btn-follow">
|
|
|
|
{lang.string('profile.follow')}
|
|
|
|
</Button>
|
|
|
|
{/if}
|
|
|
|
<Button label="{lang.string('profile.home_instance')}" href="{profile.url}">
|
2025-07-13 18:22:03 +01:00
|
|
|
<HomeIcon width="24px"/>
|
|
|
|
</Button>
|
|
|
|
<Button>
|
|
|
|
<MoreIcon width="24px"/>
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2025-07-14 00:19:42 +01:00
|
|
|
<div class="profile-post-categories">
|
|
|
|
<Button active>
|
|
|
|
{lang.string('profile.posts')}
|
|
|
|
</Button>
|
|
|
|
<Button>
|
|
|
|
{lang.string('profile.replies')}
|
|
|
|
</Button>
|
|
|
|
<Button>
|
|
|
|
{lang.string('profile.media')}
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
{:catch error}
|
|
|
|
<p class="error">{error}</p>
|
|
|
|
{/await}
|
2025-07-13 18:22:03 +01:00
|
|
|
|
|
|
|
<style>
|
|
|
|
header {
|
|
|
|
margin-top: 1rem;
|
|
|
|
width: 100%;
|
|
|
|
height: 215px;
|
|
|
|
position: relative;
|
|
|
|
}
|
|
|
|
|
2025-07-14 00:19:42 +01:00
|
|
|
.profile-banner {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
object-fit: cover;
|
|
|
|
background-color: var(--bg-700);
|
|
|
|
}
|
|
|
|
|
2025-07-13 18:22:03 +01:00
|
|
|
.profile-tag {
|
|
|
|
position: absolute;
|
|
|
|
bottom: 16px;
|
|
|
|
left: 16px;
|
2025-07-13 22:00:33 +01:00
|
|
|
background: color-mix(in srgb, transparent, var(--bg-1000) 50%);
|
2025-07-13 18:22:03 +01:00
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
width: fit-content;
|
|
|
|
display: flex;
|
|
|
|
height: 64px;
|
|
|
|
border-radius: 8px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-tag img {
|
|
|
|
aspect-ratio: 1;
|
|
|
|
border-top-left-radius: 8px;
|
|
|
|
border-bottom-left-radius: 8px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-tag-names {
|
|
|
|
padding: 8px 16px;
|
|
|
|
align-self: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-tag-names * {
|
|
|
|
margin: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-tag-names h1 {
|
|
|
|
font-size: 1.15rem
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-info {
|
|
|
|
background-color: var(--bg-800);
|
|
|
|
padding: 16px;
|
|
|
|
border-bottom-left-radius: 8px;
|
|
|
|
border-bottom-right-radius: 8px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-bio {
|
|
|
|
margin: 0;
|
|
|
|
|
|
|
|
/* !! may not be required in prod */
|
|
|
|
white-space: pre-line;
|
|
|
|
}
|
2025-07-14 00:19:42 +01:00
|
|
|
:global(.profile-bio p:first-of-type) {
|
|
|
|
margin: 0;
|
|
|
|
}
|
2025-07-13 18:22:03 +01:00
|
|
|
|
2025-07-14 00:19:42 +01:00
|
|
|
.profile-counts {
|
|
|
|
padding: 0;
|
|
|
|
}
|
|
|
|
.profile-counts li {
|
|
|
|
display: inline-block;
|
|
|
|
}
|
2025-07-13 18:22:03 +01:00
|
|
|
.profile-counts > *:not(.profile-counts:first-child) {
|
|
|
|
margin-right: 16px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-actions {
|
|
|
|
width: fit-content;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: .5rem;
|
|
|
|
}
|
|
|
|
|
2025-07-14 00:19:42 +01:00
|
|
|
.profile-btn-follow {
|
2025-07-13 18:22:03 +01:00
|
|
|
padding: 0 32px;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-actions :global(button) {
|
|
|
|
height: 42px;
|
|
|
|
width: fit-content;
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile-post-categories {
|
|
|
|
display: flex;
|
|
|
|
padding: 1rem 0;
|
|
|
|
gap: 1rem;
|
|
|
|
}
|
2025-07-14 00:19:42 +01:00
|
|
|
|
|
|
|
.loading {
|
|
|
|
width: 100%;
|
|
|
|
height: 80vh;
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
align-items: center;
|
|
|
|
font-size: 2em;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
|
|
|
|
.error {
|
|
|
|
padding: 4em 0;
|
|
|
|
width: 100%;
|
|
|
|
text-align: center;
|
|
|
|
}
|
2025-07-13 22:00:33 +01:00
|
|
|
</style>
|