diff options
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/+layout.svelte | 10 | ||||
| -rw-r--r-- | src/routes/@[user]/+page.svelte | 45 | ||||
| -rw-r--r-- | src/routes/api/badges/add/+server.ts | 26 | ||||
| -rw-r--r-- | src/routes/api/badges/remove/+server.ts | 22 | ||||
| -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.svelte | 64 | ||||
| -rw-r--r-- | src/routes/user/[user]/badges/+page.server.ts | 11 | ||||
| -rw-r--r-- | src/routes/user/[user]/badges/+page.svelte | 125 |
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> |