1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-20 05:19:38 +02:00

Merge branch 'development' into seanmorley15-patch-1

This commit is contained in:
Sean Morley 2024-04-02 18:16:02 -04:00 committed by GitHub
commit ad45447e88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 6726 additions and 6493 deletions

View file

@ -1,4 +1,5 @@
# AdventureLog: Embark, Explore, Remember. 🌍 # AdventureLog: Embark, Explore, Remember. 🌍
_**⚠️ AdvenutreLog is in early development and is not at all deployable in the current form!**_ _**⚠️ AdvenutreLog is in early development and is not at all deployable in the current form!**_
### *"Never forget an adventure with AdventureLog - Your ultimate travel companion!"* ### *"Never forget an adventure with AdventureLog - Your ultimate travel companion!"*
----- -----
@ -9,3 +10,4 @@ _**⚠️ AdvenutreLog is in early development and is not at all deployable in t
2. Edit the `docker-compose.yml` file and change the database password 2. Edit the `docker-compose.yml` file and change the database password
3. Run `docker compose up -d` to build the image and start the container 3. Run `docker compose up -d` to build the image and start the container
4. Wait for the app to start up and migrate then visit the port and enjoy! 4. Wait for the app to start up and migrate then visit the port and enjoy!

View file

@ -1,15 +1,15 @@
import type { Config } from 'drizzle-kit'; import type { Config } from "drizzle-kit";
import * as dotenv from 'dotenv'; import * as dotenv from "dotenv";
dotenv.config(); dotenv.config();
const { DATABASE_URL } = process.env; const { DATABASE_URL } = process.env;
if (!DATABASE_URL) { if (!DATABASE_URL) {
throw new Error('No url'); throw new Error("No url");
} }
export default { export default {
schema: './src/lib/db/schema.ts', schema: "./src/lib/db/schema.ts",
out: './migrations', out: "./migrations",
driver: 'pg', driver: "pg",
dbCredentials: { dbCredentials: {
connectionString: DATABASE_URL connectionString: DATABASE_URL,
} },
} satisfies Config; } satisfies Config;

View file

@ -3,4 +3,4 @@ export default {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };

View file

@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />

View file

@ -1,31 +1,77 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from "svelte";
import locationDot from "$lib/assets/locationDot.svg"; import locationDot from "$lib/assets/locationDot.svg";
import calendar from "$lib/assets/calendar.svg"; import calendar from "$lib/assets/calendar.svg";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let name:String; export let type: String;
export let location:String;
export let created:string; export let name: String;
export let id:Number; export let location: String;
export let created: string;
export let id: Number;
function remove() { function remove() {
dispatch('remove', id); dispatch("remove", id);
} }
function edit() { function edit() {
dispatch('edit', id) dispatch("edit", id);
}
function add() {
dispatch("add", { name, location });
} }
</script> </script>
<div class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral shadow-xl overflow-hidden"> {#if type === "mylog"}
<div
class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral shadow-xl overflow-hidden"
>
<div class="card-body"> <div class="card-body">
<h2 class="card-title overflow-ellipsis">{name}</h2> <h2 class="card-title overflow-ellipsis">{name}</h2>
<p><img src={locationDot} class="inline-block -mt-1 mr-1" alt="Logo" />{location}</p> {#if location !== ""}
<p><img src={calendar} class="inline-block -mt-1 mr-1" alt="Logo" />{created}</p> <p>
<img
src={locationDot}
class="inline-block -mt-1 mr-1"
alt="Logo"
/>{location}
</p>
{/if}
{#if created !== ""}
<p>
<img
src={calendar}
class="inline-block -mt-1 mr-1"
alt="Logo"
/>{created}
</p>
{/if}
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<button class="btn btn-primary" on:click={edit}>Edit</button> <button class="btn btn-primary" on:click={edit}>Edit</button>
<button class="btn btn-secondary" on:click={remove}>Remove</button> <button class="btn btn-secondary" on:click={remove}>Remove</button>
</div> </div>
</div> </div>
</div> </div>
{/if}
{#if type === "featured"}
<div
class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral shadow-xl overflow-hidden"
>
<div class="card-body">
<h2 class="card-title overflow-ellipsis">{name}</h2>
{#if location != ""}
<p>
<img
src={locationDot}
class="inline-block -mt-1 mr-1"
alt="Logo"
/>{location}
</p>
{/if}
<div class="card-actions justify-end">
<button class="btn btn-primary" on:click={add}>Add</button>
</div>
</div>
</div>
{/if}

View file

@ -0,0 +1,68 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { Adventure } from "$lib/utils/types";
const dispatch = createEventDispatcher();
import { onMount } from "svelte";
let modal: HTMLDialogElement;
onMount(() => {
modal = document.getElementById("my_modal_1") as HTMLDialogElement;
if (modal) {
modal.showModal();
}
});
function close() {
dispatch("close");
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === "Escape") {
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">Edit Adventure</h3>
<p class="py-4">Press ESC key or click the button below to close</p>
<div
class="modal-action items-center"
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
>
<form method="dialog" style="width: 100%;">
<div>
<label for="name">Name</label>
<input
type="text"
id="name"
class="input input-bordered w-full max-w-xs"
/>
</div>
<div>
<label for="location">Location</label>
<input
type="text"
id="location"
class="input input-bordered w-full max-w-xs"
/>
</div>
<div>
<label for="created">Created</label>
<input
type="date"
id="created"
class="input input-bordered w-full max-w-xs"
/>
</div>
<!-- <button class="btn btn-primary mr-4 mt-4" on:click={submit}>Save</button
> -->
<!-- if there is a button in form, it will close the modal -->
<button class="btn mt-4" on:click={close}>Close</button>
</form>
</div>
</div>
</dialog>

View file

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
export let editId:number = NaN; export let editId: number = NaN;
export let editName:string = ''; export let editName: string = "";
export let editLocation:string = ''; export let editLocation: string = "";
export let editCreated: string = ''; export let editCreated: string = "";
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from "svelte";
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";
let modal: HTMLDialogElement; let modal: HTMLDialogElement;
let originalName = editName; let originalName = editName;
@ -18,42 +18,70 @@
} }
}); });
function submit() { function submit() {
const adventureEdited: Adventure = { id: editId, name: editName, location: editLocation, created: editCreated }; const adventureEdited: Adventure = {
dispatch('submit', adventureEdited); id: editId,
console.log(adventureEdited) name: editName,
location: editLocation,
created: editCreated,
};
dispatch("submit", adventureEdited);
console.log(adventureEdited);
} }
function close() { function close() {
dispatch('close'); dispatch("close");
} }
function handleKeydown(event: KeyboardEvent) { function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') { if (event.key === "Escape") {
close(); close();
} }
} }
</script> </script>
<dialog id="my_modal_1" class="modal" > <dialog id="my_modal_1" class="modal">
<!-- svelte-ignore a11y-no-noninteractive-element-interactions --> <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex --> <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0"> <div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
<h3 class="font-bold text-lg">Edit Adventure {originalName}</h3> <h3 class="font-bold text-lg">Edit Adventure {originalName}</h3>
<p class="py-4">Press ESC key or click the button below to close</p> <p class="py-4">Press ESC key or click the button below to close</p>
<div class="modal-action"> <div
<form method="dialog"> class="modal-action items-center"
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
>
<form method="dialog" style="width: 100%;">
<div>
<label for="name">Name</label> <label for="name">Name</label>
<input type="text" id="name" bind:value={editName} class="input input-bordered w-full max-w-xs" /> <input
type="text"
id="name"
bind:value={editName}
class="input input-bordered w-full max-w-xs"
/>
</div>
<div>
<label for="location">Location</label> <label for="location">Location</label>
<input type="text" id="location" bind:value={editLocation} class="input input-bordered w-full max-w-xs" /> <input
type="text"
id="location"
bind:value={editLocation}
class="input input-bordered w-full max-w-xs"
/>
</div>
<div>
<label for="created">Created</label> <label for="created">Created</label>
<input type="date" id="created" bind:value={editCreated} class="input input-bordered w-full max-w-xs" /> <input
<button class="btn btn-primary" on:click={submit}>Save</button> type="date"
id="created"
bind:value={editCreated}
class="input input-bordered w-full max-w-xs"
/>
</div>
<button class="btn btn-primary mr-4 mt-4" on:click={submit}>Save</button
>
<!-- 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" on:click={close}>Close</button> <button class="btn mt-4" on:click={close}>Close</button>
</form> </form>
</div> </div>
</div> </div>

View file

@ -1,24 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import locationDot from "$lib/assets/locationDot.svg";
import calendar from "$lib/assets/calendar.svg";
const dispatch = createEventDispatcher();
export let name:String;
export let location:String;
function add() {
dispatch('add', {name, location});
}
</script>
<div class="card min-w-max lg:w-96 md:w-80 sm:w-60 xs:w-40 bg-neutral shadow-xl overflow-hidden">
<div class="card-body">
<h2 class="card-title overflow-ellipsis">{name}</h2>
<p><img src={locationDot} class="inline-block -mt-1 mr-1" alt="Logo" />{location}</p>
<div class="card-actions justify-end">
<button class="btn btn-primary" on:click={add}>Add</button>
</div>
</div>
</div>

View file

@ -2,18 +2,52 @@
import pinLogo from "$lib/assets/pinLogo.svg"; import pinLogo from "$lib/assets/pinLogo.svg";
</script> </script>
<footer class="footer items-center p-4 bg-neutral text-neutral-content fixed bottom-0 left-0 w-full"> <footer
class="footer items-center p-4 bg-neutral text-neutral-content fixed bottom-0 left-0 w-full"
>
<aside class="items-center grid-flow-col"> <aside class="items-center grid-flow-col">
<img src={pinLogo} class="inline-block -mt-1 mr-1" alt="Logo" /> <img src={pinLogo} class="inline-block -mt-1 mr-1" alt="Logo" />
<p>Copyright © 2024 Sean Morley - All rights reserved</p> <p>Copyright © 2024 Sean Morley - All rights reserved</p>
</aside> </aside>
<nav class="grid-flow-col gap-4 md:place-self-center md:justify-self-end"> <nav class="grid-flow-col gap-4 md:place-self-center md:justify-self-end">
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y-missing-attribute -->
<a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"><path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"></path></svg> <a
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current"
><path
d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"
></path></svg
>
</a> </a>
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y-missing-attribute -->
<a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"><path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"></path></svg></a> <a
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current"
><path
d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"
></path></svg
></a
>
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y-missing-attribute -->
<a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"><path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"></path></svg></a> <a
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current"
><path
d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"
></path></svg
></a
>
</nav> </nav>
</footer> </footer>

View file

@ -1,44 +1,51 @@
<script lang="ts"> <script lang="ts">
import { visitCount } from '$lib/utils/stores/visitCountStore'; import { visitCount } from "$lib/utils/stores/visitCountStore";
import { goto } from '$app/navigation'; import { goto } from "$app/navigation";
async function goHome() { async function goHome() {
goto('/'); goto("/");
} }
async function goToLog() { async function goToLog() {
goto('/log'); goto("/log");
} }
async function goToFeatured() { async function goToFeatured() {
goto('/featured'); goto("/featured");
} }
let count = 0; let count = 0;
visitCount.subscribe((value) => { visitCount.subscribe((value) => {
count = value; count = value;
}); });
// Set the visit count to the number of adventures stored in local storage // Set the visit count to the number of adventures stored in local storage
const isBrowser = typeof window !== 'undefined'; const isBrowser = typeof window !== "undefined";
if (isBrowser) { if (isBrowser) {
const storedAdventures = localStorage.getItem('adventures'); const storedAdventures = localStorage.getItem("adventures");
if (storedAdventures) { if (storedAdventures) {
let parsed = JSON.parse(storedAdventures); let parsed = JSON.parse(storedAdventures);
visitCount.set (parsed.length); visitCount.set(parsed.length);
}
} }
}
</script> </script>
<div class="navbar bg-base-100 flex flex-col md:flex-row"> <div class="navbar bg-base-100 flex flex-col md:flex-row">
<div class="navbar-start flex justify-around md:justify-start"> <div class="navbar-start flex justify-around md:justify-start">
<button class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2" on:click={goHome}>Home</button> <button
<button class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2" on:click={goToLog}>My Log</button> class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2"
<button class="btn btn-primary my-2 md:my-0" on:click={goToFeatured}>Featured</button> on:click={goHome}>Home</button
>
<button
class="btn btn-primary my-2 md:my-0 md:mr-4 md:ml-2"
on:click={goToLog}>My Log</button
>
<button class="btn btn-primary my-2 md:my-0" on:click={goToFeatured}
>Featured</button
>
</div> </div>
<div class="navbar-center flex justify-center md:justify-center"> <div class="navbar-center flex justify-center md:justify-center">
<a class="btn btn-ghost text-xl" href="/">AdventureLog 🗺️</a> <a class="btn btn-ghost text-xl" href="/">AdventureLog 🗺️</a>
</div> </div>
<div class="navbar-end flex justify-around md:justify-end mr-4"> <div class="navbar-end flex justify-around md:justify-end mr-4">
<p>Adventures: {count} </p> <p>Adventures: {count}</p>
</div> </div>
</div> </div>

View file

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
export let action:string; export let action: string;
</script> </script>
<div class="toast toast-top toast-end z-50"> <div class="toast toast-top toast-end z-50">
<div class="alert alert-info"> <div class="alert alert-info">
<span>Adventure {action} successfully!</span> <span>Adventure {action} successfully!</span>
</div> </div>
</div> </div>

View file

