aboutsummaryrefslogtreecommitdiff
path: root/src/routes
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-10-26 15:41:57 -0700
committerFuwn <[email protected]>2023-10-26 15:41:57 -0700
commit9bac3b6b6a9103841a3456436c65af66549a83da (patch)
tree4273c0ebe5567109daf8b7a4d2c92d51694677cf /src/routes
parentfix(feeds): html encode title (diff)
parentfeat: move back to bun (diff)
downloaddue.moe-9bac3b6b6a9103841a3456436c65af66549a83da.tar.xz
due.moe-9bac3b6b6a9103841a3456436c65af66549a83da.zip
merge: branch 'badges'
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/+layout.svelte10
-rw-r--r--src/routes/@[user]/+page.svelte45
-rw-r--r--src/routes/api/badges/add/+server.ts26
-rw-r--r--src/routes/api/badges/remove/+server.ts22
-rw-r--r--src/routes/user/[user]/+page.server.ts (renamed from src/routes/@[user]/+page.server.ts)0
-rw-r--r--src/routes/user/[user]/+page.svelte64
-rw-r--r--src/routes/user/[user]/badges/+page.server.ts11
-rw-r--r--src/routes/user/[user]/badges/+page.svelte125
8 files changed, 256 insertions, 47 deletions
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 19df3f1c..20ae9ef4 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -52,11 +52,17 @@
<p id="desktop-navigation-bar">
「 <a href="/">Home</a> • <a href="/completed">Completed</a> •
<a href="/updates">Manga & LN Updates</a> •
- <a href="/tools">Tools</a> • <a href="/settings">Settings</a> 」
+ <a href="/tools">Tools</a> • {#if data.user}
+ <a href={`/user/${currentUserIdentity.name}`}>Profile</a> •
+ {/if} <a href="/settings">Settings</a> 」
</p>
<div id="mobile-navigation-bar">
- <a href="/">Home</a> • <a href="/completed">Completed</a> • <a href="/tools">Tools</a><br />
+ <a href="/">Home</a> • <a href="/completed">Completed</a> • <a href="/tools">Tools</a>
+ {#if data.user}
+ • <a href={`/user/${currentUserIdentity.name}`}>Profile</a>
+ {/if}
+ <br />
<a href="/settings">Settings</a>
<a href="/updates">Manga & LN Updates</a>
diff --git a/src/routes/@[user]/+page.svelte b/src/routes/@[user]/+page.svelte
deleted file mode 100644
index 1763193a..00000000
--- a/src/routes/@[user]/+page.svelte
+++ /dev/null
@@ -1,45 +0,0 @@
-<script lang="ts">
- import { user, type User } from '$lib/AniList/user';
- import { onMount } from 'svelte';
-
- export let data;
-
- let userData: User | undefined = undefined;
-
- onMount(() => {
- user(data.username).then((profile) => {
- userData = profile;
- });
- });
-
- // 8.5827814569536423841e0
-</script>
-
-{#if userData === null}
- Could not load user profile for <a
- href={`https://anilist.co/user/${data.username}`}
- target="_blank">@{data.username}</a
- >.
-
- <p />
-
- Does this user exist?
-{:else if userData === undefined}
- Loading ...
-{:else}
- <a href={`https://anilist.co/user/${userData.name}`} target="_blank" title={String(userData.id)}
- >@{userData.name}</a
- >
-
- <p />
-
- This user has watched {(userData.statistics.anime.minutesWatched / 60 / 24).toFixed(1)} days of anime
- and read
- {((userData.statistics.manga.chaptersRead * 8.58) / 60 / 24).toFixed(1)} days of manga.
-{/if}
-
-<p />
-
-<hr />
-
-This page is under construction!
diff --git a/src/routes/api/badges/add/+server.ts b/src/routes/api/badges/add/+server.ts
new file mode 100644
index 00000000..627176a7
--- /dev/null
+++ b/src/routes/api/badges/add/+server.ts
@@ -0,0 +1,26 @@
+import { userIdentity } from '$lib/AniList/identity.js';
+import { addUserBadge } from '$lib/userBadgesDatabase.js';
+
+export const POST = async ({ cookies, url }) => {
+ const userCookie = cookies.get('user');
+
+ if (!userCookie) {
+ return new Response('Unauthenticated', { status: 401 });
+ }
+
+ const user = JSON.parse(userCookie);
+ const identity = await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ });
+
+ addUserBadge(identity.id, {
+ post: url.searchParams.get('post') || undefined,
+ image: url.searchParams.get('image') || undefined,
+ description: url.searchParams.get('description') || undefined
+ });
+
+ return Response.json({});
+};
diff --git a/src/routes/api/badges/remove/+server.ts b/src/routes/api/badges/remove/+server.ts
new file mode 100644
index 00000000..8b05369a
--- /dev/null
+++ b/src/routes/api/badges/remove/+server.ts
@@ -0,0 +1,22 @@
+import { userIdentity } from '$lib/AniList/identity.js';
+import { removeUserBadge } from '$lib/userBadgesDatabase.js';
+
+export const POST = async ({ url, cookies }) => {
+ const userCookie = cookies.get('user');
+
+ if (!userCookie) {
+ return new Response('Unauthenticated', { status: 401 });
+ }
+
+ const user = JSON.parse(userCookie);
+ const identity = await userIdentity({
+ tokenType: user['token_type'],
+ expiresIn: user['expires_in'],
+ accessToken: user['access_token'],
+ refreshToken: user['refresh_token']
+ });
+
+ removeUserBadge(identity.id, Number(url.searchParams.get('id')));
+
+ return Response.json({});
+};
diff --git a/src/routes/@[user]/+page.server.ts b/src/routes/user/[user]/+page.server.ts
index 76d2d889..76d2d889 100644
--- a/src/routes/@[user]/+page.server.ts
+++ b/src/routes/user/[user]/+page.server.ts
diff --git a/src/routes/user/[user]/+page.svelte b/src/routes/user/[user]/+page.svelte
new file mode 100644
index 00000000..858e6aec
--- /dev/null
+++ b/src/routes/user/[user]/+page.svelte
@@ -0,0 +1,64 @@
+<script lang="ts">
+ import { user, type User } from '$lib/AniList/user';
+ import { onMount } from 'svelte';
+
+ export let data;
+
+ let userData: User | undefined = undefined;
+
+ onMount(() => {
+ user(data.username).then((profile) => {
+ userData = profile;
+ });
+ });
+
+ // 8.5827814569536423841e0
+</script>
+
+{#if userData === null}
+ Could not load user profile for <a
+ href={`https://anilist.co/user/${data.username}`}
+ target="_blank">@{data.username}</a
+ >.
+
+ <p />
+
+ Does this user exist?
+{:else if userData === undefined}
+ Loading ...
+{:else}
+ <div class="user-grid">
+ <p>
+ <a
+ href={`https://anilist.co/user/${userData.name}`}
+ target="_blank"
+ title={String(userData.id)}
+ >
+ <img src={userData.avatar.large} alt="" width="100vw" />
+ </a>
+ </p>
+
+ <div>
+ <p>
+ <a
+ href={`https://anilist.co/user/${userData.name}`}
+ target="_blank"
+ title={String(userData.id)}>@{userData.name}</a
+ >
+ • <a href={`/user/${userData.name}/badges`}>Badge Wall</a>
+ </p>
+
+ This user has watched {(userData.statistics.anime.minutesWatched / 60 / 24).toFixed(1)} days of
+ anime and read
+ {((userData.statistics.manga.chaptersRead * 8.58) / 60 / 24).toFixed(1)} days of manga.
+ </div>
+ </div>
+{/if}
+
+<style>
+ .user-grid {
+ display: flex;
+ flex-wrap: wrap;
+ column-gap: 1.5em;
+ }
+</style>
diff --git a/src/routes/user/[user]/badges/+page.server.ts b/src/routes/user/[user]/badges/+page.server.ts
new file mode 100644
index 00000000..4be5bcd2
--- /dev/null
+++ b/src/routes/user/[user]/badges/+page.server.ts
@@ -0,0 +1,11 @@
+import { user } from '$lib/AniList/user.js';
+import { getUserBadges } from '$lib/userBadgesDatabase.js';
+
+export const load = async ({ params }) => {
+ const badges = getUserBadges((await user(params.user)).id);
+
+ return {
+ username: params.user,
+ badges
+ };
+};
diff --git a/src/routes/user/[user]/badges/+page.svelte b/src/routes/user/[user]/badges/+page.svelte
new file mode 100644
index 00000000..324d568e
--- /dev/null
+++ b/src/routes/user/[user]/badges/+page.svelte
@@ -0,0 +1,125 @@
+<script lang="ts">
+ import { userIdentity } from '$lib/AniList/identity.js';
+ import type { Badge } from '$lib/userBadgesDatabase.js';
+ import { onMount } from 'svelte';
+
+ export let data;
+
+ let editMode = false;
+ let currentUserIdentity: ReturnType<typeof userIdentity>;
+
+ onMount(async () => {
+ if (data.user) {
+ currentUserIdentity = userIdentity(data.user);
+ } else {
+ currentUserIdentity = new Promise((resolve) =>
+ resolve({
+ name: 'Guest',
+ id: -1
+ })
+ );
+ }
+ });
+
+ const submitBadge = () => {
+ const imageURL = document.querySelector('input[name="image_url"]') as HTMLInputElement;
+ const activityURL = document.querySelector('input[name="activity_url"]') as HTMLInputElement;
+ const description = document.querySelector('input[name="description"]') as HTMLInputElement;
+
+ fetch(
+ `/api/badges/add?image=${encodeURIComponent(imageURL.value)}&post=${encodeURIComponent(
+ activityURL.value
+ )}&description=${encodeURIComponent(description.value)}`,
+ {
+ method: 'POST'
+ }
+ ).then(() => {
+ imageURL.value = '';
+ activityURL.value = '';
+ description.value = '';
+ });
+ };
+
+ const removeBadge = (badge: Badge) => {
+ fetch(`/api/badges/remove?id=${badge.id}`, {
+ method: 'POST'
+ }).then(() => {
+ (document.querySelector(`#badge-${badge.id}`) as HTMLAnchorElement).style.display = 'none';
+ });
+ };
+</script>
+
+{#await currentUserIdentity}
+ Loading ...
+{:then identity}
+ {@const isOwner = identity && identity.name === data.username}
+ <p>
+ <a href={`/user/${data.username}`}>Back to Profile</a>
+ {#if isOwner}
+ •
+ <a href={`#`} on:click={() => (editMode = !editMode)}>
+ {editMode ? 'Disable' : 'Enable'} Edit Mode
+ </a>
+ {/if}
+ </p>
+
+ {#if editMode && isOwner}
+ <p>
+ Delete mode is enabled. Click on an image to delete it. There is no confirmation, so be
+ careful!
+ </p>
+
+ <p>
+ <input type="text" placeholder="Image URL" name="image_url" minlength="1" maxlength="1000" />
+ <input
+ type="text"
+ placeholder="Activity URL"
+ name="activity_url"
+ minlength="1"
+ maxlength="1000"
+ />
+ <input
+ type="text"
+ placeholder="Description (Optional)"
+ name="description"
+ minlength="1"
+ maxlength="1000"
+ />
+ <a href={`#`} on:click={submitBadge}>Add Badge</a>
+ </p>
+ {/if}
+
+ <div id="badges">
+ {#each data.badges as badge}
+ {#if editMode}
+ <a href={`#`} on:click={() => removeBadge(badge)} id={`badge-${badge.id}`}>
+ <img src={badge.image} alt={badge.description} />
+ </a>
+ {:else}
+ <a href={badge.post} target="_blank" id={`badge-${badge.id}`}>
+ <img src={badge.image} alt={badge.description} />
+ </a>
+ {/if}
+ {/each}
+ </div>
+{/await}
+
+<style>
+ /* body {
+ margin: 0;
+ padding: 0;
+ text-align: center;
+ background-color: #151f2e;
+ } */
+
+ img {
+ width: 100%;
+ height: auto;
+ }
+
+ #badges {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(8%, 1fr));
+ grid-gap: 0;
+ }
+</style>