mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-05 13:15:18 +02:00
commit
0fc9cc9efe
6 changed files with 142 additions and 154 deletions
27
README.md
27
README.md
|
@ -1,11 +1,15 @@
|
||||||
# AdventureLog: Embark, Explore, Remember. 🌍
|
# AdventureLog: Embark, Explore, Remember. 🌍
|
||||||
|
|
||||||
_**⚠️ AdventureLog is in early development and is not recommended for production use until version 1.0!**_
|
_**⚠️ AdventureLog is in early development and is not recommended for production use until version 1.0!**_
|
||||||
### *"Never forget an adventure with AdventureLog - Your ultimate travel companion!"*
|
|
||||||
-----
|
### _"Never forget an adventure with AdventureLog - Your ultimate travel companion!"_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Docker 🐋 (Recomended)
|
### Docker 🐋 (Recomended)
|
||||||
|
|
||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
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
|
||||||
|
@ -14,3 +18,22 @@ _**⚠️ AdventureLog is in early development and is not recommended for produc
|
||||||
|
|
||||||
**Note**: The `ORIGIN` variable is required for CSRF protection. It can be omitted if using a reverse proxy or other HTTPS service.
|
**Note**: The `ORIGIN` variable is required for CSRF protection. It can be omitted if using a reverse proxy or other HTTPS service.
|
||||||
|
|
||||||
|
## About AdventureLog ℹ️
|
||||||
|
|
||||||
|
AdventureLog is a Svelte Kit application that utilizes a PostgreSQL database. Users can log the adventures they have experienced, as well as plan future ones. Key features include:
|
||||||
|
|
||||||
|
- Logging past adventures with fields like name, date, location, description, and rating.
|
||||||
|
- Planning future adventures with similar fields.
|
||||||
|
- Tagging different activity types for better organization.
|
||||||
|
- Viewing countries, regions, and marking visited regions.
|
||||||
|
|
||||||
|
AdventureLog aims to be your ultimate travel companion, helping you document your adventures and plan new ones effortlessly.
|
||||||
|
|
||||||
|
AdventureLog is liscensed under the GNU General Public License v3.0.
|
||||||
|
|
||||||
|
## Roadmap 🛣️
|
||||||
|
|
||||||
|
- Improved mobile device support
|
||||||
|
- Password reset functionality
|
||||||
|
- Improved error handling
|
||||||
|
- Handling of adventure cards with variable width
|
||||||
|
|
|
@ -26,6 +26,10 @@
|
||||||
|
|
||||||
function create() {
|
function create() {
|
||||||
addActivityType();
|
addActivityType();
|
||||||
|
if (newAdventure.name.trim() === "") {
|
||||||
|
alert("Name is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch("create", newAdventure);
|
dispatch("create", newAdventure);
|
||||||
console.log(newAdventure);
|
console.log(newAdventure);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +74,7 @@
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
|
required
|
||||||
bind:value={newAdventure.name}
|
bind:value={newAdventure.name}
|
||||||
class="input input-bordered w-full max-w-xs"
|
class="input input-bordered w-full max-w-xs"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export let appVersion = "Web 0.1.6-alpha";
|
export let appVersion = "Web 0.1.7-alpha";
|
||||||
export let appTitle = "AdventureLog";
|
export let appTitle = "AdventureLog";
|
||||||
export let copyrightYear = "2024"
|
export let copyrightYear = "2024";
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import type { Adventure } from "$lib/utils/types";
|
import type { Adventure } from "$lib/utils/types";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import exportFile from "$lib/assets/exportFile.svg";
|
import exportFile from "$lib/assets/exportFile.svg";
|
||||||
|
import ConfirmModal from "$lib/components/ConfirmModal.svelte";
|
||||||
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";
|
||||||
|
@ -14,6 +15,11 @@
|
||||||
import { generateRandomString } from "$lib";
|
import { generateRandomString } from "$lib";
|
||||||
import { visitCount } from "$lib/utils/stores/visitCountStore";
|
import { visitCount } from "$lib/utils/stores/visitCountStore";
|
||||||
import MoreFieldsInput from "$lib/components/CreateNewAdventure.svelte";
|
import MoreFieldsInput from "$lib/components/CreateNewAdventure.svelte";
|
||||||
|
import {
|
||||||
|
addAdventure,
|
||||||
|
removeAdventure,
|
||||||
|
saveAdventure,
|
||||||
|
} from "../../services/adventureService.js";
|
||||||
|
|
||||||
let isShowingMoreFields = false;
|
let isShowingMoreFields = false;
|
||||||
|
|
||||||
|
@ -21,6 +27,7 @@
|
||||||
|
|
||||||
let isShowingToast: boolean = false;
|
let isShowingToast: boolean = false;
|
||||||
let toastAction: string = "";
|
let toastAction: string = "";
|
||||||
|
let confirmModalOpen: boolean = false;
|
||||||
|
|
||||||
// Sets the adventures array to the data from the server
|
// Sets the adventures array to the data from the server
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -56,72 +63,26 @@
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewAdventure = (event: { detail: Adventure }) => {
|
const createNewAdventure = async (event: { detail: Adventure }) => {
|
||||||
isShowingMoreFields = false;
|
isShowingMoreFields = false;
|
||||||
let detailAdventure = event.detail;
|
let newArray = await addAdventure(event.detail, adventures);
|
||||||
console.log("Event" + event.detail.name);
|
if (newArray.length > 0) {
|
||||||
|
adventures = newArray;
|
||||||
fetch("/api/visits", {
|
|
||||||
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
|
|
||||||
adventures = [...adventures, data.adventure];
|
|
||||||
showToast("Adventure added successfully!");
|
showToast("Adventure added successfully!");
|
||||||
visitCount.update((n) => n + 1);
|
} else {
|
||||||
})
|
showToast("Failed to add adventure");
|
||||||
.catch((error) => {
|
}
|
||||||
console.error("Error:", error);
|
|
||||||
showToast(error.message);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function saveAdventure(event: { detail: Adventure }) {
|
async function save(event: { detail: Adventure }) {
|
||||||
console.log("Event", event.detail);
|
let newArray = await saveAdventure(event.detail, adventures);
|
||||||
let detailAdventure = event.detail;
|
if (newArray.length > 0) {
|
||||||
|
adventures = newArray;
|
||||||
// put request to /api/visits with id and adventure data
|
showToast("Adventure updated successfully!");
|
||||||
fetch("/api/visits", {
|
} else {
|
||||||
method: "PUT",
|
showToast("Failed to update adventure");
|
||||||
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 = adventures.findIndex(
|
|
||||||
(adventure) => adventure.id === detailAdventure.id
|
|
||||||
);
|
|
||||||
if (index !== -1) {
|
|
||||||
adventures[index] = detailAdventure;
|
|
||||||
}
|
}
|
||||||
adventureToEdit = undefined;
|
adventureToEdit = undefined;
|
||||||
showToast("Adventure edited successfully!");
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error:", error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function editAdventure(event: { detail: number }) {
|
function editAdventure(event: { detail: number }) {
|
||||||
|
@ -163,6 +124,7 @@
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
adventureToEdit = undefined;
|
adventureToEdit = undefined;
|
||||||
|
confirmModalOpen = false;
|
||||||
isShowingMoreFields = false;
|
isShowingMoreFields = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,29 +148,20 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeAdventure(event: { detail: number }) {
|
async function remove(event: { detail: number }) {
|
||||||
console.log("Event ID " + event.detail);
|
let initialLength: number = adventures.length;
|
||||||
// send delete request to server at /api/visits
|
let theAdventure = adventures.find(
|
||||||
fetch("/api/visits", {
|
(adventure) => adventure.id === event.detail
|
||||||
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
|
|
||||||
adventures = adventures.filter(
|
|
||||||
(adventure) => adventure.id !== event.detail
|
|
||||||
);
|
);
|
||||||
|
if (theAdventure) {
|
||||||
|
let newArray = await removeAdventure(theAdventure, adventures);
|
||||||
|
if (newArray.length === initialLength - 1) {
|
||||||
|
adventures = newArray;
|
||||||
showToast("Adventure removed successfully!");
|
showToast("Adventure removed successfully!");
|
||||||
visitCount.update((n) => n - 1);
|
} else {
|
||||||
})
|
showToast("Failed to remove adventure");
|
||||||
.catch((error) => {
|
}
|
||||||
console.error("Error:", error);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -254,11 +207,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if adventureToEdit && adventureToEdit.id != undefined}
|
{#if adventureToEdit && adventureToEdit.id != undefined}
|
||||||
<EditModal
|
<EditModal bind:adventureToEdit on:submit={save} on:close={handleClose} />
|
||||||
bind:adventureToEdit
|
|
||||||
on:submit={saveAdventure}
|
|
||||||
on:close={handleClose}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -269,7 +218,7 @@
|
||||||
{adventure}
|
{adventure}
|
||||||
type="mylog"
|
type="mylog"
|
||||||
on:edit={editAdventure}
|
on:edit={editAdventure}
|
||||||
on:remove={removeAdventure}
|
on:remove={remove}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
@ -293,7 +242,7 @@
|
||||||
<button class="btn btn-neutral" on:click={exportData}>
|
<button class="btn btn-neutral" on:click={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={() => (confirmModalOpen = true)}>
|
||||||
<img src={deleteIcon} class="inline-block -mt-1" alt="Logo" /> Delete Data
|
<img src={deleteIcon} class="inline-block -mt-1" alt="Logo" /> Delete Data
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-neutral" on:click={shareLink}>
|
<button class="btn btn-neutral" on:click={shareLink}>
|
||||||
|
@ -303,6 +252,16 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if confirmModalOpen}
|
||||||
|
<ConfirmModal
|
||||||
|
on:close={handleClose}
|
||||||
|
on:confirm={deleteData}
|
||||||
|
title="Delete all Adventures"
|
||||||
|
isWarning={false}
|
||||||
|
message="Are you sure you want to delete all adventures?"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>My Log | AdventureLog</title>
|
<title>My Log | AdventureLog</title>
|
||||||
<meta
|
<meta
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import {
|
import {
|
||||||
saveAdventure,
|
saveAdventure,
|
||||||
removeAdventure,
|
removeAdventure,
|
||||||
|
addAdventure,
|
||||||
} from "../../services/adventureService.js";
|
} from "../../services/adventureService.js";
|
||||||
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";
|
||||||
|
@ -76,40 +77,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createNewAdventure = (event: { detail: Adventure }) => {
|
const createNewAdventure = async (event: { detail: Adventure }) => {
|
||||||
isShowingMoreFields = false;
|
isShowingMoreFields = false;
|
||||||
let detailAdventure = event.detail;
|
let newArray = await addAdventure(event.detail, plans);
|
||||||
console.log("Event" + event.detail.name);
|
if (newArray.length > 0) {
|
||||||
|
plans = newArray;
|
||||||
fetch("/api/planner", {
|
showToast("Adventure added successfully!");
|
||||||
method: "POST",
|
} else {
|
||||||
headers: {
|
showToast("Failed to add adventure");
|
||||||
"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>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import { visitCount } from "$lib/utils/stores/visitCountStore";
|
||||||
import type { Adventure } from "$lib/utils/types";
|
import type { Adventure } from "$lib/utils/types";
|
||||||
|
|
||||||
// json that maps the type to api routes
|
/**
|
||||||
|
* Object containing the API routes for the different types of adventures.
|
||||||
|
*/
|
||||||
const apiRoutes: { [key: string]: string } = {
|
const apiRoutes: { [key: string]: string } = {
|
||||||
planner: "/api/planner",
|
planner: "/api/planner",
|
||||||
mylog: "/api/visits",
|
mylog: "/api/visits",
|
||||||
|
@ -50,6 +53,12 @@ export async function saveAdventure(
|
||||||
return adventureArray;
|
return adventureArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an adventure from the adventure array and sends a delete request to the server.
|
||||||
|
* @param adventure - The adventure to be removed.
|
||||||
|
* @param adventureArray - The array of adventures.
|
||||||
|
* @returns A promise that resolves to the updated adventure array.
|
||||||
|
*/
|
||||||
export async function removeAdventure(
|
export async function removeAdventure(
|
||||||
adventure: Adventure,
|
adventure: Adventure,
|
||||||
adventureArray: Adventure[]
|
adventureArray: Adventure[]
|
||||||
|
@ -81,28 +90,44 @@ export async function removeAdventure(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* function removeAdventure(event: { detail: number }) {
|
* Adds an adventure to the adventure array and sends a POST request to the specified URL.
|
||||||
console.log("Event ID " + event.detail);
|
* @param {Adventure} adventure - The adventure to be added.
|
||||||
// send delete request to server at /api/visits
|
* @param {Adventure[]} adventureArray - The array of adventures.
|
||||||
fetch("/api/visits", {
|
* @returns {Promise<Adventure[]>} - A promise that resolves to the updated adventure array.
|
||||||
method: "DELETE",
|
*/
|
||||||
|
export async function addAdventure(
|
||||||
|
adventure: Adventure,
|
||||||
|
adventureArray: Adventure[]
|
||||||
|
): Promise<Adventure[]> {
|
||||||
|
let url = apiRoutes[adventure.type];
|
||||||
|
let detailAdventure = adventure;
|
||||||
|
// post request to /api/visits with adventure data
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ id: event.detail }),
|
body: JSON.stringify({ detailAdventure }),
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log("Success:", data);
|
adventureArray.push(data.adventure);
|
||||||
// remove adventure from array where id matches
|
if (data.adventure.type === "mylog") {
|
||||||
plans = plans.filter((adventure) => adventure.id !== event.detail);
|
incrementVisitCount(1);
|
||||||
// showToast("Adventure removed successfully!");
|
}
|
||||||
// visitCount.update((n) => n - 1);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error:", error);
|
console.error("Error:", error);
|
||||||
|
return [];
|
||||||
});
|
});
|
||||||
}
|
|
||||||
*
|
return adventureArray;
|
||||||
*
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the visit count by the specified amount.
|
||||||
|
* @param {number} amount - The amount to increment the visit count by.
|
||||||
*/
|
*/
|
||||||
|
export function incrementVisitCount(amount: number) {
|
||||||
|
visitCount.update((n) => n + 1);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue