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:
parent
151c76dbd1
commit
b5931c6c23
10 changed files with 275 additions and 228 deletions
|
@ -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',
|
||||
),
|
||||
]
|
|
@ -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"
|
||||
|
|
|
@ -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))}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -48,15 +48,25 @@
|
|||
|
||||
// Achievement levels
|
||||
$: achievementLevel =
|
||||
(stats?.adventure_count ?? 0) >= 50
|
||||
(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) >= 10
|
||||
: (stats?.adventure_count ?? 0) >= 15
|
||||
? 'Adventure Seeker'
|
||||
: (stats?.adventure_count ?? 0) >= 10
|
||||
? 'Trailblazer'
|
||||
: (stats?.adventure_count ?? 0) >= 5
|
||||
? 'Journey Starter'
|
||||
: 'Travel Enthusiast';
|
||||
: (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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
|
@ -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,6 +248,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Map Section -->
|
||||
{#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">
|
||||
|
@ -309,6 +296,7 @@
|
|||
</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,6 +420,7 @@
|
|||
</div> -->
|
||||
|
||||
<!-- Quick Actions -->
|
||||
{#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}
|
||||
|
@ -448,6 +437,7 @@
|
|||
Clear All Filters
|
||||
</button> -->
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue