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:
parent
ad5fb02ebb
commit
9c3a52ae85
14 changed files with 121 additions and 66 deletions
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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!",
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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": "全部",
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue