mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-04 20:55:19 +02:00
commit
af3641106d
12 changed files with 320 additions and 26 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "adventurelog",
|
"name": "adventurelog",
|
||||||
"version": "0.1.6",
|
"version": "0.2.0",
|
||||||
"description": "Embark, Explore, Remember. 🌍",
|
"description": "Embark, Explore, Remember. 🌍",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
74
src/lib/components/AddLocationChooser.svelte
Normal file
74
src/lib/components/AddLocationChooser.svelte
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Adventure, Trip } from "$lib/utils/types";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import TripListModal from "./TripListModal.svelte";
|
||||||
|
let modal: HTMLDialogElement;
|
||||||
|
|
||||||
|
export let adventure: Adventure;
|
||||||
|
|
||||||
|
let tripModal: boolean = false;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
modal = document.getElementById("my_modal_1") as HTMLDialogElement;
|
||||||
|
if (modal) {
|
||||||
|
modal.showModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
dispatch("close");
|
||||||
|
}
|
||||||
|
|
||||||
|
function openTripModal() {
|
||||||
|
tripModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function visited() {
|
||||||
|
dispatch("visited");
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
function idea() {
|
||||||
|
dispatch("idea");
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function trip(event: CustomEvent<any>) {
|
||||||
|
dispatch("trip", event.detail);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if tripModal}
|
||||||
|
<TripListModal on:close={close} on:trip={trip} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<dialog id="my_modal_1" class="modal">
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||||
|
<h3 class="font-bold text-lg">
|
||||||
|
Where should Adventure: {adventure.name} be added?
|
||||||
|
</h3>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<button class="btn btn-primary mr-2" on:click={visited}
|
||||||
|
>Visited Adventures</button
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary mr-2" on:click={idea}
|
||||||
|
>Adventure Idea</button
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary mr-2" on:click={openTripModal}
|
||||||
|
>Add to Trip</button
|
||||||
|
>
|
||||||
|
<div class="h-32"></div>
|
||||||
|
<button class="btn btn-neutral" on:click={close}>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
|
@ -109,6 +109,10 @@
|
||||||
><iconify-icon icon="mdi:launch" class="text-2xl"
|
><iconify-icon icon="mdi:launch" class="text-2xl"
|
||||||
></iconify-icon></button
|
></iconify-icon></button
|
||||||
> -->
|
> -->
|
||||||
|
<button class="btn btn-primary" on:click={moreInfo}
|
||||||
|
><iconify-icon icon="mdi:launch" class="text-2xl"
|
||||||
|
></iconify-icon></button
|
||||||
|
>
|
||||||
<button class="btn btn-primary" on:click={edit}
|
<button class="btn btn-primary" on:click={edit}
|
||||||
><iconify-icon icon="mdi:file-document-edit" class="text-2xl"
|
><iconify-icon icon="mdi:file-document-edit" class="text-2xl"
|
||||||
></iconify-icon></button
|
></iconify-icon></button
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import type { Adventure } from "$lib/utils/types";
|
import type { Adventure } from "$lib/utils/types";
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { addActivityType } from "$lib";
|
import { addActivityType, generateDescription, getImage } from "$lib";
|
||||||
let modal: HTMLDialogElement;
|
let modal: HTMLDialogElement;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -46,6 +46,28 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generate() {
|
||||||
|
try {
|
||||||
|
console.log(newAdventure.name);
|
||||||
|
const desc = await generateDescription(newAdventure.name);
|
||||||
|
newAdventure.description = desc;
|
||||||
|
// Do something with the updated newAdventure object
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
// Handle the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchImage() {
|
||||||
|
try {
|
||||||
|
const imageUrl = await getImage(newAdventure.name);
|
||||||
|
newAdventure.imageUrl = imageUrl;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
// Handle the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let activityInput: string = "";
|
let activityInput: string = "";
|
||||||
|
|
||||||
function activitySetup() {
|
function activitySetup() {
|
||||||
|
@ -102,6 +124,7 @@
|
||||||
class="input input-bordered w-full max-w-xs"
|
class="input input-bordered w-full max-w-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="date">Activity Types (Comma Seperated)</label>
|
<label for="date">Activity Types (Comma Seperated)</label>
|
||||||
<input
|
<input
|
||||||
|
@ -140,6 +163,12 @@
|
||||||
<!-- if there is a button in form, it will close the modal -->
|
<!-- if there is a button in form, it will close the modal -->
|
||||||
<button class="btn mt-4" on:click={close}>Close</button>
|
<button class="btn mt-4" on:click={close}>Close</button>
|
||||||
</form>
|
</form>
|
||||||
|
<button class="btn btn-secondary" on:click={generate}
|
||||||
|
>Generate Description</button
|
||||||
|
>
|
||||||
|
<button class="btn btn-secondary" on:click={searchImage}
|
||||||
|
>Search for Image</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
|
@ -19,11 +19,7 @@
|
||||||
dispatch("add", trip);
|
dispatch("add", trip);
|
||||||
}
|
}
|
||||||
|
|
||||||
function moreInfo() {
|
// TODO: Implement markVisited function
|
||||||
console.log(trip.id);
|
|
||||||
goto(`/trip/${trip.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function markVisited() {
|
function markVisited() {
|
||||||
console.log(trip.id);
|
console.log(trip.id);
|
||||||
dispatch("markVisited", trip);
|
dispatch("markVisited", trip);
|
||||||
|
|
51
src/lib/components/TripListModal.svelte
Normal file
51
src/lib/components/TripListModal.svelte
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import type { Trip } from "$lib/utils/types";
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
let modal: HTMLDialogElement;
|
||||||
|
let trips: Trip[] = [];
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
modal = document.getElementById("my_modal_1") as HTMLDialogElement;
|
||||||
|
if (modal) {
|
||||||
|
modal.showModal();
|
||||||
|
}
|
||||||
|
let res = await fetch("/api/trips");
|
||||||
|
trips = await res.json();
|
||||||
|
console.log(trips);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
dispatch("close");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog id="my_modal_1" class="modal">
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||||
|
<h3 class="font-bold text-lg">Choose a trip</h3>
|
||||||
|
{#each trips as trip}
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
on:click={() => {
|
||||||
|
dispatch("trip", trip);
|
||||||
|
close();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{trip.name}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<!-- close button -->
|
||||||
|
<button class="btn btn-neutral" on:click={close}>Close</button>
|
||||||
|
</dialog>
|
|
@ -9,12 +9,6 @@ import {
|
||||||
integer,
|
integer,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const featuredAdventures = pgTable("featuredAdventures", {
|
|
||||||
id: serial("id").primaryKey(),
|
|
||||||
name: text("name").notNull().unique(),
|
|
||||||
location: text("location"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const sharedAdventures = pgTable("sharedAdventures", {
|
export const sharedAdventures = pgTable("sharedAdventures", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
data: json("data").notNull(),
|
data: json("data").notNull(),
|
||||||
|
|
|
@ -63,3 +63,64 @@ export function addActivityType(
|
||||||
}
|
}
|
||||||
return adventureToEdit;
|
return adventureToEdit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a description for an adventure using the adventure title.
|
||||||
|
* @param adventureTitle - The title of the adventure.
|
||||||
|
* @returns A Promise that resolves to the description of the adventure.
|
||||||
|
*/
|
||||||
|
export async function generateDescription(adventureTitle: string) {
|
||||||
|
const url = `https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=extracts&exintro&explaintext&format=json&titles=${encodeURIComponent(
|
||||||
|
adventureTitle
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// Check if the query was successful
|
||||||
|
if (data.query && data.query.pages) {
|
||||||
|
const pageId = Object.keys(data.query.pages)[0];
|
||||||
|
const page = data.query.pages[pageId];
|
||||||
|
|
||||||
|
// Check if the page exists
|
||||||
|
if (page.extract) {
|
||||||
|
return page.extract;
|
||||||
|
} else {
|
||||||
|
return `No Wikipedia article found for "${adventureTitle}".`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `Error: ${data.error.info}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Wikipedia data:", error);
|
||||||
|
return `Error fetching Wikipedia data for "${adventureTitle}".`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getImage(adventureTitle: string) {
|
||||||
|
const url = `https://en.wikipedia.org/w/api.php?origin=*&action=query&prop=pageimages&format=json&piprop=original&titles=${adventureTitle}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// Check if the query was successful
|
||||||
|
if (data.query && data.query.pages) {
|
||||||
|
const pageId = Object.keys(data.query.pages)[0];
|
||||||
|
const page = data.query.pages[pageId];
|
||||||
|
|
||||||
|
// Check if the page has an image
|
||||||
|
if (page.original && page.original.source) {
|
||||||
|
return page.original.source;
|
||||||
|
} else {
|
||||||
|
return `No image found for "${adventureTitle}".`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `Error: ${data.error.info}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Wikipedia data:", error);
|
||||||
|
return `Error fetching Wikipedia data for "${adventureTitle}".`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { db } from "$lib/db/db.server";
|
import { db } from "$lib/db/db.server";
|
||||||
import { userPlannedTrips } from "$lib/db/schema";
|
import { adventureTable, userPlannedTrips } from "$lib/db/schema";
|
||||||
import { error, type RequestEvent } from "@sveltejs/kit";
|
import { error, type RequestEvent } from "@sveltejs/kit";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import type { Trip } from "$lib/utils/types";
|
||||||
|
|
||||||
export async function POST(event: RequestEvent): Promise<Response> {
|
export async function POST(event: RequestEvent): Promise<Response> {
|
||||||
if (!event.locals.user) {
|
if (!event.locals.user) {
|
||||||
|
@ -37,7 +38,6 @@ export async function POST(event: RequestEvent): Promise<Response> {
|
||||||
description: description || null,
|
description: description || null,
|
||||||
startDate: startDate || null,
|
startDate: startDate || null,
|
||||||
endDate: endDate || null,
|
endDate: endDate || null,
|
||||||
adventures: JSON.stringify([]),
|
|
||||||
})
|
})
|
||||||
.returning({ insertedId: userPlannedTrips.id })
|
.returning({ insertedId: userPlannedTrips.id })
|
||||||
.execute();
|
.execute();
|
||||||
|
@ -78,13 +78,6 @@ export async function GET(event: RequestEvent): Promise<Response> {
|
||||||
.where(eq(userPlannedTrips.userId, event.locals.user.id))
|
.where(eq(userPlannedTrips.userId, event.locals.user.id))
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// json parse the adventures into an Adventure array
|
|
||||||
for (let trip of trips) {
|
|
||||||
if (trip.adventures) {
|
|
||||||
trip.adventures = JSON.parse(trip.adventures as unknown as string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(trips), {
|
return new Response(JSON.stringify(trips), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -110,6 +103,11 @@ export async function DELETE(event: RequestEvent): Promise<Response> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let deleted = await db
|
||||||
|
.delete(adventureTable)
|
||||||
|
.where(eq(adventureTable.tripId, body.id))
|
||||||
|
.execute();
|
||||||
|
|
||||||
let res = await db
|
let res = await db
|
||||||
.delete(userPlannedTrips)
|
.delete(userPlannedTrips)
|
||||||
.where(
|
.where(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { db } from "$lib/db/db.server";
|
import { db } from "$lib/db/db.server";
|
||||||
import { adventureTable, featuredAdventures } from "$lib/db/schema";
|
import { adventureTable } from "$lib/db/schema";
|
||||||
import type { Adventure } from "$lib/utils/types";
|
import type { Adventure } from "$lib/utils/types";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,60 @@
|
||||||
export let data;
|
export let data;
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import AdventureCard from "$lib/components/AdventureCard.svelte";
|
import AdventureCard from "$lib/components/AdventureCard.svelte";
|
||||||
import type { Adventure } from "$lib/utils/types.js";
|
import type { Adventure, Trip } from "$lib/utils/types.js";
|
||||||
|
import AddFromFeatured from "$lib/components/AddLocationChooser.svelte";
|
||||||
|
import { addAdventure } from "../../services/adventureService.js";
|
||||||
|
import SucessToast from "$lib/components/SucessToast.svelte";
|
||||||
|
|
||||||
|
let isShowingToast: boolean = false;
|
||||||
|
let toastAction: string = "";
|
||||||
|
|
||||||
|
let adventureToAdd: Adventure | null = null;
|
||||||
|
|
||||||
|
function showToast(action: string) {
|
||||||
|
toastAction = action;
|
||||||
|
isShowingToast = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isShowingToast = false;
|
||||||
|
toastAction = "";
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
async function add(event: CustomEvent<Adventure>) {
|
async function add(event: CustomEvent<Adventure>) {
|
||||||
let detailAdventure = event.detail;
|
adventureToAdd = event.detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addToTrip = async (event: { detail: Trip }) => {
|
||||||
|
if (!adventureToAdd) {
|
||||||
|
showToast("Failed to add adventure");
|
||||||
|
adventureToAdd = null;
|
||||||
|
} else {
|
||||||
|
let detailAdventure = adventureToAdd;
|
||||||
|
detailAdventure.tripId = event.detail.id;
|
||||||
|
detailAdventure.type = "planner";
|
||||||
|
console.log(detailAdventure);
|
||||||
|
let res = await fetch("/api/planner", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
detailAdventure,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (res.status === 401) {
|
||||||
|
goto("/login");
|
||||||
|
} else {
|
||||||
|
showToast("Adventure added to trip!");
|
||||||
|
adventureToAdd = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function addToVisted() {
|
||||||
|
let detailAdventure = adventureToAdd;
|
||||||
|
adventureToAdd = null;
|
||||||
|
|
||||||
const response = await fetch("/api/visits", {
|
const response = await fetch("/api/visits", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -19,10 +69,47 @@
|
||||||
|
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
goto("/login");
|
goto("/login");
|
||||||
|
} else {
|
||||||
|
showToast("Adventure added to visited list!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addIdea() {
|
||||||
|
let detailAdventure = adventureToAdd;
|
||||||
|
adventureToAdd = null;
|
||||||
|
|
||||||
|
const response = await fetch("/api/planner", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
detailAdventure,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 401) {
|
||||||
|
goto("/login");
|
||||||
|
} else {
|
||||||
|
showToast("Adventure added to idea list!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{#if isShowingToast}
|
||||||
|
<SucessToast action={toastAction} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if adventureToAdd}
|
||||||
|
<AddFromFeatured
|
||||||
|
adventure={adventureToAdd}
|
||||||
|
on:close={() => (adventureToAdd = null)}
|
||||||
|
on:visited={addToVisted}
|
||||||
|
on:idea={addIdea}
|
||||||
|
on:trip={addToTrip}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="flex justify-center items-center w-full mt-4 mb-4">
|
<div class="flex justify-center items-center w-full mt-4 mb-4">
|
||||||
<article class="prose">
|
<article class="prose">
|
||||||
<h1 class="text-center">Featured Adventure Locations</h1>
|
<h1 class="text-center">Featured Adventure Locations</h1>
|
||||||
|
|
|
@ -271,7 +271,7 @@
|
||||||
{#if tripPlans.length !== 0}
|
{#if tripPlans.length !== 0}
|
||||||
<div class="flex justify-center items-center w-full mt-4 mb-4">
|
<div class="flex justify-center items-center w-full mt-4 mb-4">
|
||||||
<article class="prose">
|
<article class="prose">
|
||||||
<h1 class="text-center">My Trip Plans</h1>
|
<h1 class="text-center">My Trip Ideas</h1>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue