mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-01 19:25:17 +02:00
feat: Add "Planner" button to Navbar component
The code changes include adding a "Planner" button to the Navbar component. This button allows users to navigate to the Planner page. The changes involve modifying the Navbar component in the src/lib/components/Navbar.svelte file.
This commit is contained in:
parent
3127784632
commit
716323657b
6 changed files with 424 additions and 2 deletions
|
@ -76,6 +76,20 @@
|
|||
></iconify-icon></button
|
||||
>
|
||||
{/if}
|
||||
{#if type == "planner"}
|
||||
<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}
|
||||
><iconify-icon icon="mdi:file-document-edit" class="text-2xl"
|
||||
></iconify-icon></button
|
||||
>
|
||||
<button class="btn btn-secondary" on:click={remove}
|
||||
><iconify-icon icon="mdi:trash-can-outline" class="text-2xl"
|
||||
></iconify-icon></button
|
||||
>
|
||||
{/if}
|
||||
{#if type == "featured"}
|
||||
<button class="btn btn-primary" on:click={add}
|
||||
><iconify-icon icon="mdi:plus" class="text-2xl"
|
||||
|
|
|
@ -78,6 +78,11 @@
|
|||
<button class="btn btn-primary my-2 md:my-0 md:mr-4" on:click={goToLog}
|
||||
>My Log</button
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary my-2 md:my-0 md:mr-4"
|
||||
on:click={() => goto("/planner")}>Planner</button
|
||||
>
|
||||
|
||||
<!-- <button
|
||||
class="btn btn-primary my-2 md:my-0 md:mr-4"
|
||||
on:click={() => goto("/planner")}>Planner</button
|
||||
|
|
223
src/routes/api/planner/+server.ts
Normal file
223
src/routes/api/planner/+server.ts
Normal file
|
@ -0,0 +1,223 @@
|
|||
import { lucia } from "$lib/server/auth";
|
||||
import { error, type RequestEvent } from "@sveltejs/kit";
|
||||
import { adventureTable } from "$lib/db/schema";
|
||||
import { db } from "$lib/db/db.server";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import type { Adventure } from "$lib/utils/types";
|
||||
|
||||
// Gets all the adventures that the user has visited
|
||||
export async function GET(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
let result = await db
|
||||
.select()
|
||||
.from(adventureTable)
|
||||
.where(
|
||||
and(
|
||||
eq(adventureTable.userId, event.locals.user.id),
|
||||
eq(adventureTable.type, "planner")
|
||||
)
|
||||
)
|
||||
.execute();
|
||||
return new Response(
|
||||
// turn the result into an Adventure object array
|
||||
JSON.stringify(
|
||||
result.map((r) => {
|
||||
const adventure: Adventure = r as Adventure;
|
||||
if (typeof adventure.activityTypes === "string") {
|
||||
try {
|
||||
adventure.activityTypes = JSON.parse(adventure.activityTypes);
|
||||
} catch (error) {
|
||||
console.error("Error parsing activityTypes:", error);
|
||||
adventure.activityTypes = undefined;
|
||||
}
|
||||
}
|
||||
return adventure;
|
||||
})
|
||||
),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// deletes the adventure given the adventure id and the user object
|
||||
export async function DELETE(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// get id from the body
|
||||
const { id } = await event.request.json();
|
||||
|
||||
if (!id) {
|
||||
return new Response(JSON.stringify({ error: "No id found" }), {
|
||||
status: 400,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let res = await db
|
||||
.delete(adventureTable)
|
||||
.where(
|
||||
and(
|
||||
eq(adventureTable.userId, event.locals.user.id),
|
||||
eq(adventureTable.id, Number(id))
|
||||
)
|
||||
)
|
||||
.execute();
|
||||
|
||||
return new Response(JSON.stringify({ id: id, res: res }), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const body = await event.request.json();
|
||||
if (!body.detailAdventure) {
|
||||
return error(400, {
|
||||
message: "No adventure data provided",
|
||||
});
|
||||
}
|
||||
|
||||
const { name, location, date, description, activityTypes, rating } =
|
||||
body.detailAdventure;
|
||||
|
||||
if (!name) {
|
||||
return error(400, {
|
||||
message: "Name field is required!",
|
||||
});
|
||||
}
|
||||
|
||||
if (rating && (rating < 1 || rating > 5)) {
|
||||
return error(400, {
|
||||
message: "Rating must be between 1 and 5",
|
||||
});
|
||||
}
|
||||
|
||||
console.log(activityTypes);
|
||||
|
||||
// insert the adventure to the user's visited list
|
||||
let res = await db
|
||||
.insert(adventureTable)
|
||||
.values({
|
||||
userId: event.locals.user.id,
|
||||
type: "planner",
|
||||
name: name,
|
||||
location: location || null,
|
||||
date: date || null,
|
||||
description: description || null,
|
||||
activityTypes: JSON.stringify(activityTypes) || null,
|
||||
rating: rating || null,
|
||||
})
|
||||
.returning({ insertedId: adventureTable.id })
|
||||
.execute();
|
||||
|
||||
let insertedId = res[0].insertedId;
|
||||
console.log(insertedId);
|
||||
|
||||
body.detailAdventure.id = insertedId;
|
||||
|
||||
// return a response with the adventure object values
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
adventure: body.detailAdventure,
|
||||
message: { message: "Adventure added" },
|
||||
id: insertedId,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// put route to update existing adventure
|
||||
export async function PUT(event: RequestEvent): Promise<Response> {
|
||||
if (!event.locals.user) {
|
||||
return new Response(JSON.stringify({ error: "No user found" }), {
|
||||
status: 401,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const body = await event.request.json();
|
||||
if (!body.detailAdventure) {
|
||||
return error(400, {
|
||||
message: "No adventure data provided",
|
||||
});
|
||||
}
|
||||
|
||||
const { name, location, date, description, activityTypes, id, rating } =
|
||||
body.detailAdventure;
|
||||
|
||||
if (!name) {
|
||||
return error(400, {
|
||||
message: "Name field is required!",
|
||||
});
|
||||
}
|
||||
|
||||
// update the adventure in the user's visited list
|
||||
await db
|
||||
.update(adventureTable)
|
||||
.set({
|
||||
name: name,
|
||||
location: location,
|
||||
date: date,
|
||||
description: description,
|
||||
rating: rating,
|
||||
activityTypes: JSON.stringify(activityTypes),
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(adventureTable.userId, event.locals.user.id),
|
||||
eq(adventureTable.id, Number(id))
|
||||
)
|
||||
)
|
||||
.execute();
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
adventure: body.detailAdventure,
|
||||
message: { message: "Adventure updated" },
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -18,7 +18,12 @@ export async function GET(event: RequestEvent): Promise<Response> {
|
|||
let result = await db
|
||||
.select()
|
||||
.from(adventureTable)
|
||||
.where(eq(adventureTable.userId, event.locals.user.id))
|
||||
.where(
|
||||
and(
|
||||
eq(adventureTable.userId, event.locals.user.id),
|
||||
eq(adventureTable.type, "mylog")
|
||||
)
|
||||
)
|
||||
.execute();
|
||||
return new Response(
|
||||
// turn the result into an Adventure object array
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { redirect } from "@sveltejs/kit";
|
||||
import type { PageServerLoad } from "./$types";
|
||||
import type { Adventure } from "$lib/utils/types";
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (!event.locals.user) {
|
||||
return redirect(302, "/login");
|
||||
}
|
||||
const response = await event.fetch("/api/planner");
|
||||
const result = await response.json();
|
||||
return {
|
||||
result,
|
||||
};
|
||||
};
|
|
@ -1 +1,162 @@
|
|||
<h1>Welcome to the planner</h1>
|
||||
<script lang="ts">
|
||||
import type { Adventure } from "$lib/utils/types.js";
|
||||
import { onMount } from "svelte";
|
||||
import AdventureCard from "$lib/components/AdventureCard.svelte";
|
||||
import EditModal from "$lib/components/EditModal.svelte";
|
||||
import MoreFieldsInput from "$lib/components/CreateNewAdventure.svelte";
|
||||
export let data;
|
||||
let plans: Adventure[] = [];
|
||||
let isLoading = true;
|
||||
|
||||
onMount(async () => {
|
||||
console.log(data);
|
||||
plans = data.result;
|
||||
isLoading = false;
|
||||
});
|
||||
|
||||
let isShowingMoreFields: boolean = false;
|
||||
|
||||
let adventureToEdit: Adventure | undefined;
|
||||
|
||||
console.log(data);
|
||||
|
||||
function editPlan(event: { detail: number }) {
|
||||
const adventure = plans.find((adventure) => adventure.id === event.detail);
|
||||
if (adventure) {
|
||||
adventureToEdit = adventure;
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
adventureToEdit = undefined;
|
||||
isShowingMoreFields = false;
|
||||
}
|
||||
|
||||
function savePlan(event: { detail: Adventure }) {
|
||||
console.log("Event", event.detail);
|
||||
let detailAdventure = event.detail;
|
||||
|
||||
// put request to /api/visits with id and adventure data
|
||||
fetch("/api/planner", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
detailAdventure,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Success:", data);
|
||||
// update local array with new data
|
||||
const index = plans.findIndex(
|
||||
(adventure) => adventure.id === detailAdventure.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
plans[index] = detailAdventure;
|
||||
}
|
||||
adventureToEdit = undefined;
|
||||
// showToast("Adventure edited successfully!");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
|
||||
function removeAdventure(event: { detail: number }) {
|
||||
console.log("Event ID " + event.detail);
|
||||
// send delete request to server at /api/visits
|
||||
fetch("/api/visits", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ id: event.detail }),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("Success:", data);
|
||||
// remove adventure from array where id matches
|
||||
plans = plans.filter((adventure) => adventure.id !== event.detail);
|
||||
// showToast("Adventure removed successfully!");
|
||||
// visitCount.update((n) => n - 1);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
|
||||
const createNewAdventure = (event: { detail: Adventure }) => {
|
||||
isShowingMoreFields = false;
|
||||
let detailAdventure = event.detail;
|
||||
console.log("Event" + event.detail.name);
|
||||
|
||||
fetch("/api/planner", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
detailAdventure,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return response.json().then((data) => {
|
||||
throw new Error(
|
||||
data.error || `Failed to add adventure - ${data?.message}`
|
||||
);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
// add to local array for instant view update
|
||||
plans = [...plans, data.adventure];
|
||||
// showToast("Adventure added successfully!");
|
||||
// visitCount.update((n) => n + 1);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
// showToast(error.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row items-center justify-center gap-4">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
on:click={() => (isShowingMoreFields = !isShowingMoreFields)}
|
||||
>
|
||||
<iconify-icon icon="mdi:plus" class="text-2xl"></iconify-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if isLoading}
|
||||
<div class="flex justify-center items-center w-full mt-16">
|
||||
<span class="loading loading-spinner w-24 h-24"></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="grid xl:grid-cols-3 lg:grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-4 mt-4 content-center auto-cols-auto ml-6 mr-6"
|
||||
>
|
||||
{#each plans as adventure (adventure.id)}
|
||||
<AdventureCard
|
||||
{adventure}
|
||||
type="planner"
|
||||
on:edit={editPlan}
|
||||
on:remove={removeAdventure}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if adventureToEdit && adventureToEdit.id != undefined}
|
||||
<EditModal bind:adventureToEdit on:submit={savePlan} on:close={handleClose} />
|
||||
{/if}
|
||||
|
||||
{#if isShowingMoreFields}
|
||||
<MoreFieldsInput on:create={createNewAdventure} on:close={handleClose} />
|
||||
{/if}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue