added trips page

This commit is contained in:
adeliptr 2025-05-31 14:53:07 +09:00
parent 86c84b42e9
commit 5b0a51a6f4
8 changed files with 605 additions and 280 deletions

View File

@ -1,13 +1,7 @@
<script>
import Button from "./Button.svelte";
export let title = 'Past Trips';
export let desc = 'Click to view all past trips';
export let title = 'Your Trips';
export let desc = 'Click to view all your trips';
export let onClick = () => {};
// change if there is a button
export let buttonText = '+ Plan a new trip';
export let onButtonClick = undefined;
</script>
<div class="bottom-bar">
@ -17,10 +11,6 @@
<h2>{title}</h2>
<p class="hint">{desc}</p>
</div>
{#if typeof onButtonClick === 'function'}
<Button text={buttonText} type="single" onClick={onButtonClick} />
{/if}
</div>
<style>

View File

@ -61,12 +61,12 @@
align-items: center;
gap: 1rem;
margin: 0;
font-size: 1rem;
font-family: 'Inter', sans-serif;
}
.date-text h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 500;
}

View File

@ -0,0 +1,110 @@
<script lang="ts">
import { Colors } from '$lib/constants/Colors';
import { goto } from '$app/navigation';
let title = "Travel App";
export let activeTab = "Home";
function handleNavigation(tab: "Home" | "Planner" | "Memory") {
activeTab = tab;
if (tab === 'Home') {
goto('/');
} else if (tab === 'Planner') {
goto('/trips');
} else {
console.log("will be implemented later");
}
}
</script>
<nav>
<div class="logo">{title}</div>
<div class="right-nav">
<div class="menu">
<button
class:active={activeTab === "Home"}
onclick={() => handleNavigation("Home")}>
Home
</button>
<button
class:active={activeTab === "Planner"}
onclick={() => handleNavigation("Planner")}>
Planner
</button>
<button
class:active={activeTab === "Memory"}
onclick={() => handleNavigation("Memory")}>
Memory
</button>
</div>
<div class="profile">
<button class="profile-btn" aria-label="Open profile">
<i class="fa-regular fa-user fa-xl" style="color: {Colors.black}"></i>
</button>
</div>
</div>
</nav>
<style>
nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
border-bottom: 1px solid var(--gray-100);
background-color: var(--white);
}
.logo {
font-size: 1.5rem;
font-weight: bold;
}
.right-nav {
display: flex;
align-items: center;
gap: 1.5rem;
}
.menu {
display: flex;
}
.menu button {
background: none;
border: none;
font-size: 1rem;
cursor: pointer;
padding: 0.5rem 1rem;
color: var(--gray-400);
transition: all 0.2s ease;
min-width: 100px;
text-align: center;
}
.menu button.active {
color: var(--black);
font-weight: 600;
}
.menu button:hover {
color: var(--black);
}
.profile-btn {
background: none;
border: none;
width: 2.5rem;
height: 2.5rem;
opacity: 0.3;
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
transition: background-color 0.2s ease;
}
.profile-btn:hover {
background-color: var(--gray-100);
opacity: 1;
}
</style>

View File

