1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-23 06:49:37 +02:00

feat: Refactor user detail view and enhance localization strings for multiple languages

This commit is contained in:
Sean Morley 2025-02-03 19:56:25 -05:00
parent ad5fb02ebb
commit 9c3a52ae85
14 changed files with 121 additions and 66 deletions

View file

@ -2,29 +2,42 @@ from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.decorators import action
from django.shortcuts import get_object_or_404
from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion
from adventures.models import Adventure, Collection
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
from django.contrib.auth import get_user_model
User = get_user_model()
class StatsViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing the stats of a user.
"""
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get'], url_path='counts/(?P<username>[^/.]+)')
def counts(self, request, username):
if request.user.username == username:
user = get_object_or_404(User, username=username)
else:
user = get_object_or_404(User, username=username, public_profile=True)
serializer = PublicUserSerializer(user)
@action(detail=False, methods=['get'])
def counts(self, request):
# remove the email address from the response
user.email = None
# get the counts for the user
adventure_count = Adventure.objects.filter(
user_id=request.user.id).count()
user_id=user.id).count()
trips_count = Collection.objects.filter(
user_id=request.user.id).count()
user_id=user.id).count()
visited_city_count = VisitedCity.objects.filter(
user_id=request.user.id).count()
user_id=user.id).count()
total_cities = City.objects.count()
visited_region_count = VisitedRegion.objects.filter(
user_id=request.user.id).count()
user_id=user.id).count()
total_regions = Region.objects.count()
visited_country_count = VisitedRegion.objects.filter(
user_id=request.user.id).values('region__country').distinct().count()
user_id=user.id).values('region__country').distinct().count()
total_countries = Country.objects.count()
return Response({
'adventure_count': adventure_count,

View file

@ -82,7 +82,6 @@ class PublicUserDetailView(APIView):
operation_description="Get public user information."
)
def get(self, request, username):
print(request.user)
if request.user.username == username:
user = get_object_or_404(User, username=username)
else:

View file

@ -10,7 +10,6 @@
ReverseGeocode
} from '$lib/types';
import { onMount } from 'svelte';
import { enhance } from '$app/forms';
import { addToast } from '$lib/toasts';
import { deserialize } from '$app/forms';
import { t } from 'svelte-i18n';
@ -1257,7 +1256,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
{#if immichIntegration}
<ImmichSelect
adventure={adventure}
{adventure}
on:fetchImage={(e) => {
url = e.detail;
fetchImage();

View file

@ -234,7 +234,8 @@
"images": "Bilder",
"primary": "Primär",
"upload": "Hochladen",
"view_attachment": "Anhang anzeigen"
"view_attachment": "Anhang anzeigen",
"of": "von"
},
"home": {
"desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit",
@ -302,7 +303,11 @@
"both_passwords_required": "Beide Passwörter sind erforderlich",
"new_password": "Neues Passwort",
"reset_failed": "Passwort konnte nicht zurückgesetzt werden",
"or_3rd_party": "Oder melden Sie sich bei einem Drittanbieter an"
"or_3rd_party": "Oder melden Sie sich bei einem Drittanbieter an",
"no_public_adventures": "Keine öffentlichen Abenteuer gefunden",
"no_public_collections": "Keine öffentlichen Sammlungen gefunden",
"user_adventures": "Benutzerabenteuer",
"user_collections": "Benutzersammlungen"
},
"users": {
"no_users_found": "Keine Benutzer mit öffentlichen Profilen gefunden."

View file

@ -165,6 +165,7 @@
"delete_collection_success": "Collection deleted successfully!",
"delete_collection_warning": "Are you sure you want to delete this collection? This will also delete all of the linked adventures. This action cannot be undone.",
"cancel": "Cancel",
"of": "of",
"delete_collection": "Delete Collection",
"delete_adventure": "Delete Adventure",
"adventure_delete_success": "Adventure deleted successfully!",

View file

@ -281,7 +281,8 @@
"primary": "Primario",
"upload": "Subir",
"view_attachment": "Ver archivo adjunto",
"attachment_name": "Nombre del archivo adjunto"
"attachment_name": "Nombre del archivo adjunto",
"of": "de"
},
"worldtravel": {
"all": "Todo",
@ -326,7 +327,11 @@
"both_passwords_required": "Se requieren ambas contraseñas",
"new_password": "Nueva contraseña",
"reset_failed": "No se pudo restablecer la contraseña",
"or_3rd_party": "O inicie sesión con un servicio de terceros"
"or_3rd_party": "O inicie sesión con un servicio de terceros",
"no_public_adventures": "No se encontraron aventuras públicas",
"no_public_collections": "No se encontraron colecciones públicas",
"user_adventures": "Aventuras de usuario",
"user_collections": "Colecciones de usuarios"
},
"users": {
"no_users_found": "No se encontraron usuarios con perfiles públicos."

View file

@ -234,7 +234,8 @@
"images": "Images",
"primary": "Primaire",
"upload": "Télécharger",
"view_attachment": "Voir la pièce jointe"
"view_attachment": "Voir la pièce jointe",
"of": "de"
},
"home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",
@ -302,7 +303,11 @@
"both_passwords_required": "Les deux mots de passe sont requis",
"new_password": "Nouveau mot de passe",
"reset_failed": "Échec de la réinitialisation du mot de passe",
"or_3rd_party": "Ou connectez-vous avec un service tiers"
"or_3rd_party": "Ou connectez-vous avec un service tiers",
"no_public_adventures": "Aucune aventure publique trouvée",
"no_public_collections": "Aucune collection publique trouvée",
"user_adventures": "Aventures utilisateur",
"user_collections": "Collections d'utilisateurs"
},
"users": {
"no_users_found": "Aucun utilisateur trouvé avec des profils publics."

View file

@ -234,7 +234,8 @@
"images": "Immagini",
"primary": "Primario",
"upload": "Caricamento",
"view_attachment": "Visualizza allegato"
"view_attachment": "Visualizza allegato",
"of": "Di"
},
"home": {
"desc_1": "Scopri, pianifica ed esplora con facilità",
@ -302,7 +303,11 @@
"both_passwords_required": "Sono necessarie entrambe le password",
"new_password": "Nuova parola d'ordine",
"reset_failed": "Impossibile reimpostare la password",
"or_3rd_party": "Oppure accedi con un servizio di terze parti"
"or_3rd_party": "Oppure accedi con un servizio di terze parti",
"no_public_adventures": "Nessuna avventura pubblica trovata",
"no_public_collections": "Nessuna collezione pubblica trovata",
"user_adventures": "Avventure utente",
"user_collections": "Collezioni utente"
},
"users": {
"no_users_found": "Nessun utente trovato con profili pubblici."

View file

@ -234,7 +234,8 @@
"images": "Afbeeldingen",
"primary": "Primair",
"upload": "Uploaden",
"view_attachment": "Bijlage bekijken"
"view_attachment": "Bijlage bekijken",
"of": "van"
},
"home": {
"desc_1": "Ontdek, plan en verken met gemak",
@ -302,7 +303,11 @@
"both_passwords_required": "Beide wachtwoorden zijn vereist",
"new_password": "Nieuw wachtwoord",
"reset_failed": "Kan het wachtwoord niet opnieuw instellen",
"or_3rd_party": "Of log in met een service van derden"
"or_3rd_party": "Of log in met een service van derden",
"no_public_adventures": "Geen openbare avonturen gevonden",
"no_public_collections": "Geen openbare collecties gevonden",
"user_adventures": "Gebruikersavonturen",
"user_collections": "Gebruikerscollecties"
},
"users": {
"no_users_found": "Er zijn geen gebruikers gevonden met openbare profielen."

View file

@ -281,7 +281,8 @@
"images": "Obrazy",
"primary": "Podstawowy",
"upload": "Wgrywać",
"view_attachment": "Zobacz załącznik"
"view_attachment": "Zobacz załącznik",
"of": "z"
},
"worldtravel": {
"country_list": "Lista krajów",
@ -326,7 +327,11 @@
"both_passwords_required": "Obydwa hasła są wymagane",
"new_password": "Nowe hasło",
"reset_failed": "Nie udało się zresetować hasła",
"or_3rd_party": "Lub zaloguj się za pomocą usługi strony trzeciej"
"or_3rd_party": "Lub zaloguj się za pomocą usługi strony trzeciej",
"no_public_adventures": "Nie znaleziono publicznych przygód",
"no_public_collections": "Nie znaleziono publicznych kolekcji",
"user_adventures": "Przygody użytkowników",
"user_collections": "Kolekcje użytkowników"
},
"users": {
"no_users_found": "Nie znaleziono użytkowników z publicznymi profilami."

View file

@ -234,7 +234,8 @@
"images": "Bilder",
"primary": "Primär",
"upload": "Ladda upp",
"view_attachment": "Visa bilaga"
"view_attachment": "Visa bilaga",
"of": "av"
},
"home": {
"desc_1": "Upptäck, planera och utforska med lätthet",
@ -326,7 +327,11 @@
"both_passwords_required": "Båda lösenorden krävs",
"new_password": "Nytt lösenord",
"reset_failed": "Det gick inte att återställa lösenordet",
"or_3rd_party": "Eller logga in med en tredjepartstjänst"
"or_3rd_party": "Eller logga in med en tredjepartstjänst",
"no_public_adventures": "Inga offentliga äventyr hittades",
"no_public_collections": "Inga offentliga samlingar hittades",
"user_adventures": "Användaräventyr",
"user_collections": "Användarsamlingar"
},
"users": {
"no_users_found": "Inga användare hittades med offentliga profiler."

View file

@ -234,7 +234,8 @@
"images": "图片",
"primary": "基本的",
"upload": "上传",
"view_attachment": "查看附件"
"view_attachment": "查看附件",
"of": "的"
},
"home": {
"desc_1": "轻松发现、规划和探索",
@ -302,7 +303,11 @@
"both_passwords_required": "两个密码都需要",
"new_password": "新密码",
"reset_failed": "重置密码失败",
"or_3rd_party": "或者使用第三方服务登录"
"or_3rd_party": "或者使用第三方服务登录",
"no_public_adventures": "找不到公共冒险",
"no_public_collections": "找不到公共收藏",
"user_adventures": "用户冒险",
"user_collections": "用户收集"
},
"worldtravel": {
"all": "全部",

View file

@ -1,31 +1,29 @@
import { redirect, error } from '@sveltejs/kit';
import type { PageServerLoad, RequestEvent } from '../../$types';
import { t } from 'svelte-i18n';
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;
// @ts-ignore
let username = event.params.uuid as string;
if (!uuid) {
if (!username) {
return error(404, 'Not found');
}
// let sessionId = event.cookies.get('sessionid');
// let stats = null;
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 res = await event.fetch(`${endpoint}/api/stats/counts/${username}`, {});
if (!res.ok) {
console.error('Failed to fetch user stats');
} else {
stats = await res.json();
}
let userData = await event.fetch(`${endpoint}/auth/user/${uuid}/`);
let userData = await event.fetch(`${endpoint}/auth/user/${username}/`);
if (!userData.ok) {
return error(404, 'Not found');
}
@ -35,6 +33,7 @@ export const load: PageServerLoad = async (event: RequestEvent) => {
return {
user: data.user,
adventures: data.adventures,
collections: data.collections
collections: data.collections,
stats: stats
};
};

View file

@ -5,26 +5,21 @@
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;
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;
stats = data.stats || null;
</script>
<section class="min-h-screen bg-base-100 py-8 px-4">
@ -83,7 +78,7 @@
</div>
<!-- Stats Section -->
<!-- {#if stats}
{#if stats}
<div class="divider my-8"></div>
<h2 class="text-2xl font-bold text-center mb-6 text-primary">
@ -105,35 +100,44 @@
<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)}%
{stats.visited_country_count}
</div>
<div class="stat-desc text-center">
{stats.visited_country_count}/{stats.total_countries}
{Math.round((stats.visited_country_count / stats.total_countries) * 100)}% {$t(
'adventures.of'
)}
{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)}%
{stats.visited_region_count}
</div>
<div class="stat-desc text-center">
{stats.visited_region_count}/{stats.total_regions}
{Math.round((stats.visited_region_count / stats.total_regions) * 100)}% {$t(
'adventures.of'
)}
{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)}%
{stats.visited_city_count}
</div>
<div class="stat-desc text-center">
{stats.visited_city_count}/{stats.total_cities}
{Math.round((stats.visited_city_count / stats.total_cities) * 100)}% {$t(
'adventures.of'
)}
{stats.total_cities}
</div>
</div>
</div>
</div>
{/if} -->
{/if}
<!-- Adventures Section -->
<div class="divider my-8"></div>