1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-04 20:55:19 +02:00

Refactor adventure references to locations across the backend and frontend

- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.
This commit is contained in:
Sean Morley 2025-06-24 17:23:02 -04:00
parent 5c74203103
commit fae928252a
29 changed files with 173 additions and 109 deletions

View file

@ -14,7 +14,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs):
"""
Retrieve a list of distinct categories for adventures associated with the current user.
Retrieve a list of distinct categories for locations associated with the current user.
"""
categories = self.get_queryset().distinct()
serializer = self.get_serializer(categories, many=True)
@ -29,7 +29,7 @@ class CategoryViewSet(viewsets.ModelViewSet):
if instance.name == 'general':
return Response({"error": "Cannot delete the general category"}, status=400)
# set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user
# set any locations with this category to a default category called general before deleting the category, if general does not exist create it for the user
general_category = Category.objects.filter(user=request.user, name='general').first()
if not general_category:

View file

@ -29,12 +29,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures
# For individual adventure retrieval, include public locations
return Checklist.objects.filter(
Q(is_public=True) | Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')
else:
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Checklist.objects.filter(
Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')

View file

@ -20,7 +20,7 @@ class GlobalSearchView(viewsets.ViewSet):
# Initialize empty results
results = {
"adventures": [],
"locations": [],
"collections": [],
"users": [],
"countries": [],
@ -30,11 +30,11 @@ class GlobalSearchView(viewsets.ViewSet):
"visited_cities": []
}
# Adventures: Full-Text Search
adventures = Location.objects.annotate(
# Locations: Full-Text Search
locations = Location.objects.annotate(
search=SearchVector('name', 'description', 'location')
).filter(search=SearchQuery(search_term), user=request.user)
results["adventures"] = LocationSerializer(adventures, many=True).data
results["locations"] = LocationSerializer(locations, many=True).data
# Collections: Partial Match Search
collections = Collection.objects.filter(

View file

@ -12,8 +12,8 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
@action(detail=False, methods=['get'])
def generate(self, request):
adventures = Location.objects.filter(user=request.user)
serializer = LocationSerializer(adventures, many=True)
locations = Location.objects.filter(user=request.user)
serializer = LocationSerializer(locations, many=True)
user = request.user
name = f"{user.first_name} {user.last_name}"
@ -21,9 +21,9 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
cal.add('prodid', '-//My Adventure Calendar//example.com//')
cal.add('version', '2.0')
for adventure in serializer.data:
if adventure['visits']:
for visit in adventure['visits']:
for location in serializer.data:
if location['visits']:
for visit in location['visits']:
# Skip if start_date is missing
if not visit.get('start_date'):
continue
@ -41,7 +41,7 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
# Create event
event = Event()
event.add('summary', adventure['name'])
event.add('summary', location['name'])
event.add('dtstart', start_date)
event.add('dtend', end_date)
event.add('dtstamp', datetime.now())
@ -49,11 +49,11 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
event.add('class', 'PUBLIC')
event.add('created', datetime.now())
event.add('last-modified', datetime.now())
event.add('description', adventure['description'])
if adventure.get('location'):
event.add('location', adventure['location'])
if adventure.get('link'):
event.add('url', adventure['link'])
event.add('description', location['description'])
if location.get('location'):
event.add('location', location['location'])
if location.get('link'):
event.add('url', location['link'])
organizer = vCalAddress(f'MAILTO:{user.email}')
organizer.params['cn'] = vText(name)

View file

@ -9,7 +9,6 @@ from adventures.serializers import LocationImageSerializer
from integrations.models import ImmichIntegration
import uuid
import requests
import os
class AdventureImageViewSet(viewsets.ModelViewSet):
serializer_class = LocationImageSerializer

View file

@ -7,10 +7,9 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
import requests
from adventures.models import Location, Category, Transportation, Lodging
from adventures.models import Location, Category
from adventures.permissions import IsOwnerOrSharedWithFullAccess
from adventures.serializers import LocationSerializer, TransportationSerializer, LodgingSerializer
from adventures.serializers import LocationSerializer
from adventures.utils import pagination
@ -28,7 +27,7 @@ class LocationViewSet(viewsets.ModelViewSet):
def get_queryset(self):
"""
Returns queryset based on user authentication and action type.
Public actions allow unauthenticated access to public adventures.
Public actions allow unauthenticated access to public locations.
"""
user = self.request.user
public_allowed_actions = {'retrieve', 'additional_info'}
@ -67,7 +66,7 @@ class LocationViewSet(viewsets.ModelViewSet):
# Apply sorting logic
queryset = self._apply_ordering(queryset, order_by, order_direction)
# Filter adventures without collections if requested
# Filter locations without collections if requested
if include_collections == 'false':
queryset = queryset.filter(collections__isnull=True)
@ -147,7 +146,7 @@ class LocationViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def filtered(self, request):
"""Filter adventures by category types and visit status."""
"""Filter locations by category types and visit status."""
types = request.query_params.get('types', '').split(',')
# Handle 'all' types
@ -180,7 +179,7 @@ class LocationViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get'])
def all(self, request):
"""Get all adventures (public and owned) with optional collection filtering."""
"""Get all locations (public and owned) with optional collection filtering."""
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)

View file

@ -25,11 +25,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
def get_queryset(self):
user = self.request.user
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
# For individual adventure retrieval, include public locations, user's own locations and shared locations
return Lodging.objects.filter(
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Lodging.objects.filter(
Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')

View file

@ -15,7 +15,7 @@ class NoteViewSet(viewsets.ModelViewSet):
# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
# Prevent listing all locations
return Response({"detail": "Listing all notes is not allowed."},
status=status.HTTP_403_FORBIDDEN)
@ -39,12 +39,12 @@ class NoteViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures
# For individual adventure retrieval, include public locations
return Note.objects.filter(
Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')
else:
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Note.objects.filter(
Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at')

View file

@ -5,8 +5,6 @@ from rest_framework.response import Response
from django.conf import settings
import requests
from geopy.distance import geodesic
import time
class RecommendationsViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
@ -14,7 +12,7 @@ class RecommendationsViewSet(viewsets.ViewSet):
HEADERS = {'User-Agent': 'AdventureLog Server'}
def parse_google_places(self, places, origin):
adventures = []
locations = []
for place in places:
location = place.get('location', {})
@ -45,16 +43,16 @@ class RecommendationsViewSet(viewsets.ViewSet):
"distance_km": round(distance_km, 2),
}
adventures.append(adventure)
locations.append(adventure)
# Sort by distance ascending
adventures.sort(key=lambda x: x["distance_km"])
locations.sort(key=lambda x: x["distance_km"])
return adventures
return locations
def parse_overpass_response(self, data, request):
nodes = data.get('elements', [])
adventures = []
locations = []
all = request.query_params.get('all', False)
origin = None
@ -102,13 +100,13 @@ class RecommendationsViewSet(viewsets.ViewSet):
"powered_by": "osm"
}
adventures.append(adventure)
locations.append(adventure)
# Sort by distance if available
if origin:
adventures.sort(key=lambda x: x.get("distance_km") or float("inf"))
locations.sort(key=lambda x: x.get("distance_km") or float("inf"))
return adventures
return locations
def query_overpass(self, lat, lon, radius, category, request):
@ -172,8 +170,8 @@ class RecommendationsViewSet(viewsets.ViewSet):
print("Overpass API error:", e)
return Response({"error": "Failed to retrieve data from Overpass API."}, status=500)
adventures = self.parse_overpass_response(data, request)
return Response(adventures)
locations = self.parse_overpass_response(data, request)
return Response(locations)
def query_google_nearby(self, lat, lon, radius, category, request):
"""Query Google Places API (New) for nearby places"""
@ -216,9 +214,9 @@ class RecommendationsViewSet(viewsets.ViewSet):
places = data.get('places', [])
origin = (float(lat), float(lon))
adventures = self.parse_google_places(places, origin)
locations = self.parse_google_places(places, origin)
return Response(adventures)
return Response(locations)
except requests.exceptions.RequestException as e:
print(f"Google Places API error: {e}")

View file

@ -5,9 +5,7 @@ from rest_framework.response import Response
from worldtravel.models import Region, City, VisitedRegion, VisitedCity
from adventures.models import Location
from adventures.serializers import LocationSerializer
import requests
from adventures.geocoding import reverse_geocode
from adventures.geocoding import extractIsoCode
from django.conf import settings
from adventures.geocoding import search_google, search_osm
@ -47,14 +45,14 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
@action(detail=False, methods=['post'])
def mark_visited_region(self, request):
# searches through all of the users adventures, if the serialized data is_visited, is true, runs reverse geocode on the adventures and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
# searches through all of the users locations, if the serialized data is_visited, is true, runs reverse geocode on the locations and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
new_region_count = 0
new_regions = {}
new_city_count = 0
new_cities = {}
adventures = Location.objects.filter(user=self.request.user)
serializer = LocationSerializer(adventures, many=True)
for adventure, serialized_adventure in zip(adventures, serializer.data):
locations = Location.objects.filter(user=self.request.user)
serializer = LocationSerializer(locations, many=True)
for adventure, serialized_adventure in zip(locations, serializer.data):
if serialized_adventure['is_visited'] == True:
lat = adventure.latitude
lon = adventure.longitude

View file

@ -1,11 +1,9 @@
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 Location, Collection
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
from django.contrib.auth import get_user_model
User = get_user_model()
@ -26,7 +24,7 @@ class StatsViewSet(viewsets.ViewSet):
user.email = None
# get the counts for the user
adventure_count = Location.objects.filter(
location_count = Location.objects.filter(
user=user.id).count()
trips_count = Collection.objects.filter(
user=user.id).count()
@ -40,7 +38,7 @@ class StatsViewSet(viewsets.ViewSet):
user=user.id).values('region__country').distinct().count()
total_countries = Country.objects.count()
return Response({
'adventure_count': adventure_count,
'location_count': location_count,
'trips_count': trips_count,
'visited_city_count': visited_city_count,
'total_cities': total_cities,

View file

@ -10,7 +10,7 @@ class ActivityTypesView(viewsets.ViewSet):
@action(detail=False, methods=['get'])
def types(self, request):
"""
Retrieve a list of distinct activity types for adventures associated with the current user.
Retrieve a list of distinct activity types for locations associated with the current user.
Args:
request (HttpRequest): The HTTP request object.

View file

@ -1,12 +1,10 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Q
from adventures.models import Transportation
from adventures.serializers import TransportationSerializer
from rest_framework.exceptions import PermissionDenied
from adventures.permissions import IsOwnerOrSharedWithFullAccess
from rest_framework.permissions import IsAuthenticated
class TransportationViewSet(viewsets.ModelViewSet):
queryset = Transportation.objects.all()
@ -25,11 +23,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
def get_queryset(self):
user = self.request.user
if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures
# For individual adventure retrieval, include public locations, user's own locations and shared locations
return Transportation.objects.filter(
Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures
# For other actions, include user's own locations and shared locations
return Transportation.objects.filter(
Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at')

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "AdventureLog-Ergebnisse",
"online_results": "Online-Ergebnisse",
"public_adventures": "Öffentliche Abenteuer"
"public_adventures": "Öffentliche Abenteuer",
"cities": "Städte",
"countries": "Länder",
"found": "gefunden",
"result": "Ergebnis",
"results": "Ergebnisse",
"try_searching_desc": "Versuchen Sie, nach Abenteuern, Sammlungen, Ländern, Regionen, Städten oder Nutzern zu suchen."
},
"map": {
"add_adventure": "Neues Abenteuer hinzufügen",

View file

@ -589,7 +589,13 @@
"search": {
"adventurelog_results": "AdventureLog Results",
"public_adventures": "Public Adventures",
"online_results": "Online Results"
"online_results": "Online Results",
"result": "Result",
"results": "Results",
"found": "found",
"try_searching_desc": "Try searching for adventures, collections, countries, regions, cities, or users.",
"countries": "Countries",
"cities": "Cities"
},
"map": {
"view_details": "View Details",

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "Resultados del registro de aventuras",
"online_results": "Resultados en línea",
"public_adventures": "Aventuras públicas"
"public_adventures": "Aventuras públicas",
"cities": "Ciudades",
"countries": "Países",
"found": "encontró",
"result": "Resultado",
"results": "Resultados",
"try_searching_desc": "Intente buscar aventuras, colecciones, países, regiones, ciudades o usuarios."
},
"map": {
"add_adventure": "Agregar nueva aventura",

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "Résultats dans AdventureLog",
"online_results": "Résultats en ligne",
"public_adventures": "Aventures publiques"
"public_adventures": "Aventures publiques",
"cities": "Villes",
"countries": "Pays",
"found": "trouvé",
"result": "Résultat",
"results": "Résultats",
"try_searching_desc": "Essayez de rechercher des aventures, des collections, des pays, des régions, des villes ou des utilisateurs."
},
"map": {
"add_adventure": "Ajouter une nouvelle aventure",

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "Risultati di AdventureLog",
"online_results": "Risultati in linea",
"public_adventures": "Avventure pubbliche"
"public_adventures": "Avventure pubbliche",
"cities": "Città",
"countries": "Paesi",
"found": "trovato",
"result": "Risultato",
"results": "Risultati",
"try_searching_desc": "Prova a cercare avventure, collezioni, paesi, regioni, città o utenti."
},
"map": {
"add_adventure": "Aggiungi nuova avventura",

View file

@ -469,7 +469,13 @@
"search": {
"adventurelog_results": "Adventurelog 결과",
"online_results": "온라인 결과",
"public_adventures": "공개 모험"
"public_adventures": "공개 모험",
"cities": "도시",
"countries": "국가",
"found": "설립하다",
"result": "결과",
"results": "결과",
"try_searching_desc": "모험, 컬렉션, 국가, 지역, 도시 또는 사용자를 검색하십시오."
},
"settings": {
"about_this_background": "이 배경에 대해",

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "AdventureLog resultaten",
"online_results": "Online resultaten",
"public_adventures": "Openbare avonturen"
"public_adventures": "Openbare avonturen",
"cities": "Steden",
"countries": "Landen",
"found": "gevonden",
"result": "Resultaat",
"results": "Resultaat",
"try_searching_desc": "Probeer op zoek naar avonturen, collecties, landen, regio's, steden of gebruikers."
},
"map": {
"add_adventure": "Voeg nieuw avontuur toe",

View file

@ -589,7 +589,13 @@
"search": {
"adventurelog_results": "AdventureLog-resultater",
"public_adventures": "Offentlige eventyr",
"online_results": "Nettresultater"
"online_results": "Nettresultater",
"cities": "Byer",
"countries": "Land",
"found": "funnet",
"result": "Resultat",
"results": "Resultater",
"try_searching_desc": "Prøv å søke etter eventyr, samlinger, land, regioner, byer eller brukere."
},
"map": {
"view_details": "Vis detaljer",

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "Wyniki AdventureLog",
"public_adventures": "Publiczne podróże",
"online_results": "Wyniki online"
"online_results": "Wyniki online",
"cities": "Miasta",
"countries": "Kraje",
"found": "znaleziony",
"result": "Wynik",
"results": "Wyniki",
"try_searching_desc": "Spróbuj szukać przygód, kolekcji, krajów, regionów, miast lub użytkowników."
},
"map": {
"view_details": "Zobacz szczegóły",

View file

@ -589,7 +589,13 @@
"search": {
"adventurelog_results": "Результаты AdventureLog",
"public_adventures": "Публичные приключения",
"online_results": "Онлайн результаты"
"online_results": "Онлайн результаты",
"cities": "Города",
"countries": "Страны",
"found": "найденный",
"result": "Результат",
"results": "Результаты",
"try_searching_desc": "Попробуйте искать приключения, коллекции, страны, регионы, города или пользователей."
},
"map": {
"view_details": "Подробности",

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "AdventureLog-resultat",
"online_results": "Online resultat",
"public_adventures": "Offentliga äventyr"
"public_adventures": "Offentliga äventyr",
"cities": "Städer",
"countries": "Länder",
"found": "funnna",
"result": "Resultat",
"results": "Resultat",
"try_searching_desc": "Försök att söka efter äventyr, samlingar, länder, regioner, städer eller användare."
},
"map": {
"add_adventure": "Lägg till nytt äventyr",

View file

@ -572,7 +572,13 @@
"search": {
"adventurelog_results": "AdventureLog 结果",
"online_results": "在线结果",
"public_adventures": "已公开的冒险"
"public_adventures": "已公开的冒险",
"cities": "城市",
"countries": "国家",
"found": "成立",
"result": "结果",
"results": "结果",
"try_searching_desc": "尝试搜索冒险,收藏,国家,地区,城市或用户。"
},
"map": {
"add_adventure": "添加新冒险",

View file

@ -51,9 +51,9 @@
: user?.username}!
</h1>
<p class="text-lg text-base-content/60 mt-2">
{#if stats.adventure_count > 0}
{#if stats.location_count > 0}
{$t('dashboard.welcome_text_1')}
<span class="font-semibold text-primary">{stats.adventure_count}</span>
<span class="font-semibold text-primary">{stats.location_count}</span>
{$t('dashboard.welcome_text_2')}
{:else}
{$t('dashboard.welcome_text_3')}
@ -171,7 +171,7 @@
</div>
<a href="/locations" class="btn btn-ghost gap-2">
{$t('dashboard.view_all')}
<span class="badge badge-primary">{stats.adventure_count}</span>
<span class="badge badge-primary">{stats.location_count}</span>
</a>
</div>

View file

@ -23,7 +23,7 @@
visited_country_count: number;
total_regions: number;
trips_count: number;
adventure_count: number;
location_count: number;
visited_region_count: number;
total_countries: number;
visited_city_count: number;
@ -48,34 +48,34 @@
// Achievement levels
$: achievementLevel =
(stats?.adventure_count ?? 0) >= 100
(stats?.location_count ?? 0) >= 100
? 'Legendary Explorer'
: (stats?.adventure_count ?? 0) >= 75
: (stats?.location_count ?? 0) >= 75
? 'World Wanderer'
: (stats?.adventure_count ?? 0) >= 50
: (stats?.location_count ?? 0) >= 50
? 'Explorer Master'
: (stats?.adventure_count ?? 0) >= 35
: (stats?.location_count ?? 0) >= 35
? 'Globetrotter'
: (stats?.adventure_count ?? 0) >= 25
: (stats?.location_count ?? 0) >= 25
? 'Seasoned Traveler'
: (stats?.adventure_count ?? 0) >= 15
: (stats?.location_count ?? 0) >= 15
? 'Adventure Seeker'
: (stats?.adventure_count ?? 0) >= 10
: (stats?.location_count ?? 0) >= 10
? 'Trailblazer'
: (stats?.adventure_count ?? 0) >= 5
: (stats?.location_count ?? 0) >= 5
? 'Journey Starter'
: (stats?.adventure_count ?? 0) >= 1
: (stats?.location_count ?? 0) >= 1
? 'Travel Enthusiast'
: 'New Explorer';
$: achievementColor =
(stats?.adventure_count ?? 0) >= 50
(stats?.location_count ?? 0) >= 50
? 'text-warning'
: (stats?.adventure_count ?? 0) >= 25
: (stats?.location_count ?? 0) >= 25
? 'text-success'
: (stats?.adventure_count ?? 0) >= 10
: (stats?.location_count ?? 0) >= 10
? 'text-info'
: (stats?.adventure_count ?? 0) >= 5
: (stats?.location_count ?? 0) >= 5
? 'text-secondary'
: 'text-primary';
</script>
@ -159,7 +159,7 @@
{/if}
<!-- User rank achievement -->
{#if stats && stats.adventure_count > 0}
{#if stats && stats.location_count > 0}
<div class="flex items-center justify-center gap-2 text-base-content/70">
<Award class="w-5 h-5" />
<span class={`text-lg ${achievementColor}`}>{achievementLevel}</span>
@ -191,7 +191,7 @@
<div class="text-primary/70 font-medium text-sm uppercase tracking-wide">
{$t('locations.locations')}
</div>
<div class="text-4xl font-bold text-primary">{stats.adventure_count}</div>
<div class="text-4xl font-bold text-primary">{stats.location_count}</div>
<div class="text-primary/60 mt-2 flex items-center gap-1">
<TrendingUp class="w-4 h-4" />
{achievementLevel}

View file

@ -33,7 +33,7 @@ export const load = (async (event) => {
let data = await res.json();
return {
adventures: data.adventures,
locations: data.locations,
collections: data.collections,
users: data.users,
countries: data.countries,

View file

@ -1,5 +1,5 @@
<script lang="ts">
import AdventureCard from '$lib/components/LocationCard.svelte';
import LocationCard from '$lib/components/LocationCard.svelte';
import RegionCard from '$lib/components/RegionCard.svelte';
import CityCard from '$lib/components/CityCard.svelte';
import CountryCard from '$lib/components/CountryCard.svelte';
@ -27,7 +27,7 @@
$: query = $page.url.searchParams.get('query') ?? '';
// Assign updated results from data, so when data changes, the displayed items update:
$: adventures = data.adventures as Location[];
$: locations = data.locations as Location[];
$: collections = data.collections as Collection[];
$: users = data.users as User[];
$: countries = data.countries as Country[];
@ -38,7 +38,7 @@
// new stats
$: totalResults =
adventures.length +
locations.length +
collections.length +
users.length +
countries.length +
@ -64,11 +64,13 @@
<h1
class="text-3xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent"
>
Search{query ? `: ${query}` : ''}
{$t('navbar.search')}{query ? `: ${query}` : ''}
</h1>
{#if hasResults}
<p class="text-sm text-base-content/60">
{totalResults} result{totalResults !== 1 ? 's' : ''} found
{totalResults}
{totalResults !== 1 ? $t('search.results') : $t('search.result')}
{$t('search.found')}
</p>
{/if}
</div>
@ -87,22 +89,22 @@
{$t('adventures.no_results')}
</h3>
<p class="text-base-content/50 text-center max-w-md">
Try searching for adventures, collections, countries, regions, cities, or users.
{$t('search.try_searching_desc')}
</p>
</div>
{:else}
{#if adventures.length > 0}
{#if locations.length > 0}
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-primary/10 rounded-lg">
<SearchIcon class="w-6 h-6 text-primary" />
</div>
<h2 class="text-2xl font-bold">Adventures</h2>
<div class="badge badge-primary">{adventures.length}</div>
<h2 class="text-2xl font-bold">{$t('locations.locations')}</h2>
<div class="badge badge-primary">{locations.length}</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{#each adventures as adventure}
<AdventureCard {adventure} user={null} />
{#each locations as adventure}
<LocationCard {adventure} user={null} />
{/each}
</div>
</div>
@ -115,7 +117,7 @@
<!-- you can replace with a CollectionIcon -->
<SearchIcon class="w-6 h-6 text-secondary" />
</div>
<h2 class="text-2xl font-bold">Collections</h2>
<h2 class="text-2xl font-bold">{$t('navbar.collections')}</h2>
<div class="badge badge-secondary">{collections.length}</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -133,7 +135,7 @@
<!-- you can replace with a GlobeIcon -->
<SearchIcon class="w-6 h-6 text-accent" />
</div>
<h2 class="text-2xl font-bold">Countries</h2>
<h2 class="text-2xl font-bold">{$t('search.countries')}</h2>
<div class="badge badge-accent">{countries.length}</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -151,7 +153,7 @@
<!-- MapIcon -->
<SearchIcon class="w-6 h-6 text-info" />
</div>
<h2 class="text-2xl font-bold">Regions</h2>
<h2 class="text-2xl font-bold">{$t('map.regions')}</h2>
<div class="badge badge-info">{regions.length}</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -172,7 +174,7 @@
<!-- CityIcon -->
<SearchIcon class="w-6 h-6 text-warning" />
</div>
<h2 class="text-2xl font-bold">Cities</h2>
<h2 class="text-2xl font-bold">{$t('search.cities')}</h2>
<div class="badge badge-warning">{cities.length}</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
@ -190,7 +192,7 @@
<!-- UserIcon -->
<SearchIcon class="w-6 h-6 text-success" />
</div>
<h2 class="text-2xl font-bold">Users</h2>
<h2 class="text-2xl font-bold">{$t('navbar.users')}</h2>
<div class="badge badge-success">{users.length}</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">