@ -1,8 +1,8 @@
import { drizzle } from 'drizzle-orm/postgres-js'; import { drizzle } from "drizzle-orm/postgres-js";
import postgres from 'postgres'; import postgres from "postgres";
import dotenv from 'dotenv'; import dotenv from "dotenv";
dotenv.config(); dotenv.config();
const { DATABASE_URL } = process.env; const { DATABASE_URL } = process.env;
const client = postgres(DATABASE_URL) const client = postgres(DATABASE_URL);
export const db = drizzle(client, {}); export const db = drizzle(client, {});

View file

@ -1,12 +1,12 @@
import { pgTable,json,text,serial } from "drizzle-orm/pg-core"; import { pgTable, json, text, serial } from "drizzle-orm/pg-core";
export const featuredAdventures = pgTable("featuredAdventures",{ export const featuredAdventures = pgTable("featuredAdventures", {
id:serial("id").primaryKey(), id: serial("id").primaryKey(),
name:text("name").notNull(), name: text("name").notNull(),
location:text("location"), 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(),
}) });

View file

@ -1,7 +1,8 @@
// place files you want to import through the `$lib` alias in this folder. // place files you want to import through the `$lib` alias in this folder.
export function generateRandomString() { export function generateRandomString() {
let randomString = ''; let randomString = "";
const digits = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; const digits =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const randomIndex = Math.floor(Math.random() * digits.length); const randomIndex = Math.floor(Math.random() * digits.length);

View file

@ -1,4 +1,3 @@
import { onMount } from "svelte";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
export const visitCount = writable(0); export const visitCount = writable(0);

View file

@ -1,6 +1,6 @@
export interface Adventure { export interface Adventure {
id: number id: number;
name: string; name: string;
location: string; location: string;
created:string created: string;
} }

View file

@ -4,16 +4,15 @@
import "../app.css"; import "../app.css";
// only show footer if scrolled to the bottom // only show footer if scrolled to the bottom
</script> </script>
<Navbar /> <Navbar />
<section> <section>
<slot></slot> <slot />
</section> </section>
<!-- <Footer /> --> <!-- <Footer /> -->
<!-- <style> <!-- <style>
section { section {
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 5rem; margin-bottom: 5rem;

View file

@ -1,13 +1,25 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from "$app/navigation";
import campingDrawing from "$lib/assets/camping.svg"; import campingDrawing from "$lib/assets/camping.svg";
import { visitCount } from "$lib/utils/stores/visitCountStore";
async function navToLog() { async function navToLog() {
goto('/log'); goto("/log");
} }
</script> </script>
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
<article class="prose"><h1 class="mb-4">Welcome. Let's get Exploring!</h1></article> <article class="prose">
<h1 class="mb-4">Welcome. Let's get Exploring!</h1>
</article>
<img src={campingDrawing} class="w-1/4 mb-4" alt="Logo" /> <img src={campingDrawing} class="w-1/4 mb-4" alt="Logo" />
<button on:click={navToLog} class="btn btn-primary">Open Log</button> <button on:click={navToLog} class="btn btn-primary">Open Log</button>
<div class="stats shadow">
<div class="stat">
<div class="stat-title">Logged Adventures</div>
<div class="stat-value text-center">{$visitCount}</div>
<!-- <div class="stat-desc">21% more than last month</div> -->
</div>
</div>
</div> </div>

View file

@ -3,9 +3,12 @@ import { sharedAdventures } from "$lib/db/schema";
import type { Adventure } from "$lib/utils/types"; import type { Adventure } from "$lib/utils/types";
export async function POST({ request }: { request: Request }) { export async function POST({ request }: { request: Request }) {
const { key , data } = await request.json(); const { key, data } = await request.json();
let adventure = data as Adventure; let adventure = data as Adventure;
console.log(adventure); console.log(adventure);
await db.insert(sharedAdventures).values({ id:key,data:adventure }).execute(); await db
return new Response(JSON.stringify({key:key})); .insert(sharedAdventures)
.values({ id: key, data: adventure })
.execute();
return new Response(JSON.stringify({ key: key }));
} }

View file

@ -1,11 +1,13 @@
import { db } from '$lib/db/db.server'; import { db } from "$lib/db/db.server";
import { featuredAdventures } from '$lib/db/schema'; import { featuredAdventures } from "$lib/db/schema";
import type { Adventure } from '$lib/utils/types'; import type { Adventure } from "$lib/utils/types";
export const load = async () => {
export const load = (async () => { const result = await db
const result = await db.select().from(featuredAdventures).orderBy(featuredAdventures.id); .select()
.from(featuredAdventures)
.orderBy(featuredAdventures.id);
return { return {
result : result as Adventure[] result: result as Adventure[],
}; };
}) };

View file

@ -1,20 +1,19 @@
<script lang="ts"> <script lang="ts">
export let data export let data;
console.log(data.result); console.log(data.result);
import FeaturedAdventureCard from '$lib/components/FeaturedAdventureCard.svelte'; import AdventureCard from "$lib/components/AdventureCard.svelte";
import type { Adventure } from '$lib/utils/types.js'; import type { Adventure } from "$lib/utils/types.js";
import { addAdventure, getNextId } from '../../services/adventureService.js'; import { addAdventure, getNextId } from "../../services/adventureService.js";
function add(event: CustomEvent<{name: string, location: string}>) { function add(event: CustomEvent<{ name: string; location: string }>) {
console.log(event.detail); console.log(event.detail);
let newAdventure:Adventure = { let newAdventure: Adventure = {
id: getNextId(), id: getNextId(),
name: event.detail.name, name: event.detail.name,
location: event.detail.location, location: event.detail.location,
created: "" created: "",
} };
addAdventure(newAdventure); addAdventure(newAdventure);
} }
</script> </script>
@ -24,8 +23,17 @@
</article> </article>
</div> </div>
<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"> <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 data.result as adventure (adventure.id)} {#each data.result as adventure (adventure.id)}
<FeaturedAdventureCard on:add={add} name={adventure.name} location={adventure.location} /> <AdventureCard
type="featured"
on:add={add}
name={adventure.name}
location={adventure.location}
created=""
id={NaN}
/>
{/each} {/each}
</div> </div>

View file

@ -1,77 +1,90 @@
<script lang="ts"> <script lang="ts">
import AdventureCard from "$lib/components/AdventureCard.svelte"; import AdventureCard from "$lib/components/AdventureCard.svelte";
import type { Adventure } from '$lib/utils/types'; import type { Adventure } from "$lib/utils/types";
import { addAdventure, clearAdventures, getAdventures, getNextId, removeAdventure ,saveEdit } from "../../services/adventureService"; import {
import { onMount } from 'svelte'; addAdventure,
clearAdventures,
getAdventures,
getNextId,
removeAdventure,
saveEdit,
} from "../../services/adventureService";
import { onMount } from "svelte";
import { exportData } from "../../services/export"; import { exportData } from "../../services/export";
import { importData } from "../../services/import"; import { importData } from "../../services/import";
import exportFile from "$lib/assets/exportFile.svg"; import exportFile from "$lib/assets/exportFile.svg";
import deleteIcon from "$lib/assets/deleteIcon.svg"; import deleteIcon from "$lib/assets/deleteIcon.svg";
import SucessToast from "$lib/components/SucessToast.svelte"; import SucessToast from "$lib/components/SucessToast.svelte";
import mapDrawing from "$lib/assets/adventure_map.svg" import mapDrawing from "$lib/assets/adventure_map.svg";
import EditModal from "$lib/components/EditModal.svelte"; import EditModal from "$lib/components/EditModal.svelte";
import { generateRandomString } from "$lib"; import { generateRandomString } from "$lib";
let newName = ''; let newName = "";
let newLocation = ''; let newLocation = "";
let editId:number = NaN; let editId: number = NaN;
let editName:string = ''; let editName: string = "";
let editLocation:string = ''; let editLocation: string = "";
let editCreated: string = ''; let editCreated: string = "";
let adventures: Adventure[] = []; let adventures: Adventure[] = [];
let isShowingToast:boolean = false; let isShowingToast: boolean = false;
let toastAction:string = ''; let toastAction: string = "";
function showToast(action:string) { function showToast(action: string) {
toastAction = action; toastAction = action;
isShowingToast = true; isShowingToast = true;
console.log('showing toast'); console.log("showing toast");
setTimeout(() => { setTimeout(() => {
isShowingToast = false; isShowingToast = false;
toastAction = ''; toastAction = "";
console.log('hiding toast'); console.log("hiding toast");
}, 3000); }, 3000);
} }
const createNewAdventure = () => { const createNewAdventure = () => {
let currentDate = new Date(); let currentDate = new Date();
let dateString = currentDate.toISOString().slice(0,10); // Get date in "yyyy-mm-dd" format let dateString = currentDate.toISOString().slice(0, 10); // Get date in "yyyy-mm-dd" format
const newAdventure: Adventure = { id:getNextId(), name: newName, location: newLocation, created: dateString}; const newAdventure: Adventure = {
id: getNextId(),
name: newName,
location: newLocation,
created: dateString,
};
addAdventure(newAdventure); addAdventure(newAdventure);
newName = ''; // Reset newName and newLocation after adding adventure newName = ""; // Reset newName and newLocation after adding adventure
newLocation = ''; newLocation = "";
adventures = getAdventures(); // add to local array adventures = getAdventures(); // add to local array
showToast('added'); showToast("added");
}; };
onMount(async () => { onMount(async () => {
adventures = getAdventures() adventures = getAdventures();
}); });
function triggerRemoveAdventure(event: { detail: number; }) { function triggerRemoveAdventure(event: { detail: number }) {
removeAdventure(event) removeAdventure(event);
showToast('removed'); showToast("removed");
adventures = getAdventures() adventures = getAdventures();
} }
function saveAdventure(event: { detail: Adventure; }) { function saveAdventure(event: { detail: Adventure }) {
console.log("Event" + event.detail) console.log("Event" + event.detail);
saveEdit(event.detail) saveEdit(event.detail);
editId = NaN; editId = NaN;
editName = ''; editName = "";
editLocation = ''; editLocation = "";
editCreated = ''; editCreated = "";
adventures = getAdventures() adventures = getAdventures();
showToast('edited'); showToast("edited");
} }
function editAdventure(event: { detail: number; }) { function editAdventure(event: { detail: number }) {
const adventure = adventures.find(adventure => adventure.id === event.detail); const adventure = adventures.find(
(adventure) => adventure.id === event.detail
);
if (adventure) { if (adventure) {
editId = adventure.id; editId = adventure.id;
editName = adventure.name; editName = adventure.name;
@ -81,40 +94,38 @@
} }
function shareLink() { function shareLink() {
let key = generateRandomString() let key = generateRandomString();
let data = JSON.stringify(adventures) let data = JSON.stringify(adventures);
fetch('/api/share', { fetch("/api/share", {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "Content-Type": "application/json",
}, },
body: JSON.stringify({ key, data }), body: JSON.stringify({ key, data }),
}) })
.then(response => response.json()) .then((response) => response.json())
.then(data => { .then((data) => {
console.log('Success:', data); console.log("Success:", data);
let url = window.location.origin + '/shared/' + key let url = window.location.origin + "/shared/" + key;
navigator.clipboard.writeText(url) navigator.clipboard.writeText(url);
}) })
.catch((error) => { .catch((error) => {
console.error('Error:', error); console.error("Error:", error);
}); });
} }
function handleClose() { function handleClose() {
editId = NaN; editId = NaN;
editName = ''; editName = "";
editLocation = ''; editLocation = "";
editCreated = ''; editCreated = "";
} }
function deleteData() { function deleteData() {
clearAdventures(); clearAdventures();
adventures = getAdventures(); adventures = getAdventures();
showToast('deleted'); showToast("deleted");
} }
</script> </script>
<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">
@ -125,17 +136,27 @@
<div class="flex flex-row items-center justify-center gap-4"> <div class="flex flex-row items-center justify-center gap-4">
<form on:submit={createNewAdventure} class="flex gap-2"> <form on:submit={createNewAdventure} class="flex gap-2">
<input type="text" bind:value={newName} placeholder="Adventure Name" class="input input-bordered w-full max-w-xs" /> <input
<input type="text" bind:value={newLocation} placeholder="Adventure Location" class="input input-bordered w-full max-w-xs" /> type="text"
<input class="btn btn-primary" type="submit" value="Add Adventure"> bind:value={newName}
placeholder="Adventure Name"
class="input input-bordered w-full max-w-xs"
/>
<input
type="text"
bind:value={newLocation}
placeholder="Adventure Location"
class="input input-bordered w-full max-w-xs"
/>
<input class="btn btn-primary" type="submit" value="Add Adventure" />
</form> </form>
</div> </div>
{#if adventures.length != 0} {#if adventures.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 Visited Adventure Locations</h1> <h1 class="text-center">My Visited Adventure Locations</h1>
</article> </article>
</div> </div>
{/if} {/if}
{#if isShowingToast} {#if isShowingToast}
@ -143,25 +164,54 @@
{/if} {/if}
{#if !Number.isNaN(editId)} {#if !Number.isNaN(editId)}
<EditModal bind:editId={editId} bind:editName={editName} bind:editLocation={editLocation} bind:editCreated={editCreated} on:submit={saveAdventure} on:close={handleClose} /> <EditModal
bind:editId
bind:editName
bind:editLocation
bind:editCreated
on:submit={saveAdventure}
on:close={handleClose}
/>
{/if} {/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"> <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 adventures as adventure (adventure.id)} {#each adventures as adventure (adventure.id)}
<AdventureCard id={adventure.id} name={adventure.name} location={adventure.location} created={adventure.created} on:remove={triggerRemoveAdventure} on:edit={editAdventure} /> <AdventureCard
type="mylog"
id={adventure.id}
name={adventure.name}
location={adventure.location}
created={adventure.created}
on:remove={triggerRemoveAdventure}
on:edit={editAdventure}
/>
{/each} {/each}
</div> </div>
{#if adventures.length == 0} {#if adventures.length == 0}
<div class="flex flex-col items-center justify-center mt-16"> <div class="flex flex-col items-center justify-center mt-16">
<article class="prose mb-4"><h2>Add some adventures!</h2></article> <article class="prose mb-4"><h2>Add some adventures!</h2></article>
<img src={mapDrawing} width="25%" alt="Logo" /> <img src={mapDrawing} width="25%" alt="Logo" />
</div> </div>
{/if} {/if}
{#if adventures.length != 0} {#if adventures.length != 0}
<div class="flex flex-row items-center justify-center mt-16 gap-4 mb-4"> <div class="flex justify-center items-center w-full mt-4">
<button class="btn btn-neutral" on:click={async () => { window.location.href = exportData(); }}> <article class="prose">
<h2 class="text-center">Actions</h2>
</article>
</div>
<div
class="flex flex-row items-center justify-center mt-2 gap-4 mb-4 flex-wrap"
>
<button
class="btn btn-neutral"
on:click={async () => {
window.location.href = exportData();
}}
>
<img src={exportFile} class="inline-block -mt-1" alt="Logo" /> Save as File <img src={exportFile} class="inline-block -mt-1" alt="Logo" /> Save as File
</button> </button>
<button class="btn btn-neutral" on:click={deleteData}> <button class="btn btn-neutral" on:click={deleteData}>
@ -170,5 +220,5 @@
<button class="btn btn-neutral" on:click={shareLink}> <button class="btn btn-neutral" on:click={shareLink}>
<img src={deleteIcon} class="inline-block -mt-1" alt="Logo" /> Share as Link <img src={deleteIcon} class="inline-block -mt-1" alt="Logo" /> Share as Link
</button> </button>
</div> </div>
{/if} {/if}

View file

@ -5,10 +5,14 @@ import type { Adventure } from "$lib/utils/types";
export async function load({ params }) { export async function load({ params }) {
let key = params.key; let key = params.key;
let result = await db.select().from(sharedAdventures).where(eq(sharedAdventures.id, key)).execute(); let result = await db
.select()
.from(sharedAdventures)
.where(eq(sharedAdventures.id, key))
.execute();
let adventure = result[0].data as Adventure; let adventure = result[0].data as Adventure;
console.log(adventure); console.log(adventure);
return { return {
result: adventure result: adventure,
}; };
}; }

View file

@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Adventure } from '$lib/utils/types' import type { Adventure } from "$lib/utils/types";
export let data; export let data;
let result = data.result; let result = data.result;
</script> </script>
<p>{result}</p> <p>{result}</p>

View file

@ -1,80 +1,76 @@
import type { Adventure } from '$lib/utils/types'; import type { Adventure } from "$lib/utils/types";
let adventures: Adventure[] = []; let adventures: Adventure[] = [];
import { visitCount } from '$lib/utils/stores/visitCountStore'; import { visitCount } from "$lib/utils/stores/visitCountStore";
// Check if localStorage is available (browser environment) // Check if localStorage is available (browser environment)
const isBrowser = typeof window !== 'undefined'; const isBrowser = typeof window !== "undefined";
// Load adventures from localStorage on startup (only in the browser) // Load adventures from localStorage on startup (only in the browser)
if (isBrowser) { if (isBrowser) {
const storedAdventures = localStorage.getItem('adventures'); const storedAdventures = localStorage.getItem("adventures");
if (storedAdventures) { if (storedAdventures) {
adventures = JSON.parse(storedAdventures); adventures = JSON.parse(storedAdventures);
} }
} }
export function getNextId() { export function getNextId() {
let nextId = Math.max(0, ...adventures.map(adventure => adventure.id)) + 1; let nextId = Math.max(0, ...adventures.map((adventure) => adventure.id)) + 1;
return nextId; return nextId;
} }
export function setAdventures(importArray: Adventure[]) { export function setAdventures(importArray: Adventure[]) {
adventures = importArray adventures = importArray;
} }
export function addAdventure(adventure: Adventure) { export function addAdventure(adventure: Adventure) {
adventures = [...adventures, adventure]; adventures = [...adventures, adventure];
if (isBrowser) { if (isBrowser) {
localStorage.setItem('adventures', JSON.stringify(adventures)); localStorage.setItem("adventures", JSON.stringify(adventures));
visitCount.update((n) => n + 1); visitCount.update((n) => n + 1);
} }
console.log(adventures); console.log(adventures);
} }
export function getAdventures(): Adventure[] { export function getAdventures(): Adventure[] {
return adventures; return adventures;
} }
export function removeAdventure(event: { detail: number; }) { export function removeAdventure(event: { detail: number }) {
adventures = adventures.filter(adventure => adventure.id !== event.detail); adventures = adventures.filter((adventure) => adventure.id !== event.detail);
if (isBrowser) { if (isBrowser) {
localStorage.setItem('adventures', JSON.stringify(adventures)); localStorage.setItem("adventures", JSON.stringify(adventures));
visitCount.update((n) => n - 1); visitCount.update((n) => n - 1);
} }
} }
export function saveEdit(adventure:Adventure) { export function saveEdit(adventure: Adventure) {
let editId = adventure.id; let editId = adventure.id;
console.log("saving edit") console.log("saving edit");
let editName = adventure.name; let editName = adventure.name;
let editLocation = adventure.location; let editLocation = adventure.location;
let editCreated = adventure.created; let editCreated = adventure.created;
let oldAdventure: Adventure | undefined = adventures.find(adventure => adventure.id === editId); let oldAdventure: Adventure | undefined = adventures.find(
console.log("old" + oldAdventure) (adventure) => adventure.id === editId
);
console.log("old" + oldAdventure);
if (oldAdventure) { if (oldAdventure) {
oldAdventure.name = editName; oldAdventure.name = editName;
oldAdventure.location = editLocation; oldAdventure.location = editLocation;
oldAdventure.created = editCreated; oldAdventure.created = editCreated;
} }
editId = NaN; editId = NaN;
console.log("done") console.log("done");
if (isBrowser) { if (isBrowser) {
localStorage.setItem('adventures', JSON.stringify(adventures)); localStorage.setItem("adventures", JSON.stringify(adventures));
} }
} }
export function clearAdventures() { export function clearAdventures() {
adventures = []; adventures = [];
if (isBrowser) { if (isBrowser) {
localStorage.setItem('adventures', JSON.stringify(adventures)); localStorage.setItem("adventures", JSON.stringify(adventures));
visitCount.set(0); visitCount.set(0);
} }
} }

View file

@ -1,11 +1,11 @@
import type { Adventure } from '$lib/utils/types'; import type { Adventure } from "$lib/utils/types";
import { getAdventures } from './adventureService'; import { getAdventures } from "./adventureService";
export function exportData() { export function exportData() {
let adventures: Adventure[] = getAdventures() let adventures: Adventure[] = getAdventures();
let jsonArray = JSON.stringify(adventures) let jsonArray = JSON.stringify(adventures);
console.log(jsonArray) console.log(jsonArray);
let blob = new Blob([jsonArray], {type: "application/json"}); let blob = new Blob([jsonArray], { type: "application/json" });
let url = URL.createObjectURL(blob); let url = URL.createObjectURL(blob);
return url return url;
} }

View file

@ -1,12 +1,11 @@
import type { Adventure } from '$lib/utils/types'; import type { Adventure } from "$lib/utils/types";
import { setAdventures } from './adventureService'; import { setAdventures } from "./adventureService";
export function importData(file:File) { export function importData(file: File) {
let reader = new FileReader(); let reader = new FileReader();
reader.onload = function() { reader.onload = function () {
let importArray: Adventure[] = JSON.parse(reader.result as string); let importArray: Adventure[] = JSON.parse(reader.result as string);
setAdventures(importArray); setAdventures(importArray);
} };
reader.readAsText(file); reader.readAsText(file);
} }

View file

@ -1,9 +1,9 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import adapterNode from '@sveltejs/adapter-node'; import adapterNode from "@sveltejs/adapter-node";
import adapterVercel from '@sveltejs/adapter-vercel'; import adapterVercel from "@sveltejs/adapter-vercel";
let adapter; let adapter;
if (process.env.USING_VERCEL === 'true') { if (process.env.USING_VERCEL === "true") {
adapter = adapterVercel; adapter = adapterVercel;
} else { } else {
adapter = adapterNode; adapter = adapterNode;
@ -13,8 +13,8 @@ if (process.env.USING_VERCEL === 'true') {
const config = { const config = {
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
kit: { kit: {
adapter: adapter() adapter: adapter(),
} },
}; };
export default config; export default config;

View file

@ -1,8 +1,8 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ['./src/**/*.{html,js,svelte,ts}'], content: ["./src/**/*.{html,js,svelte,ts}"],
theme: { theme: {
extend: {} extend: {},
}, },
plugins: [require("@tailwindcss/typography"), require("daisyui")], plugins: [require("@tailwindcss/typography"), require("daisyui")],
daisyui: { daisyui: {

View file

@ -1,6 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from 'vite'; import { defineConfig } from "vite";
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()] plugins: [sveltekit()],
}); });