feat: Firebase integration, 3-step new trip form, full country list, and UI polish
- Connect Firestore for journal entries and visited countries (real-time onSnapshot) - Connect Firebase Storage for photo uploads - Add NewEntryForm: 3-step flow (trip details → photos → reflection questions) - Expand country list to full world-atlas dataset (~240 countries) matching the map - Filter city suggestions by selected country in both NewEntryForm and EditForm - Redesign StatsPanel as floating horizontal card with donut chart and progress bar - Center timeline layout with responsive side margins - Replace "entry" language with "trip" throughout (Add trip, Save trip, Delete trip) - Remove footer from Layout Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { db } from '../firebase.js';
|
||||
import {
|
||||
collection, onSnapshot, addDoc, updateDoc, deleteDoc, doc, serverTimestamp
|
||||
} from 'firebase/firestore';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
@@ -14,110 +18,28 @@ import { writable } from 'svelte/store';
|
||||
* }} JournalEntry
|
||||
*/
|
||||
|
||||
/** @type {JournalEntry[]} */
|
||||
const mockEntries = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'First Day in Tokyo',
|
||||
date: '2024-03-15',
|
||||
location: { country: 'Japan', cities: ['Tokyo'] },
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1513407030348-c983a97b98d8?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1490806843957-31f4c9a91c65?w=600&q=80',
|
||||
],
|
||||
transport: 'flight',
|
||||
tripType: 'solo',
|
||||
days: 5,
|
||||
memo: 'Got completely lost in Shinjuku — stumbled into a tiny ramen shop with no English menu. The chashu just melted. Worth every wrong turn.',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Arashiyama Bamboo Grove',
|
||||
date: '2024-03-18',
|
||||
location: { country: 'Japan', cities: ['Kyoto'] },
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1528360983277-13d401cdc186?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1545569341-9eb8b30979d9?w=600&q=80',
|
||||
],
|
||||
transport: 'train',
|
||||
tripType: 'friends',
|
||||
days: 3,
|
||||
memo: 'Arrived at 6am before the crowds. Just me and the wind moving through the bamboo. One of those moments you keep coming back to.',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Sunset on Montmartre',
|
||||
date: '2024-06-02',
|
||||
location: { country: 'France', cities: ['Paris'] },
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1502602898657-3e91760cbb34?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1499856871958-5b9627545d1a?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1511739001486-6bfe10ce785f?w=600&q=80',
|
||||
],
|
||||
transport: 'flight',
|
||||
tripType: 'solo',
|
||||
days: 7,
|
||||
memo: 'Watched the whole city turn orange from the steps of Sacré-Cœur. A street musician was playing La Vie en Rose. Cliché, perfect.',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'Inside La Sagrada Família',
|
||||
date: '2024-06-10',
|
||||
location: { country: 'Spain', cities: ['Barcelona'] },
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1523531294919-4bcd7c65e216?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1583422409516-2895a77efded?w=600&q=80',
|
||||
],
|
||||
transport: 'flight',
|
||||
tripType: 'friends',
|
||||
days: 4,
|
||||
memo: 'Nothing prepares you for the light inside. The stained glass turns the whole nave into a kaleidoscope. Gaudí was building a forest.',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'Central Park in Fall',
|
||||
date: '2023-10-20',
|
||||
location: { country: 'USA', cities: ['New York'] },
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1534430480872-3498386e7856?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1485871981521-5b1fd3805345?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1522083165195-3424ed129620?w=600&q=80',
|
||||
],
|
||||
transport: 'car',
|
||||
tripType: 'friends',
|
||||
days: 6,
|
||||
memo: 'Peak foliage. Joggers, picnics, a guy playing saxophone near Bethesda Fountain. Hard to believe a city this big wraps around this much quiet.',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'Wat Pho Reclining Buddha',
|
||||
date: '2024-01-08',
|
||||
location: { country: 'Thailand', cities: ['Bangkok'] },
|
||||
photos: [
|
||||
'https://images.unsplash.com/photo-1563492065599-3520f775eeed?w=600&q=80',
|
||||
'https://images.unsplash.com/photo-1552465011-b4e21bf6e79a?w=600&q=80',
|
||||
],
|
||||
transport: 'ship',
|
||||
tripType: 'solo',
|
||||
days: 2,
|
||||
memo: 'Stood in front of the 45m golden Buddha for a long time. The mother-of-pearl inlay on the soles of the feet is impossibly detailed.',
|
||||
},
|
||||
];
|
||||
export const journals = writable(/** @type {JournalEntry[]} */([]));
|
||||
export const journalsLoading = writable(true);
|
||||
|
||||
export const journals = writable(mockEntries);
|
||||
const entriesRef = collection(db, 'entries');
|
||||
|
||||
onSnapshot(entriesRef, (snap) => {
|
||||
journals.set(snap.docs.map(d => ({ id: d.id, ...d.data() })));
|
||||
journalsLoading.set(false);
|
||||
});
|
||||
|
||||
/** @param {Omit<JournalEntry, 'id'>} entry */
|
||||
export function addJournal(entry) {
|
||||
journals.update((entries) => [...entries, { ...entry, id: crypto.randomUUID() }]);
|
||||
export async function addJournal(entry) {
|
||||
await addDoc(entriesRef, { ...entry, createdAt: serverTimestamp() });
|
||||
}
|
||||
|
||||
/** @param {string} id */
|
||||
export function removeJournal(id) {
|
||||
journals.update((entries) => entries.filter((e) => e.id !== id));
|
||||
export async function removeJournal(id) {
|
||||
await deleteDoc(doc(db, 'entries', id));
|
||||
}
|
||||
|
||||
/** @param {JournalEntry} updated */
|
||||
export function updateJournal(updated) {
|
||||
journals.update((entries) => entries.map((e) => e.id === updated.id ? updated : e));
|
||||
export async function updateJournal(updated) {
|
||||
const { id, ...data } = updated;
|
||||
await updateDoc(doc(db, 'entries', id), data);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user