1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-04 12:45:17 +02:00

feat: Update user profile handling and enhance public user details response

This commit is contained in:
Sean Morley 2025-01-29 22:50:53 -05:00
parent b68e2dedaa
commit 85b55660f9
10 changed files with 274 additions and 212 deletions

View file

@ -34,7 +34,9 @@
? `${user.first_name} ${user.last_name}`
: user.username}
</p>
<li><button on:click={() => goto('/profile')}>{$t('navbar.profile')}</button></li>
<li>
<button on:click={() => goto(`/profile/${user.username}`)}>{$t('navbar.profile')}</button>
</li>
<li><button on:click={() => goto('/adventures')}>{$t('navbar.my_adventures')}</button></li>
<li><button on:click={() => goto('/shared')}>{$t('navbar.shared_with_me')}</button></li>
<li><button on:click={() => goto('/settings')}>{$t('navbar.settings')}</button></li>

View file

@ -326,7 +326,11 @@
"new_password": "New Password (6+ characters)",
"both_passwords_required": "Both passwords are required",
"reset_failed": "Failed to reset password",
"or_3rd_party": "Or login with a third-party service"
"or_3rd_party": "Or login with a third-party service",
"no_public_adventures": "No public adventures found",
"no_public_collections": "No public collections found",
"user_adventures": "User Adventures",
"user_collections": "User Collections"
},
"users": {
"no_users_found": "No users found with public profiles."

View file

@ -1,29 +0,0 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad, RequestEvent } from '../$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
export const load: PageServerLoad = async (event: RequestEvent) => {
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
if (!event.locals.user || !event.cookies.get('sessionid')) {
return redirect(302, '/login');
}
let sessionId = event.cookies.get('sessionid');
let stats = null;
let res = await event.fetch(`${endpoint}/api/stats/counts/`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
if (!res.ok) {
console.error('Failed to fetch user stats');
} else {
stats = await res.json();
}
return {
user: event.locals.user,
stats
};
};

View file

@ -1,112 +0,0 @@
<script lang="ts">
export let data;
import { t } from 'svelte-i18n';
let stats: {
visited_country_count: number;
total_regions: number;
trips_count: number;
adventure_count: number;
visited_region_count: number;
total_countries: number;
visited_city_count: number;
total_cities: number;
} | null;
stats = data.stats || null;
</script>
<section class="min-h-screen bg-base-100 py-8 px-4">
<div class="flex flex-col items-center">
<!-- Profile Picture -->
{#if data.user.profile_pic}
<div class="avatar">
<div
class="w-24 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 shadow-md"
>
<img src={data.user.profile_pic} alt="Profile" />
</div>
</div>
{/if}
<!-- User Name -->
{#if data.user && data.user.first_name && data.user.last_name}
<h1 class="text-4xl font-bold text-primary mt-4">
{data.user.first_name}
{data.user.last_name}
</h1>
{/if}
<p class="text-lg text-base-content mt-2">{data.user.username}</p>
<!-- Member Since -->
{#if data.user && data.user.date_joined}
<div class="mt-4 flex items-center text-center text-base-content">
<p class="text-lg font-medium">{$t('profile.member_since')}</p>
<div class="flex items-center ml-2">
<iconify-icon icon="mdi:calendar" class="text-2xl text-primary"></iconify-icon>
<p class="ml-2 text-lg">
{new Date(data.user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })}
</p>
</div>
</div>
{/if}
</div>
<!-- Stats Section -->
{#if stats}
<div class="divider my-8"></div>
<h2 class="text-2xl font-bold text-center mb-6 text-primary">
{$t('profile.user_stats')}
</h2>
<div class="flex justify-center">
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
<div class="stat">
<div class="stat-title">{$t('navbar.adventures')}</div>
<div class="stat-value text-center">{stats.adventure_count}</div>
</div>
<div class="stat">
<div class="stat-title">{$t('navbar.collections')}</div>
<div class="stat-value text-center">{stats.trips_count}</div>
</div>
<div class="stat">
<div class="stat-title">{$t('profile.visited_countries')}</div>
<div class="stat-value text-center">
{Math.round((stats.visited_country_count / stats.total_countries) * 100)}%
</div>
<div class="stat-desc text-center">
{stats.visited_country_count}/{stats.total_countries}
</div>
</div>
<div class="stat">
<div class="stat-title">{$t('profile.visited_regions')}</div>
<div class="stat-value text-center">
{Math.round((stats.visited_region_count / stats.total_regions) * 100)}%
</div>
<div class="stat-desc text-center">
{stats.visited_region_count}/{stats.total_regions}
</div>
</div>
<div class="stat">
<div class="stat-title">{$t('profile.visited_cities')}</div>
<div class="stat-value text-center">
{Math.round((stats.visited_city_count / stats.total_cities) * 100)}%
</div>
<div class="stat-desc text-center">
{stats.visited_city_count}/{stats.total_cities}
</div>
</div>
</div>
</div>
{/if}
</section>
<svelte:head>
<title>Profile | AdventureLog</title>
<meta name="description" content="{data.user.first_name}'s profile on AdventureLog." />
</svelte:head>

View file

@ -0,0 +1,40 @@
import { redirect, error } from '@sveltejs/kit';
import type { PageServerLoad, RequestEvent } from '../../$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
export const load: PageServerLoad = async (event: RequestEvent) => {
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
let uuid = event.params.uuid as string;
if (!uuid) {
return error(404, 'Not found');
}
// let sessionId = event.cookies.get('sessionid');
// let stats = null;
// let res = await event.fetch(`${endpoint}/api/stats/counts/`, {
// headers: {
// Cookie: `sessionid=${sessionId}`
// }
// });
// if (!res.ok) {
// console.error('Failed to fetch user stats');
// } else {
// stats = await res.json();
// }
let userData = await event.fetch(`${endpoint}/auth/user/${uuid}/`);
if (!userData.ok) {
return error(404, 'Not found');
}
let data = await userData.json();
return {
user: data.user,
adventures: data.adventures,
collections: data.collections
};
};

View file

@ -0,0 +1,180 @@
<script lang="ts">
export let data;
import AdventureCard from '$lib/components/AdventureCard.svelte';
import CollectionCard from '$lib/components/CollectionCard.svelte';
import type { Adventure, Collection, User } from '$lib/types.js';
import { t } from 'svelte-i18n';
// let stats: {
// visited_country_count: number;
// total_regions: number;
// trips_count: number;
// adventure_count: number;
// visited_region_count: number;
// total_countries: number;
// visited_city_count: number;
// total_cities: number;
// } | null;
const user: User = data.user;
const adventures: Adventure[] = data.adventures;
const collections: Collection[] = data.collections;
// console.log(user);
// console.log(adventures);
// console.log(collections);
// stats = data.stats || null;
</script>
<section class="min-h-screen bg-base-100 py-8 px-4">
<div class="flex flex-col items-center">
<!-- Profile Picture -->
{#if user.profile_pic}
<div class="avatar">
<div
class="w-24 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 shadow-md"
>
<img src={user.profile_pic} alt="Profile" />
</div>
</div>
{:else}
<!-- show first last initial -->
<div class="avatar">
<div
class="w-24 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 shadow-md"
>
{#if user.first_name && user.last_name}
<img
src={`https://eu.ui-avatars.com/api/?name=${user.first_name}+${user.last_name}&size=250`}
alt="Profile"
/>
{:else}
<img
src={`https://eu.ui-avatars.com/api/?name=${user.username}&size=250`}
alt="Profile"
/>
{/if}
</div>
</div>
{/if}
<!-- User Name -->
{#if user && user.first_name && user.last_name}
<h1 class="text-4xl font-bold text-primary mt-4">
{user.first_name}
{user.last_name}
</h1>
{/if}
<p class="text-lg text-base-content mt-2">{user.username}</p>
<!-- Member Since -->
{#if user && user.date_joined}
<div class="mt-4 flex items-center text-center text-base-content">
<p class="text-lg font-medium">{$t('profile.member_since')}</p>
<div class="flex items-center ml-2">
<iconify-icon icon="mdi:calendar" class="text-2xl text-primary"></iconify-icon>
<p class="ml-2 text-lg">
{new Date(user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })}
</p>
</div>
</div>
{/if}
</div>
<!-- Stats Section -->
<!-- {#if stats}
<div class="divider my-8"></div>
<h2 class="text-2xl font-bold text-center mb-6 text-primary">
{$t('profile.user_stats')}
</h2>
<div class="flex justify-center">
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
<div class="stat">
<div class="stat-title">{$t('navbar.adventures')}</div>
<div class="stat-value text-center">{stats.adventure_count}</div>
</div>
<div class="stat">
<div class="stat-title">{$t('navbar.collections')}</div>
<div class="stat-value text-center">{stats.trips_count}</div>
</div>
<div class="stat">
<div class="stat-title">{$t('profile.visited_countries')}</div>
<div class="stat-value text-center">
{Math.round((stats.visited_country_count / stats.total_countries) * 100)}%
</div>
<div class="stat-desc text-center">
{stats.visited_country_count}/{stats.total_countries}
</div>
</div>
<div class="stat">
<div class="stat-title">{$t('profile.visited_regions')}</div>
<div class="stat-value text-center">
{Math.round((stats.visited_region_count / stats.total_regions) * 100)}%
</div>
<div class="stat-desc text-center">
{stats.visited_region_count}/{stats.total_regions}
</div>
</div>
<div class="stat">
<div class="stat-title">{$t('profile.visited_cities')}</div>
<div class="stat-value text-center">
{Math.round((stats.visited_city_count / stats.total_cities) * 100)}%
</div>
<div class="stat-desc text-center">
{stats.visited_city_count}/{stats.total_cities}
</div>
</div>
</div>
</div>
{/if} -->
<!-- Adventures Section -->
<div class="divider my-8"></div>
<h2 class="text-2xl font-bold text-center mb-6 text-primary">
{$t('auth.user_adventures')}
</h2>
{#if adventures && adventures.length === 0}
<p class="text-lg text-center text-base-content">
{$t('auth.no_public_adventures')}
</p>
{:else}
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
{#each adventures as adventure}
<AdventureCard {adventure} user={null} />
{/each}
</div>
{/if}
<!-- Collections Section -->
<div class="divider my-8"></div>
<h2 class="text-2xl font-bold text-center mb-6 text-primary">
{$t('auth.user_collections')}
</h2>
{#if collections && collections.length === 0}
<p class="text-lg text-center text-base-content">
{$t('auth.no_public_collections')}
</p>
{:else}
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
{#each collections as collection}
<CollectionCard {collection} type={''} />
{/each}
</div>
{/if}
</section>
<svelte:head>
<title>{user.first_name || user.username}'s Profile | AdventureLog</title>
<meta name="description" content="User Profile" />
</svelte:head>