1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-18 20:39:36 +02:00

refactor(worldtravel): remove insert_id fields from city, country, and region models; update related migration

feat(search): enhance search results display with total results count and improved layout
fix(profile): update achievement levels based on adventure count; remove unused quick actions
refactor(shared): delete unused shared collections route and related components
feat(worldtravel): improve interactive map functionality and layout in world travel detail view
This commit is contained in:
Sean Morley 2025-06-14 14:05:30 -04:00
parent 151c76dbd1
commit b5931c6c23
10 changed files with 275 additions and 228 deletions

View file

@ -0,0 +1,25 @@
# Generated by Django 5.2.1 on 2025-06-14 17:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('worldtravel', '0015_city_insert_id_country_insert_id_region_insert_id'),
]
operations = [
migrations.RemoveField(
model_name='city',
name='insert_id',
),
migrations.RemoveField(
model_name='country',
name='insert_id',
),
migrations.RemoveField(
model_name='region',
name='insert_id',
),
]

View file

@ -17,7 +17,6 @@ class Country(models.Model):
capital = models.CharField(max_length=100, blank=True, null=True)
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
insert_id = models.UUIDField(unique=False, blank=True, null=True)
class Meta:
verbose_name = "Country"
@ -32,7 +31,6 @@ class Region(models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE)
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
insert_id = models.UUIDField(unique=False, blank=True, null=True)
def __str__(self):
return self.name
@ -43,7 +41,6 @@ class City(models.Model):
region = models.ForeignKey(Region, on_delete=models.CASCADE)
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
insert_id = models.UUIDField(unique=False, blank=True, null=True)
class Meta:
verbose_name_plural = "Cities"

View file

