This commit is contained in:
Chaebean Yang 2025-06-09 20:32:13 +09:00
commit 8e6126c26f
6 changed files with 86 additions and 40 deletions

View File

@ -1,15 +1,15 @@
import { initializeApp, getApps, getApp } from 'firebase/app'; import { initializeApp, getApps, getApp } from 'firebase/app';
import { getDatabase } from 'firebase/database'; import { getDatabase } from 'firebase/database';
// Your web app's Firebase configuration // Firebase configuration
const firebaseConfig = { const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY, apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL, databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID appId: import.meta.env.VITE_FIREBASE_APP_ID
}; };
// Initialize Firebase (with duplicate app prevention) // Initialize Firebase (with duplicate app prevention)

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { Loader } from '@googlemaps/js-api-loader'; import { Loader } from '@googlemaps/js-api-loader';
import { fetchUnsplashPhoto } from '../../services/unsplash';
// Extend the PlaceResult type to include our custom photoUrl // Extend the PlaceResult type to include our custom photoUrl
interface ExtendedPlaceResult extends google.maps.places.PlaceResult { interface ExtendedPlaceResult extends google.maps.places.PlaceResult {
@ -58,25 +59,12 @@
const autocomplete = new google.maps.places.Autocomplete(input, autocompleteOptions); const autocomplete = new google.maps.places.Autocomplete(input, autocompleteOptions);
autocomplete.setFields(['name', 'formatted_address', 'photos', 'place_id', 'geometry']); autocomplete.setFields(['name', 'formatted_address', 'photos', 'place_id', 'geometry']);
autocomplete.addListener('place_changed', () => { autocomplete.addListener('place_changed', async () => {
const place = autocomplete.getPlace() as ExtendedPlaceResult; const place = autocomplete.getPlace() as ExtendedPlaceResult;
if (place && place.name) { if (place && place.name) {
// If the place has photos, get the URL for the first photo // Use Unsplash for the photo
if (place.photos && place.photos.length > 0) { const unsplashUrl = await fetchUnsplashPhoto(place.name);
try { place.photoUrl = unsplashUrl || '/placeholder.jpeg';
const photoOptions = {
maxWidth: 400,
maxHeight: 300
};
place.photoUrl = place.photos[0].getUrl(photoOptions);
} catch (error) {
console.error('Error getting photo URL:', error);
place.photoUrl = '/placeholder.jpeg';
}
}
else {
place.photoUrl = '/placeholder.jpeg';
}
selectedPlace = place; selectedPlace = place;
lastSelectedPlaceName = input.value.trim(); lastSelectedPlaceName = input.value.trim();

View File

@ -145,6 +145,28 @@
} }
} }
const UNSPLASH_ACCESS_KEY = import.meta.env.VITE_UNSPLASH_ACCESS_KEY;
async function fetchUnsplashPhoto(query: string): Promise<string | null> {
try {
const response = await fetch(
`https://api.unsplash.com/search/photos?query=${encodeURIComponent(query)}&client_id=${UNSPLASH_ACCESS_KEY}`
);
if (!response.ok) {
console.error("Failed to fetch Unsplash photo");
return null;
}
const data = await response.json();
const firstPhoto = data.results?.[0];
return firstPhoto?.urls?.regular || null;
} catch (error) {
console.error("Error fetching Unsplash photo:", error);
return null;
}
}
async function handleStart() { async function handleStart() {
destinationError = !destination; destinationError = !destination;
startDateError = !startDate; startDateError = !startDate;
@ -174,7 +196,7 @@
const placeDetails = { const placeDetails = {
name: selectedPlace.name, name: selectedPlace.name,
formatted_address: selectedPlace.formatted_address, formatted_address: selectedPlace.formatted_address,
photo: selectedPlace.photos?.[0]?.getUrl(), photo: await fetchUnsplashPhoto(destination) || '/placeholder.jpeg',
location: { location: {
lat: selectedPlace.geometry.location.lat(), lat: selectedPlace.geometry.location.lat(),
lng: selectedPlace.geometry.location.lng() lng: selectedPlace.geometry.location.lng()

View File

@ -6,10 +6,12 @@
import '../../app.css'; import '../../app.css';
import { ref, get } from 'firebase/database'; import { ref, get } from 'firebase/database';
import { db } from '../../firebase'; import { db } from '../../firebase';
import { goto } from '$app/navigation';
let mapContainer: HTMLDivElement; let mapContainer: HTMLDivElement;
interface TripLocation { interface TripLocation {
tid: string;
name: string; name: string;
location: { location: {
lat: number; lat: number;
@ -17,6 +19,7 @@
}; };
startDate: string; startDate: string;
endDate: string; endDate: string;
hasMemory: boolean;
} }
function formatDate(dateStr: string): string { function formatDate(dateStr: string): string {
@ -37,16 +40,19 @@
const locationMap: Record<string, TripLocation[]> = {}; const locationMap: Record<string, TripLocation[]> = {};
Object.values(snapshot.val()).forEach((trip: any) => { Object.entries(snapshot.val()).forEach(([tid, trip]: [string, any]) => {
const endDate = new Date(trip.endDate); const endDate = new Date(trip.endDate);
if (endDate < today && trip.destination?.location) { if (endDate < today && trip.destination?.location) {
const key = `${trip.destination.location.lat},${trip.destination.location.lng}`; const key = `${trip.destination.location.lat},${trip.destination.location.lng}`;
if (!locationMap[key]) locationMap[key] = []; if (!locationMap[key]) locationMap[key] = [];
const hasMemory = trip.memories && Object.keys(trip.memories).length > 0;
locationMap[key].push({ locationMap[key].push({
tid,
name: trip.destination.name, name: trip.destination.name,
location: trip.destination.location, location: trip.destination.location,
startDate: trip.startDate, startDate: trip.startDate,
endDate: trip.endDate endDate: trip.endDate,
hasMemory
}); });
} }
}); });
@ -167,14 +173,23 @@
.attr('cy', baseY - offsetY) .attr('cy', baseY - offsetY)
.attr('r', 5) .attr('r', 5)
.attr('fill', Colors.planner.med400) .attr('fill', Colors.planner.med400)
.attr('class', 'trip-marker'); .attr('class', 'trip-marker')
.attr('data-has-memory', trip.hasMemory ? 'true' : 'false')
.style('cursor', 'pointer')
.on('click', function () {
goto(`/itinerary/${trip.tid}`);
});
markerGroup.append('text') markerGroup.append('text')
.attr('x', baseX + 10) .attr('x', baseX + 10)
.attr('y', baseY - offsetY + 4) .attr('y', baseY - offsetY + 4)
.attr('font-size', '12px') .attr('font-size', '12px')
.attr('class', 'trip-label') .attr('class', 'trip-label')
.text(`${formatDate(trip.startDate)} - ${formatDate(trip.endDate)}`); .text(`${formatDate(trip.startDate)} - ${formatDate(trip.endDate)}`)
.style('cursor', 'pointer')
.on('click', function () {
goto(`/itinerary/${trip.tid}`);
});
markerGroup markerGroup
.style('opacity', 0) .style('opacity', 0)
@ -271,10 +286,14 @@
fill: var(--memory-500); fill: var(--memory-500);
} }
:global(.dark .trip-marker) { :global(.dark .trip-marker[data-has-memory="true"]) {
fill: var(--memory-500); fill: var(--memory-500);
} }
:global(.dark .trip-marker[data-has-memory="false"]) {
fill: var(--planner-400);
}
:global(.dark .trip-line) { :global(.dark .trip-line) {
stroke: var(--memory-500); stroke: var(--memory-500);
} }

View File

@ -22,6 +22,7 @@
import RecommendationPopup from '$lib/components/RecommendationPopup.svelte'; import RecommendationPopup from '$lib/components/RecommendationPopup.svelte';
import LoadingOverlay from '$lib/components/LoadingOverlay.svelte'; import LoadingOverlay from '$lib/components/LoadingOverlay.svelte';
import TurnIntoItineraryPopup from '$lib/components/TurnIntoItineraryPopup.svelte'; import TurnIntoItineraryPopup from '$lib/components/TurnIntoItineraryPopup.svelte';
import { fetchUnsplashPhoto } from '../../../services/unsplash';
import type { Place } from '$lib/constants/Interfaces'; import type { Place } from '$lib/constants/Interfaces';
let tripData: any = null; let tripData: any = null;
@ -288,12 +289,6 @@
itinerary: true itinerary: true
}; };
let recommendedPlaces = [
{ name: "Place name" },
{ name: "Place name" },
{ name: "Place name" }
];
let placesToVisit: any[] = []; let placesToVisit: any[] = [];
async function handleDeletePlace(index: number) { async function handleDeletePlace(index: number) {
@ -419,12 +414,13 @@
if (results.length > 0) { if (results.length > 0) {
const place = results[0]; const place = results[0];
const photoUrl = place.photos?.[0]?.getUrl(); // Use Unsplash for the photo
const unsplashUrl = await fetchUnsplashPhoto(place.name || rec.name);
const newPlace = { const newPlace = {
name: place.name || rec.name, name: place.name || rec.name,
desc: place.formatted_address || '', desc: place.formatted_address || '',
image: photoUrl || '/placeholder.jpeg', image: unsplashUrl || '/placeholder.jpeg',
geometry: place.geometry?.location ? { geometry: place.geometry?.location ? {
lat: place.geometry.location.lat(), lat: place.geometry.location.lat(),
lng: place.geometry.location.lng() lng: place.geometry.location.lng()

21
src/services/unsplash.ts Normal file
View File

@ -0,0 +1,21 @@
const UNSPLASH_ACCESS_KEY = import.meta.env.VITE_UNSPLASH_ACCESS_KEY;
export async function fetchUnsplashPhoto(query: string): Promise<string | null> {
try {
const response = await fetch(
`https://api.unsplash.com/search/photos?query=${encodeURIComponent(query)}&client_id=${UNSPLASH_ACCESS_KEY}`
);
if (!response.ok) {
console.error("Failed to fetch Unsplash photo");
return null;
}
const data = await response.json();
const firstPhoto = data.results?.[0];
return firstPhoto?.urls?.regular || null;
} catch (error) {
console.error("Error fetching Unsplash photo:", error);
return null;
}
}