add image to PlaceCard
This commit is contained in:
parent
3caa21522e
commit
143ac1d2ea
1047
package-lock.json
generated
1047
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -28,6 +28,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@googlemaps/js-api-loader": "^1.16.8",
|
"@googlemaps/js-api-loader": "^1.16.8",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
|
"firebase": "^11.8.1",
|
||||||
"topojson-client": "^3.1.0",
|
"topojson-client": "^3.1.0",
|
||||||
"topojson-server": "^3.0.1"
|
"topojson-server": "^3.0.1"
|
||||||
}
|
}
|
||||||
|
|
20
src/firebase.js
Normal file
20
src/firebase.js
Normal 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);
|
|
@ -2,17 +2,22 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { Loader } from '@googlemaps/js-api-loader';
|
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 onPlaceSelected: (place: google.maps.places.PlaceResult) => void;
|
||||||
export let countryRestriction: string | undefined = undefined;
|
export let countryRestriction: string | undefined = undefined;
|
||||||
export let placeTypes: string[] = ['establishment'];
|
export let placeTypes: string[] = ['establishment'];
|
||||||
export let placeholder = 'Add a place';
|
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 inputContainer: HTMLDivElement;
|
||||||
let inputWrapper: HTMLDivElement;
|
let inputWrapper: HTMLDivElement;
|
||||||
let showAddButton = false;
|
let showAddButton = false;
|
||||||
let lastSelectedPlaceName = '';
|
let lastSelectedPlaceName = '';
|
||||||
let selectedPlace: google.maps.places.PlaceResult | null = null;
|
let selectedPlace: ExtendedPlaceResult | null = null;
|
||||||
let inputElement: HTMLInputElement;
|
let inputElement: HTMLInputElement;
|
||||||
|
|
||||||
const GOOGLE_PLACES_API_KEY = import.meta.env.VITE_GOOGLE_PLACES_API_KEY;
|
const GOOGLE_PLACES_API_KEY = import.meta.env.VITE_GOOGLE_PLACES_API_KEY;
|
||||||
|
@ -35,7 +40,8 @@
|
||||||
|
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'text';
|
input.type = 'text';
|
||||||
input.id = id;
|
input.id = `places-input-${id}`;
|
||||||
|
input.className = 'places-input';
|
||||||
input.placeholder = placeholder;
|
input.placeholder = placeholder;
|
||||||
|
|
||||||
inputWrapper.appendChild(input);
|
inputWrapper.appendChild(input);
|
||||||
|
@ -50,12 +56,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
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']);
|
autocomplete.setFields(['name', 'formatted_address', 'photos', 'place_id', 'geometry']);
|
||||||
// TODO: how to get the photos?
|
|
||||||
|
|
||||||
autocomplete.addListener('place_changed', () => {
|
autocomplete.addListener('place_changed', () => {
|
||||||
const place = autocomplete.getPlace();
|
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
|
||||||
|
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;
|
selectedPlace = place;
|
||||||
lastSelectedPlaceName = input.value.trim();
|
lastSelectedPlaceName = input.value.trim();
|
||||||
showAddButton = true;
|
showAddButton = true;
|
||||||
|
@ -150,7 +168,7 @@
|
||||||
color: var(--planner-400);
|
color: var(--planner-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(input#add-places) {
|
:global(.places-input) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0.75rem 0.75rem 0.75rem 2.5rem;
|
padding: 0.75rem 0.75rem 0.75rem 2.5rem;
|
||||||
|
@ -163,12 +181,12 @@
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(input#add-places:hover) {
|
:global(.places-input:hover) {
|
||||||
background-color: var(--gray-100);
|
background-color: var(--gray-100);
|
||||||
border-color: var(--gray-100);
|
border-color: var(--gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(input#add-places:focus) {
|
:global(.places-input:focus) {
|
||||||
outline-color: var(--planner-400);
|
outline-color: var(--planner-400);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
export let text = 'Button';
|
export let text = 'Button';
|
||||||
export let type = 'single';
|
export let type = 'single';
|
||||||
export let onClick = () => {};
|
export let onClick = () => {};
|
||||||
|
export let disabled = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="{type}" onclick={onClick}>
|
<button class="{type}" on:click={onClick} {disabled}>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -20,6 +21,17 @@
|
||||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
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 {
|
.single {
|
||||||
background-color: var(--planner-300);
|
background-color: var(--planner-300);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
export let date;
|
export let date;
|
||||||
export let isExpanded = true;
|
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() {
|
function toggleDate() {
|
||||||
isExpanded = !isExpanded;
|
isExpanded = !isExpanded;
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
const newPlace = {
|
const newPlace = {
|
||||||
name: place.name || 'Unknown Place',
|
name: place.name || 'Unknown Place',
|
||||||
desc: place.formatted_address || '',
|
desc: place.formatted_address || '',
|
||||||
img: place.photos ? place.photos[0].getUrl() : 'placeholder.jpeg',
|
image: (place as any).photoUrl || 'placeholder.jpeg',
|
||||||
time: 'Add Time'
|
time: 'Add Time'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param e {any}
|
||||||
|
*/
|
||||||
const handleClickOutside = (e) => {
|
const handleClickOutside = (e) => {
|
||||||
if (!e.target.closest('.profile')) {
|
if (!e.target.closest('.profile')) {
|
||||||
showDropdown = false;
|
showDropdown = false;
|
||||||
|
|
|
@ -4,19 +4,19 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let showPopup = false;
|
export let showPopup = false;
|
||||||
export let locations = [];
|
export let locations: any[] = [];
|
||||||
export let onAddMemory = () => {};
|
export let onAddMemory = (p0?: { location: string; images: any[]; startDate: string; endDate: string; }) => {};
|
||||||
export let onCancel = () => {};
|
export let onCancel = () => {};
|
||||||
|
|
||||||
let selectedLocation = '';
|
let selectedLocation = '';
|
||||||
let customLocation = '';
|
let customLocation = '';
|
||||||
let images = [];
|
let images: any[] = [];
|
||||||
let dragActive = false;
|
let dragActive = false;
|
||||||
|
|
||||||
let startDate = '';
|
let startDate = '';
|
||||||
let endDate = '';
|
let endDate = '';
|
||||||
|
|
||||||
function handleFiles(files) {
|
function handleFiles(files: any) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.type.startsWith('image/')) {
|
if (file.type.startsWith('image/')) {
|
||||||
images = [...images, file];
|
images = [...images, file];
|
||||||
|
@ -24,23 +24,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDrop(event) {
|
function handleDrop(event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dragActive = false;
|
dragActive = false;
|
||||||
handleFiles(event.dataTransfer.files);
|
handleFiles(event.dataTransfer.files);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragOver(event) {
|
function handleDragOver(event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dragActive = true;
|
dragActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragLeave(event) {
|
function handleDragLeave(event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dragActive = false;
|
dragActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInputChange(event) {
|
function handleInputChange(event: any) {
|
||||||
if (event.target.files) {
|
if (event.target.files) {
|
||||||
handleFiles(event.target.files);
|
handleFiles(event.target.files);
|
||||||
}
|
}
|
||||||
|
@ -138,8 +138,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="input-form">
|
<div class="input-form">
|
||||||
|
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||||
<label>Upload images</label>
|
<label>Upload images</label>
|
||||||
<div class="drop-area {dragActive ? 'active' : ''}"
|
<div class="drop-area {dragActive ? 'active' : ''}"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
on:drop={handleDrop}
|
on:drop={handleDrop}
|
||||||
on:dragover={handleDragOver}
|
on:dragover={handleDragOver}
|
||||||
on:dragleave={handleDragLeave}
|
on:dragleave={handleDragLeave}
|
||||||
|
@ -152,7 +155,8 @@
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
id="fileInput"
|
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}
|
{#if images.length === 0}
|
||||||
<span>Drop image here</span>
|
<span>Drop image here</span>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -24,6 +24,22 @@
|
||||||
|
|
||||||
const GOOGLE_PLACES_API_KEY = import.meta.env.VITE_GOOGLE_PLACES_API_KEY;
|
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 () => {
|
onMount(async () => {
|
||||||
if (!GOOGLE_PLACES_API_KEY) {
|
if (!GOOGLE_PLACES_API_KEY) {
|
||||||
console.error('Google Maps API key is missing');
|
console.error('Google Maps API key is missing');
|
||||||
|
@ -231,7 +247,7 @@
|
||||||
|
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<Button text="Cancel" type="gray" onClick={handleCancel} />
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -191,7 +191,8 @@
|
||||||
|
|
||||||
.place-image.detailed {
|
.place-image.detailed {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
height: 100%;
|
aspect-ratio: 16 / 9;
|
||||||
|
height: auto;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
const newPlace = {
|
const newPlace = {
|
||||||
name: place.name || 'Unknown Place',
|
name: place.name || 'Unknown Place',
|
||||||
desc: place.formatted_address || '',
|
desc: place.formatted_address || '',
|
||||||
img: place.photos ? place.photos[0].getUrl() : 'placeholder.jpeg'
|
image: (place as any).photoUrl || 'placeholder.jpeg'
|
||||||
};
|
};
|
||||||
|
|
||||||
placesToVisit = [...placesToVisit, newPlace];
|
placesToVisit = [...placesToVisit, newPlace];
|
||||||
|
@ -118,6 +118,14 @@
|
||||||
console.log('save update');
|
console.log('save update');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRecommendPlaces() {
|
||||||
|
console.log(`will give recommendation using OpenAI`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTurnIntoItinerary() {
|
||||||
|
console.log(`please turn this into itinerary`);
|
||||||
|
}
|
||||||
|
|
||||||
function showPastTrips() {
|
function showPastTrips() {
|
||||||
// TODO: Implement past trips view
|
// TODO: Implement past trips view
|
||||||
console.log('Show past trips');
|
console.log('Show past trips');
|
||||||
|
@ -173,6 +181,11 @@
|
||||||
onPlaceSelected={handlePlaceSelected}
|
onPlaceSelected={handlePlaceSelected}
|
||||||
countryRestriction="tw"
|
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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
@ -353,6 +366,11 @@
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.places-buttons {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
.button-group {
|
.button-group {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NewMemoryPopup bind:showPopup={showNewMemoryPopup} fromPage="memories" />
|
<NewMemoryPopup bind:showPopup={showNewMemoryPopup} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user