@ -0,0 +1,165 @@
<script>
import { Colors } from '$lib/constants/Colors';
import { goto } from '$app/navigation';
import Button from './Button.svelte';
export let showPopup = false;
export let fromPage = 'home';
let destination = "";
let startDate = "";
let endDate = "";
let friends = "";
function handleCancel() {
showPopup = false;
destination = "";
startDate = "";
endDate = "";
friends = "";
}
function handleStart() {
console.log(destination, startDate, endDate, friends);
goto(`/itinerary?from=${fromPage}`);
handleCancel();
}
</script>
{#if showPopup}
<div class="overlay">
<div class="popup">
<h1>Start a New Plan</h1>
<div class="input-form">
<label for="destination">Destination</label>
<input
type="text"
id="destination"
bind:value={destination}
placeholder="Where do you want to go?"
/>
</div>
<div class="date-group">
<div class="input-form">
<label for="start-date">Start Date</label>
<input
type="date"
id="start-date"
bind:value={startDate}
/>
</div>
<div class="input-form">
<label for="end-date">End Date</label>
<input
type="date"
id="end-date"
bind:value={endDate}
/>
</div>
</div>
<div class="input-form">
<label for="trip-friends">
<span class="invite-label">
+ Invite Friends
<i class="fa-solid fa-user-group" style="color: {Colors.gray.dark800}"></i>
</span>
</label>
<input
type="text"
id="trip-friends"
bind:value={friends}
placeholder="Enter email addresses"
/>
</div>
<div class="button-group">
<Button text="Cancel" type="gray" onClick={handleCancel} />
<Button text="Start" type="blue" onClick={handleStart} />
</div>
</div>
</div>
{/if}
<style>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
.popup {
background: var(--white);
padding: 2rem;
border-radius: 20px;
width: 80%;
max-width: 560px;
box-shadow: 0 0px 20px rgba(0, 0, 0, 0.1);
}
.popup h1 {
margin: 0 0 2rem 0;
font-size: 1.5rem;
font-weight: 600;
}
.input-form {
margin-bottom: 1.5rem;
}
.input-form label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--gray-800);
}
.input-form input {
width: 95.3%;
padding: 0.75rem;
border: 1px solid var(--gray-200);
border-radius: 8px;
font-size: 1rem;
}
input:focus {
outline-color: var(--planner-600);
}
.date-group {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.date-group .input-form {
flex: 1;
margin-bottom: 0;
}
.date-group .input-form input {
width: 90%;
}
.invite-label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.button-group {
display: flex;
gap: 1rem;
margin-top: 2rem;
}
</style>

View File

@ -0,0 +1,69 @@
<script>
export let destination = '';
export let startDate = '';
export let endDate = '';
export let image = '';
</script>
<div class="trip-card">
<div class="image" style="background-image: url({image})">
<!-- Image placeholder if no image provided -->
{#if !image}
<div class="placeholder">
<i class="fa-solid fa-image" style="color: var(--gray-400)"></i>
</div>
{/if}
</div>
<div class="info">
<h3>{destination}</h3>
<p class="date">{startDate} - {endDate}</p>
</div>
</div>
<style>
.trip-card {
background: var(--white);
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: transform 0.2s ease, box-shadow 0.2s ease;
cursor: pointer;
font-family: 'Inter', sans-serif;
}
.trip-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.image {
height: 160px;
background-size: cover;
background-position: center;
background-color: var(--gray-100);
}
.placeholder {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
.info {
padding: 1rem;
}
.info h3 {
margin: 0;
font-size: 1.2rem;
font-weight: 600;
}
.date {
margin: 0.25rem 0 0 0;
font-size: 0.8rem;
color: var(--gray-400);
}
</style>

View File

@ -1,288 +1,56 @@
<script>
import '../app.css';
import { goto } from '$app/navigation';
import { Colors } from '$lib/constants/Colors';
import WorldMap from '$lib/components/WorldMap.svelte';
import Button from '$lib/components/Button.svelte';
import BottomBar from '$lib/components/BottomBar.svelte';
import NewTripPopup from '$lib/components/NewTripPopup.svelte';
import Nav from '$lib/components/Nav.svelte';
let title = "Travel App";
let activeTab = "Planner";
let showNewTripPopup = false;
let destination = "";
let startDate = "";
let endDate = "";
let friends = "";
const GOOGLE_PLACES_API_KEY = import.meta.env.VITE_GOOGLE_PLACES_API_KEY;
function handleNewTrip() {
showNewTripPopup = true;
}
function handleCancel() {
showNewTripPopup = false;
destination = "";
startDate = "";
endDate = "";
friends = "";
}
function handleStart() {
console.log(destination, startDate, endDate, friends);
goto('/itinerary');
}
function handlePastTrip() {
console.log("let's see the past trip");
showNewTripPopup = true;
}
</script>
<main>
<nav>
<div class="logo">{title}</div>
<div class="right-nav">
<div class="menu">
<button
class:active={activeTab === "Planner"}
onclick={() => activeTab = "Planner"}>
Planner
</button>
<button
class:active={activeTab === "Memory"}
onclick={() => activeTab = "Memory"}>
Memory
</button>
</div>
<div class="profile">
<button class="profile-btn" aria-label="Open profile">
<i class="fa-regular fa-user fa-xl" style="color: {Colors.black}"></i>
</button>
</div>
</div>
</nav>
<div class="map-container">
<main>
<Nav activeTab="Home" />
<div class="map-container">
<WorldMap />
</div>
</div>
<div class="floating-button">
<Button text="+ Plan a new trip" type="single" onClick={handleNewTrip} />
</div>
<BottomBar onClick={handlePastTrip} onButtonClick={handleNewTrip} />
<BottomBar onClick={() => goto('/trips')} />
{#if showNewTripPopup}
<!-- (optional) add onclick={handleCancel} -->
<div class="overlay">
<div class="popup">
<h1>Start a New Plan</h1>
<div class="input-form">
<label for="destination">Destination</label>
<input
type="text"
id="destination"
bind:value={destination}
placeholder="Where do you want to go?"
/>
</div>
<NewTripPopup bind:showPopup={showNewTripPopup} fromPage="home" />
</main>
<div class="date-group">
<div class="input-form">
<label for="start-date">Start Date</label>
<input
type="date"
id="start-date"
bind:value={startDate}
/>
</div>
<div class="input-form">
<label for="end-date">End Date</label>
<input
type="date"
id="end-date"
bind:value={endDate}
/>
</div>
</div>
<div class="input-form">
<label for="trip-friends">
<span class="invite-label">
+ Invite Friends
<i class="fa-solid fa-user-group" style="color: {Colors.gray.dark800}"></i>
</span>
</label>
<input
type="text"
id="trip-friends"
bind:value={friends}
placeholder="Enter email addresses"
/>
</div>
<div class="button-group">
<Button text="Cancel" type="gray" onClick={handleCancel} />
<Button text="Start" type="blue" onClick={handleStart} />
</div>
</div>
</div>
{/if}
</main>
<style>
main {
<style>
main {
height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--gray-50);
font-family: 'Inter', sans-serif;
}
nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
border-bottom: 1px solid var(--gray-100);
background-color: var(--white);
}
.logo {
font-size: 1.5rem;
font-weight: bold;
}
}
.right-nav {
display: flex;
align-items: center;
gap: 1.5rem;
}
.menu {
display: flex;
gap: 0.5rem;
}
.menu button {
background: none;
border: none;
font-size: 1rem;
cursor: pointer;
padding: 0.5rem 1rem;
color:var(--gray-400);
transition: all 0.2s ease;
}
.menu button.active {
color: var(--black);
font-weight: bold;
}
.menu button:hover {
color: var(--black);
}
.profile-btn {
background: none;
border: none;
width: 2.5rem;
height: 2.5rem;
opacity: 0.3;
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
transition: background-color 0.2s ease;
}
.profile-btn:hover {
background-color: var(--gray-100);
opacity: 1;
}
.map-container {
.map-container {
flex: 1;
position: relative;
background-color: var(--gray-50);
/* overflow: hidden; */
}
}
/* Popup Styling */
.overlay {
.floating-button {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.popup {
background: var(--white);
padding: 2rem;
border-radius: 20px;
width: 80%;
max-width: 560px;
box-shadow: 0 0px 20px rgba(0, 0, 0, 0.1);
}
.popup h1 {
margin: 0 0 2rem 0;
font-size: 1.5rem;
font-weight: 600;
/* color: var(--planner-600); */
}
.input-form {
margin-bottom: 1.5rem;
}
.input-form label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--gray-800);
}
.input-form input {
width: 95.3%;
padding: 0.75rem;
border: 1px solid var(--gray-200);
border-radius: 8px;
font-size: 1rem;
}
input:focus {
outline-color: var(--planner-600);
}
.date-group {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.date-group .input-form {
flex: 1;
margin-bottom: 0;
}
.date-group .input-form input{
width: 90%;
}
.invite-label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.button-group {
display: flex;
gap: 1rem;
margin-top: 2rem;
}
</style>
bottom: 2rem;
right: 2rem;
z-index: 10;
}
</style>

View File

@ -1,14 +1,13 @@
<script lang="ts">
import '../../app.css';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import { Colors } from '$lib/constants/Colors';
import { slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import ProfilePicture from '$lib/components/ProfilePicture.svelte';
import BottomBar from '$lib/components/BottomBar.svelte';
import Button from '$lib/components/Button.svelte';
import ItineraryDate from '$lib/components/ItineraryDate.svelte';
import { page } from '$app/state';
// Placeholder data obtained from the popup
let destination = "Taiwan";
@ -16,7 +15,7 @@
let startDate = "27/04/2025";
let endDate = "30/04/2025";
let places: string[] = [];
const place_placeholder = { name: 'Somewhere', desc: 'desc of the place'}
const place_placeholder = { name: 'Somewhere'}
const places_placeholder = Array(3).fill(place_placeholder);
// Array of dates between startDate to endDate
@ -40,7 +39,15 @@
}
function handleBack() {
goto('/');
// Get the 'from' parameter from the URL
const fromPage = page.url.searchParams.get('from');
console.log(`fromPage = ${fromPage}`);
if (fromPage === 'trips') {
goto('/trips');
} else {
goto('/');
}
}
function handleAddPlace() {
@ -142,7 +149,7 @@
<div class="map-section">
<div class="map"></div>
<BottomBar desc={desc} onClick={handlePastTrip} />
<BottomBar title="Past Trips" desc={desc} onClick={handlePastTrip} />
</div>
</main>
@ -160,6 +167,8 @@
box-sizing: border-box;
padding: 0.5rem 0rem 0rem 0rem;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.map-section {
@ -176,10 +185,11 @@
header {
display: flex;
flex-shrink: 0;
align-items: center;
gap: 1rem;
padding: 0 2rem 1.5rem 1rem;
margin-bottom: 3rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--gray-100);
}
@ -229,6 +239,10 @@
.content {
padding: 0 1.5rem 0 1.5rem;
flex: 1;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.section-header {
@ -262,6 +276,7 @@
.section-text h2 {
margin: 0;
font-size: 1.2rem;
font-weight: 600;
}
@ -272,11 +287,12 @@
.button-group {
position: sticky;
flex-shrink: 0;
background-color: var(--white);
padding: 1.5rem 0;
bottom: 0;
display: flex;
gap: 1rem;
margin-top: 2rem;
margin-top: auto;
}
</style>

View File

@ -0,0 +1,207 @@
<script lang="ts">
import '../../app.css';
import TripCard from '$lib/components/TripCard.svelte';
import Button from '$lib/components/Button.svelte';
import NewTripPopup from '$lib/components/NewTripPopup.svelte';
import Nav from '$lib/components/Nav.svelte';
interface Trip {
destination: string;
startDate: string;
endDate: string;
imageUrl: string;
}
let activeTab = "Ongoing Trips";
let showNewTripPopup = false;
let contentContainer: HTMLElement;
// Sample data, replace with actual data later
const sample_trip = {
destination: "Taiwan",
startDate: "04.27.2025",
endDate: "04.30.2025",
imageUrl: ""
}
let ongoingTrips = Array(3).fill(sample_trip);
// let pastTrips: Trip[] = [];
let pastTrips = Array(14).fill(sample_trip);
function handleNewTrip() {
showNewTripPopup = true;
}
function handleTabChange(tab: string) {
activeTab = tab;
contentContainer.scrollTo({
top: 0,
behavior: 'smooth'
});
}
</script>
<main>
<Nav activeTab="Planner" />
<div class="content" bind:this={contentContainer}>
<div class="header">
<h1>Your Trips</h1>
</div>
<div class="tabs">
<button
class:active={activeTab === "Ongoing Trips"}
onclick={() => handleTabChange("Ongoing Trips")}>
Ongoing Trips
</button>
<button
class:active={activeTab === "Past Trips"}
onclick={() => handleTabChange("Past Trips")}>
Past Trips
</button>
</div>
<div class="trips-container">
{#if activeTab === "Ongoing Trips"}
{#if ongoingTrips.length === 0}
<div class="empty-state">
<p>There is no ongoing trip</p>
</div>
{:else}
<div class="trips-grid">
{#each ongoingTrips as trip}
<TripCard {...trip} />
{/each}
</div>
{/if}
{:else}
{#if pastTrips.length === 0}
<div class="empty-state">
<p>There is no past trip</p>
</div>
{:else}
<div class="trips-grid">
{#each pastTrips as trip}
<TripCard {...trip} />
{/each}
</div>
{/if}
{/if}
</div>
<div class="floating-button">
<Button text="+ Plan a new trip" type="single" onClick={handleNewTrip} />
</div>
</div>
<NewTripPopup bind:showPopup={showNewTripPopup} fromPage="trips" />
</main>
<style>
main {
height: 100vh;
background-color: var(--white);
font-family: 'Inter', sans-serif;
display: flex;
flex-direction: column;
}
.content {
flex: 1;
padding: 0 1rem 2rem 1rem;
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: auto;
}
.header {
padding-top: 2rem;
padding-left: 1rem;
background-color: var(--white);
}
.header h1 {
font-size: 2rem;
font-weight: 600;
margin: 0;
}
.tabs {
display: flex;
position: sticky;
top: 0;
padding-top: 1.5rem;
padding-left: 1rem;
background-color: var(--white);
gap: 1.5rem;
margin-bottom: 0.5rem;
z-index: 5;
}
.tabs button {
background: none;
border: none;
font-size: 1rem;
cursor: pointer;
padding: 0.75rem 0;
color: var(--gray-400);
transition: color 0.3s ease;
position: relative;
min-width: 120px;
text-align: center;
}
.tabs button:hover {
color: black;
}
.tabs button.active {
color: var(--planner-600);
font-weight: 600;
}
.tabs button.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background-color: var(--planner-600);
transition: transform 0.2s ease;
}
.trips-container {
flex: 1;
padding: 0 1rem;
display: flex;
flex-direction: column;
}
.trips-grid {
display: grid;
padding-top: 1.5rem;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
.empty-state {
flex: 1;
display: flex;
margin-top: -10rem;
justify-content: center;
align-items: center;
color: var(--gray-400);
font-size: 1.1rem;
}
.floating-button {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 10;
}
</style>