@ -277,7 +277,7 @@
on:click={() => goto(`/adventures/${adventure.id}`)}
>
<Launch class="w-4 h-4" />
View Details
{$t('adventures.open_details')}
</button>
{#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}

View file

@ -5,7 +5,6 @@
// Icons
import Account from '~icons/mdi/account';
import MapMarker from '~icons/mdi/map-marker';
import Share from '~icons/mdi/share-variant';
import Shield from '~icons/mdi/shield-account';
import Settings from '~icons/mdi/cog';
import Logout from '~icons/mdi/logout';
@ -37,12 +36,6 @@
label: 'navbar.my_adventures',
section: 'main'
},
{
path: '/shared',
icon: Share,
label: 'navbar.shared_with_me',
section: 'main'
},
{
path: '/settings',
icon: Settings,

View file

@ -51,11 +51,11 @@
}
];
const stats = [
{ label: 'Countries Tracked', value: '195+', icon: GlobeIcon },
{ label: 'Adventures Logged', value: '10K+', icon: CalendarIcon },
{ label: 'Active Travelers', value: '5K+', icon: StarIcon }
];
// const stats = [
// { label: 'Countries Tracked', value: '195+', icon: GlobeIcon },
// { label: 'Adventures Logged', value: '10K+', icon: CalendarIcon },
// { label: 'Active Travelers', value: '5K+', icon: StarIcon }
// ];
</script>
<div class="min-h-screen bg-gradient-to-br from-base-200 via-base-100 to-base-200">
@ -167,7 +167,7 @@
</div>
<!-- Stats -->
<div class="grid grid-cols-3 gap-6 pt-8 border-t border-base-300">
<!-- <div class="grid grid-cols-3 gap-6 pt-8 border-t border-base-300">
{#each stats as stat}
<div class="text-center">
<div class="flex justify-center mb-2">
@ -179,7 +179,7 @@
<div class="text-sm text-base-content/60">{stat.label}</div>
</div>
{/each}
</div>
</div> -->
</div>
<!-- Right Content - Hero Image -->
@ -317,7 +317,7 @@
on:click={() => goto('/signup')}
class="btn btn-lg bg-white text-primary hover:bg-white/90 gap-3 shadow-lg group"
>
Get Started Free
Get Started
<ChevronRight class="w-4 h-4 group-hover:translate-x-1 transition-transform" />
</button>
<button

View file

@ -48,15 +48,25 @@
// Achievement levels
$: achievementLevel =
(stats?.adventure_count ?? 0) >= 50
? 'Explorer Master'
: (stats?.adventure_count ?? 0) >= 25
? 'Seasoned Traveler'
: (stats?.adventure_count ?? 0) >= 10
? 'Adventure Seeker'
: (stats?.adventure_count ?? 0) >= 5
? 'Journey Starter'
: 'Travel Enthusiast';
(stats?.adventure_count ?? 0) >= 100
? 'Legendary Explorer'
: (stats?.adventure_count ?? 0) >= 75
? 'World Wanderer'
: (stats?.adventure_count ?? 0) >= 50
? 'Explorer Master'
: (stats?.adventure_count ?? 0) >= 35
? 'Globetrotter'
: (stats?.adventure_count ?? 0) >= 25
? 'Seasoned Traveler'
: (stats?.adventure_count ?? 0) >= 15
? 'Adventure Seeker'
: (stats?.adventure_count ?? 0) >= 10
? 'Trailblazer'
: (stats?.adventure_count ?? 0) >= 5
? 'Journey Starter'
: (stats?.adventure_count ?? 0) >= 1
? 'Travel Enthusiast'
: 'New Explorer';
$: achievementColor =
(stats?.adventure_count ?? 0) >= 50
@ -155,14 +165,6 @@
<span class={`text-lg ${achievementColor}`}>{achievementLevel}</span>
</div>
{/if}
<!-- Quick Actions -->
<div class="flex gap-3 mt-6">
<button class="btn btn-primary gap-2">
<Share class="w-4 h-4" />
Share Profile
</button>
</div>
</div>
</div>
</div>
@ -400,7 +402,7 @@
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{#each collections as collection}
<div class="collection-card">
<CollectionCard {collection} type={''} user={data.user} />
<CollectionCard {collection} type={''} user={null} />
</div>
{/each}
</div>

View file

@ -18,6 +18,7 @@
VisitedRegion,
VisitedCity
} from '$lib/types';
import SearchIcon from '~icons/mdi/magnify';
export let data: PageData;
@ -34,71 +35,171 @@
$: cities = data.cities as City[];
$: visited_regions = data.visited_regions as VisitedRegion[];
$: visited_cities = data.visited_cities as VisitedCity[];
// new stats
$: totalResults =
adventures.length +
collections.length +
users.length +
countries.length +
regions.length +
cities.length;
$: hasResults = totalResults > 0;
</script>
<h1 class="text-4xl font-bold text-center m-4">Search{query ? `: ${query}` : ''}</h1>
{#if adventures.length > 0}
<h2 class="text-3xl font-bold text-center m-4">Adventures</h2>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each adventures as adventure}
<AdventureCard {adventure} user={null} />
{/each}
</div>
{/if}
{#if collections.length > 0}
<h2 class="text-3xl font-bold text-center m-4">Collections</h2>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each collections as collection}
<CollectionCard {collection} type="" />
{/each}
</div>
{/if}
{#if countries.length > 0}
<h2 class="text-3xl font-bold text-center m-4">Countries</h2>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each countries as country}
<CountryCard {country} />
{/each}
</div>
{/if}
{#if regions.length > 0}
<h2 class="text-3xl font-bold text-center m-4">Regions</h2>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each regions as region}
<RegionCard {region} visited={visited_regions.some((vr) => vr.region === region.id)} />
{/each}
</div>
{/if}
{#if cities.length > 0}
<h2 class="text-3xl font-bold text-center m-4">Cities</h2>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each cities as city}
<CityCard {city} visited={visited_cities.some((vc) => vc.city === city.id)} />
{/each}
</div>
{/if}
{#if users.length > 0}
<h2 class="text-3xl font-bold text-center m-4">Users</h2>
<div class="flex flex-wrap gap-4 mr-4 ml-4 justify-center content-center">
{#each users as user}
<UserCard {user} />
{/each}
</div>
{/if}
{#if adventures.length === 0 && regions.length === 0 && cities.length === 0 && countries.length === 0 && collections.length === 0 && users.length === 0}
<p class="text-center text-lg m-4">
{$t('adventures.no_results')}
</p>
{/if}
<svelte:head>
<title>Search: {query}</title>
<meta name="description" content="AdventureLog global search results for {query}" />
</svelte:head>
<div class="min-h-screen bg-gradient-to-br from-base-200 via-base-100 to-base-200">
<!-- Header -->
<div class="sticky top-0 z-40 bg-base-100/80 backdrop-blur-lg border-b border-base-300">
<div class="container mx-auto px-6 py-4 flex items-center">
<div class="flex items-center gap-3">
<div class="p-2 bg-primary/10 rounded-xl">
<SearchIcon class="w-8 h-8 text-primary" />
</div>
<div>
<h1
class="text-3xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent"
>
Search{query ? `: ${query}` : ''}
</h1>
{#if hasResults}
<p class="text-sm text-base-content/60">
{totalResults} result{totalResults !== 1 ? 's' : ''} found
</p>
{/if}
</div>
</div>
</div>
</div>
<!-- Main content -->
<div class="container mx-auto px-6 py-8">
{#if !hasResults}
<div class="flex flex-col items-center justify-center py-16">
<div class="p-6 bg-base-200/50 rounded-2xl mb-6">
<SearchIcon class="w-16 h-16 text-base-content/30" />
</div>
<h3 class="text-xl font-semibold text-base-content/70 mb-2">
{$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.
</p>
</div>
{:else}
{#if adventures.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>
</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}
</div>
</div>
{/if}
{#if collections.length > 0}
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-secondary/10 rounded-lg">
<!-- you can replace with a CollectionIcon -->
<SearchIcon class="w-6 h-6 text-secondary" />
</div>
<h2 class="text-2xl font-bold">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">
{#each collections as collection}
<CollectionCard {collection} type="" user={null} />
{/each}
</div>
</div>
{/if}
{#if countries.length > 0}
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-accent/10 rounded-lg">
<!-- you can replace with a GlobeIcon -->
<SearchIcon class="w-6 h-6 text-accent" />
</div>
<h2 class="text-2xl font-bold">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">
{#each countries as country}
<CountryCard {country} />
{/each}
</div>
</div>
{/if}
{#if regions.length > 0}
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-info/10 rounded-lg">
<!-- MapIcon -->
<SearchIcon class="w-6 h-6 text-info" />
</div>
<h2 class="text-2xl font-bold">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">
{#each regions as region}
<RegionCard
{region}
visited={visited_regions.some((vr) => vr.region === region.id)}
/>
{/each}
</div>
</div>
{/if}
{#if cities.length > 0}
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-warning/10 rounded-lg">
<!-- CityIcon -->
<SearchIcon class="w-6 h-6 text-warning" />
</div>
<h2 class="text-2xl font-bold">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">
{#each cities as city}
<CityCard {city} visited={visited_cities.some((vc) => vc.city === city.id)} />
{/each}
</div>
</div>
{/if}
{#if users.length > 0}
<div class="mb-12">
<div class="flex items-center gap-3 mb-6">
<div class="p-2 bg-success/10 rounded-lg">
<!-- UserIcon -->
<SearchIcon class="w-6 h-6 text-success" />
</div>
<h2 class="text-2xl font-bold">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">
{#each users as user}
<UserCard {user} />
{/each}
</div>
</div>
{/if}
{/if}
</div>
</div>

View file

@ -1,27 +0,0 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
if (!event.locals.user) {
return redirect(302, '/login');
} else {
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/collections/shared/`, {
headers: {
Cookie: `sessionid=${sessionId}`
}
});
if (!res.ok) {
return redirect(302, '/login');
} else {
return {
props: {
collections: await res.json()
}
};
}
}
}) satisfies PageServerLoad;

View file

@ -1,34 +0,0 @@
<script lang="ts">
import { goto } from '$app/navigation';
import CollectionCard from '$lib/components/CollectionCard.svelte';
import type { Collection } from '$lib/types';
import type { PageData } from './$types';
import { t } from 'svelte-i18n';
export let data: PageData;
console.log(data);
let collections: Collection[] = data.props.collections;
</script>
{#if collections.length > 0}
<div class="flex flex-wrap gap-4 mr-4 justify-center content-center">
{#each collections as collection}
<CollectionCard type="viewonly" {collection} />
{/each}
</div>
{:else}
<p class="text-center font-semibold text-xl mt-6">
{$t('share.no_shared_found')}
{#if data.user && !data.user?.public_profile}
<p>{$t('share.set_public')}</p>
<button class="btn btn-neutral mt-4" on:click={() => goto('/settings')}
>{$t('share.go_to_settings')}</button
>
{/if}
</p>
{/if}
<svelte:head>
<title>Shared Collections</title>
<meta name="description" content="Collections shared with you by other users." />
</svelte:head>

View file

@ -208,20 +208,6 @@
</button>
{/if}
</div>
<!-- Map Toggle -->
<button
class="btn btn-outline gap-2 {showGeo ? 'btn-active' : ''}"
on:click={() => (showGeo = !showGeo)}
>
{#if showGeo}
<Map class="w-4 h-4" />
<span class="hidden sm:inline">Hide Labels</span>
{:else}
<Map class="w-4 h-4" />
<span class="hidden sm:inline">Show Labels</span>
{/if}
</button>
</div>
<!-- Filter Chips -->
@ -262,53 +248,55 @@
</div>
<!-- Map Section -->
<div class="container mx-auto px-6 py-4">
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<Map class="w-5 h-5 text-primary" />
<h2 class="text-lg font-semibold">Interactive Map</h2>
</div>
<div class="flex items-center gap-2 text-sm text-base-content/60">
<div class="flex items-center gap-1">
<div class="w-3 h-3 bg-green-200 rounded-full border"></div>
<span>Visited</span>
{#if regions.some((region) => region.latitude && region.longitude)}
<div class="container mx-auto px-6 py-4">
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-4">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<Map class="w-5 h-5 text-primary" />
<h2 class="text-lg font-semibold">Interactive Map</h2>
</div>
<div class="flex items-center gap-1">
<div class="w-3 h-3 bg-red-200 rounded-full border"></div>
<span>Not Visited</span>
<div class="flex items-center gap-2 text-sm text-base-content/60">
<div class="flex items-center gap-1">
<div class="w-3 h-3 bg-green-200 rounded-full border"></div>
<span>Visited</span>
</div>
<div class="flex items-center gap-1">
<div class="w-3 h-3 bg-red-200 rounded-full border"></div>
<span>Not Visited</span>
</div>
</div>
</div>
<MapLibre
style={getBasemapUrl()}
class="aspect-[16/10] w-full rounded-lg"
standardControls
center={[regions[0]?.longitude || 0, regions[0]?.latitude || 0]}
zoom={6}
>
{#each regions as region}
{#if region.latitude && region.longitude && showGeo}
<Marker
lngLat={[region.longitude, region.latitude]}
class="grid px-2 py-1 place-items-center rounded-full border border-gray-200 {visitedRegions.some(
(visitedRegion) => visitedRegion.region === region.id
)
? 'bg-green-200'
: 'bg-red-200'} text-black focus:outline-6 focus:outline-black"
on:click={togleVisited(region)}
>
<span class="text-xs">
{region.name}
</span>
</Marker>
{/if}
{/each}
</MapLibre>
</div>
<MapLibre
style={getBasemapUrl()}
class="aspect-[16/10] w-full rounded-lg"
standardControls
center={[regions[0]?.longitude || 0, regions[0]?.latitude || 0]}
zoom={6}
>
{#each regions as region}
{#if region.latitude && region.longitude && showGeo}
<Marker
lngLat={[region.longitude, region.latitude]}
class="grid px-2 py-1 place-items-center rounded-full border border-gray-200 {visitedRegions.some(
(visitedRegion) => visitedRegion.region === region.id
)
? 'bg-green-200'
: 'bg-red-200'} text-black focus:outline-6 focus:outline-black"
on:click={togleVisited(region)}
>
<span class="text-xs">
{region.name}
</span>
</Marker>
{/if}
{/each}
</MapLibre>
</div>
</div>
</div>
{/if}
<!-- Main Content -->
<div class="container mx-auto px-6 py-8">
@ -329,7 +317,7 @@
{:else}
<!-- Regions Grid -->
<div
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6"
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"
>
{#each filteredRegions as region}
<RegionCard
@ -432,22 +420,24 @@
</div> -->
<!-- Quick Actions -->
<div class="space-y-3">
<button class="btn btn-outline w-full gap-2" on:click={() => (showGeo = !showGeo)}>
{#if showGeo}
<Map class="w-4 h-4" />
Hide Map Labels
{:else}
<Map class="w-4 h-4" />
Show Map Labels
{/if}
</button>
{#if regions.some((region) => region.latitude && region.longitude)}
<div class="space-y-3">
<button class="btn btn-outline w-full gap-2" on:click={() => (showGeo = !showGeo)}>
{#if showGeo}
<Map class="w-4 h-4" />
Hide Map Labels
{:else}
<Map class="w-4 h-4" />
Show Map Labels
{/if}
</button>
<!-- <button class="btn btn-ghost w-full gap-2" on:click={clearFilters}>
<!-- <button class="btn btn-ghost w-full gap-2" on:click={clearFilters}>
<Clear class="w-4 h-4" />
Clear All Filters
</button> -->
</div>
</div>
{/if}
</div>
</div>
</div>