add image to PlaceCard

This commit is contained in:
adeliptr 2025-06-05 14:03:42 +09:00
parent 3caa21522e
commit 143ac1d2ea
12 changed files with 1170 additions and 29 deletions

1047
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@
"dependencies": {
"@googlemaps/js-api-loader": "^1.16.8",
"d3": "^7.9.0",
"firebase": "^11.8.1",
"topojson-client": "^3.1.0",
"topojson-server": "^3.0.1"
}

20
src/firebase.js Normal file
View File

@ -0,0 +1,20 @@
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { ref, child, get, set, getDatabase, onValue, push } from 'firebase/database';
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const db = getDatabase(app);

View File

@ -2,17 +2,22 @@
import { onMount } from 'svelte';
import { Loader } from '@googlemaps/js-api-loader';
// Extend the PlaceResult type to include our custom photoUrl
interface ExtendedPlaceResult extends google.maps.places.PlaceResult {
photoUrl?: string;
}
export let onPlaceSelected: (place: google.maps.places.PlaceResult) => void;
export let countryRestriction: string | undefined = undefined;
export let placeTypes: string[] = ['establishment'];
export let placeholder = 'Add a place';
export let id = 'add-places';
export let id = crypto.randomUUID(); // Generate unique ID for each instance
let inputContainer: HTMLDivElement;
let inputWrapper: HTMLDivElement;
let showAddButton = false;
let lastSelectedPlaceName = '';
let selectedPlace: google.maps.places.PlaceResult | null = null;
let selectedPlace: ExtendedPlaceResult | null = null;
let inputElement: HTMLInputElement;
const GOOGLE_PLACES_API_KEY = import.meta.env.VITE_GOOGLE_PLACES_API_KEY;
@ -35,7 +40,8 @@
const input = document.createElement('input');
input.type = 'text';
input.id = id;
input.id = `places-input-${id}`;
input.className = 'places-input';
input.placeholder = placeholder;
inputWrapper.appendChild(input);
@ -50,12 +56,24 @@
}
const autocomplete = new google.maps.places.Autocomplete(input, autocompleteOptions);
autocomplete.setFields(['name', 'formatted_address', 'photos', 'place_id']);
// TODO: how to get the photos?
autocomplete.setFields(['name', 'formatted_address', 'photos', 'place_id', 'geometry']);
autocomplete.addListener('place_changed', () => {
const place = autocomplete.getPlace();
const place = autocomplete.getPlace() as ExtendedPlaceResult;
if (place && place.name) {
// If the place has photos, get the URL for the first photo
if (place.photos && place.photos.length > 0) {
const photoOptions = {
maxWidth: 400,
maxHeight: 300
};
// Get the photo URL
console.log(place.photos[0]);
const photoUrl = place.photos[0].getUrl(photoOptions);
place.photoUrl = photoUrl;
console.log(place.photoUrl);
}
selectedPlace = place;
lastSelectedPlaceName = input.value.trim();
showAddButton = true;
@ -150,7 +168,7 @@
color: var(--planner-400);
}
:global(input#add-places) {
:global(.places-input) {
width: 100%;
box-sizing: border-box;
padding: 0.75rem 0.75rem 0.75rem 2.5rem;
@ -163,12 +181,12 @@
transition: all 0.2s ease;
}
:global(input#add-places:hover) {
:global(.places-input:hover) {
background-color: var(--gray-100);
border-color: var(--gray-100);
}
:global(input#add-places:focus) {
:global(.places-input:focus) {
outline-color: var(--planner-400);
background-color: white;
}

View File

@ -3,13 +3,14 @@
export let text = 'Button';
export let type = 'single';
export let onClick = () => {};
</script>
export let disabled = false;
</script>
<button class="{type}" onclick={onClick}>
<button class="{type}" on:click={onClick} {disabled}>
{text}
</button>
</button>
<style>
<style>
button {
border: none;
padding: 0.8rem 1.5rem;
@ -20,6 +21,17 @@
transition: transform 0.2s ease, opacity 0.2s ease;
}
button:disabled {
cursor: not-allowed;
opacity: 0.5;
transform: none;
}
button:disabled:hover {
opacity: 0.5;
transform: none;
}
.single {
background-color: var(--planner-300);
color: var(--white);
@ -73,4 +85,4 @@
.orange:hover {
background-color: var(--memory-400);
}
</style>
</style>

View File

@ -6,7 +6,7 @@
export let date;
export let isExpanded = true;
export let places: { name: string; desc?: string; img?: string; time?: string; }[] = [];
export let places: { name: string; desc?: string; image?: string; time?: string; }[] = [];
function toggleDate() {
isExpanded = !isExpanded;
@ -16,7 +16,7 @@
const newPlace = {
name: place.name || 'Unknown Place',
desc: place.formatted_address || '',
img: place.photos ? place.photos[0].getUrl() : 'placeholder.jpeg',
image: (place as any).photoUrl || 'placeholder.jpeg',
time: 'Add Time'
};

View File

@ -3,6 +3,10 @@
import { onMount } from 'svelte';
onMount(() => {
/**
*
* @param e {any}
*/
const handleClickOutside = (e) => {
if (!e.target.closest('.profile')) {
showDropdown = false;

View File

@ -4,19 +4,19 @@
import { onMount } from 'svelte';
export let showPopup = false;
export let locations = [];
export let onAddMemory = () => {};
export let locations: any[] = [];
export let onAddMemory = (p0?: { location: string; images: any[]; startDate: string; endDate: string; }) => {};
export let onCancel = () => {};
let selectedLocation = '';
let customLocation = '';
let images = [];
let images: any[] = [];
let dragActive = false;
let startDate = '';
let endDate = '';
function handleFiles(files) {
function handleFiles(files: any) {
for (const file of files) {
if (file.type.startsWith('image/')) {
images = [...images, file];
@ -24,23 +24,23 @@
}
}
function handleDrop(event) {
function handleDrop(event: any) {
event.preventDefault();
dragActive = false;
handleFiles(event.dataTransfer.files);
}
function handleDragOver(event) {
function handleDragOver(event: any) {
event.preventDefault();
dragActive = true;
}
function handleDragLeave(event) {
function handleDragLeave(event: any) {
event.preventDefault();
dragActive = false;
}
function handleInputChange(event) {
function handleInputChange(event: any) {
if (event.target.files) {
handleFiles(event.target.files);
}
@ -138,8 +138,11 @@
{/if}
<div class="input-form">
<!-- svelte-ignore a11y_label_has_associated_control -->
<label>Upload images</label>
<div class="drop-area {dragActive ? 'active' : ''}"
role="button"
tabindex="0"
on:drop={handleDrop}
on:dragover={handleDragOver}
on:dragleave={handleDragLeave}
@ -152,7 +155,8 @@
style="display: none;"
id="fileInput"
/>
<div class="drop-label" on:click={() => document.getElementById('fileInput')?.click()}>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="drop-label" role="button" tabindex="0" on:click={() => document.getElementById('fileInput')?.click()}>
{#if images.length === 0}
<span>Drop image here</span>
{:else}

View File

@ -24,6 +24,22 @@
const GOOGLE_PLACES_API_KEY = import.meta.env.VITE_GOOGLE_PLACES_API_KEY;
// Add reactive statements to clear errors when valid input is provided
$: if (destination) destinationError = false;
$: if (startDate) startDateError = false;
$: if (endDate) endDateError = false;
// Clear date order error when either date changes
$: if (startDate && endDate) {
const startDateTime = new Date(startDate).getTime();
const endDateTime = new Date(endDate).getTime();
if (endDateTime >= startDateTime) {
dateOrderError = false;
startDateError = false;
endDateError = false;
}
}
onMount(async () => {
if (!GOOGLE_PLACES_API_KEY) {
console.error('Google Maps API key is missing');
@ -231,7 +247,7 @@
<div class="button-group">
<Button text="Cancel" type="gray" onClick={handleCancel} />
<Button text="Start" type="blue" onClick={handleStart} />
<Button text="Start" type="blue" onClick={handleStart} disabled={destinationError || startDateError || endDateError}/>
</div>
</div>
</div>

View File

@ -191,7 +191,8 @@
.place-image.detailed {
width: 30%;
height: 100%;
aspect-ratio: 16 / 9;
height: auto;
border-radius: 20px;
}

View File

@ -100,7 +100,7 @@
const newPlace = {
name: place.name || 'Unknown Place',
desc: place.formatted_address || '',
img: place.photos ? place.photos[0].getUrl() : 'placeholder.jpeg'
image: (place as any).photoUrl || 'placeholder.jpeg'
};
placesToVisit = [...placesToVisit, newPlace];
@ -118,6 +118,14 @@
console.log('save update');
}
function handleRecommendPlaces() {
console.log(`will give recommendation using OpenAI`);
}
function handleTurnIntoItinerary() {
console.log(`please turn this into itinerary`);
}
function showPastTrips() {
// TODO: Implement past trips view
console.log('Show past trips');
@ -173,6 +181,11 @@
onPlaceSelected={handlePlaceSelected}
countryRestriction="tw"
/>
<div class="places-buttons">
<Button text="Recommend Places" type="blue" onClick={handleRecommendPlaces} />
<Button text="Turn into Itinerary" type="blue" onClick={handleTurnIntoItinerary} />
</div>
</div>
{/if}
</section>
@ -353,6 +366,11 @@
margin-left: 0.5rem;
}
.places-buttons {
margin-top: 0.5rem;
display: flex;
gap: 0.5rem;
}
.button-group {
position: sticky;
flex-shrink: 0;

View File

@ -56,7 +56,7 @@
</div>
</div>
<NewMemoryPopup bind:showPopup={showNewMemoryPopup} fromPage="memories" />
<NewMemoryPopup bind:showPopup={showNewMemoryPopup} />
</main>
<style>