[READ-ONLY] Mirror of https://github.com/flo-bit/skywatched. review movies and tv shows, based on at proto skywatched.app
0

Configure Feed

Select the types of activity you want to include in your feed.

added follows personalfeed and logout button

gree45 (Feb 7, 2025, 3:49 PM +0100) 4787b59e ee065460

+420 -122
+8
src/lib/Components/Sidebar.svelte
··· 22 22 label: 'Feed', 23 23 href: '/feed' 24 24 }, 25 + 25 26 { 26 27 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> 27 28 <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /> ··· 42 43 showSidebar.value = false; 43 44 rateMovieModal.showEmpty(); 44 45 } 46 + }, 47 + { 48 + icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> 49 + <path stroke-linecap="round" stroke-linejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 0 1-1.161.886l-.143.048a1.107 1.107 0 0 0-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 0 1-1.652.928l-.679-.906a1.125 1.125 0 0 0-1.906.172L4.5 15.75l-.612.153M12.75 3.031a9 9 0 0 0-8.862 12.872M12.75 3.031a9 9 0 0 1 6.69 14.036m0 0-.177-.529A2.25 2.25 0 0 0 17.128 15H16.5l-.324-.324a1.453 1.453 0 0 0-2.328.377l-.036.073a1.586 1.586 0 0 1-.982.816l-.99.282c-.55.157-.894.702-.8 1.267l.073.438c.08.474.49.821.97.821.846 0 1.598.542 1.865 1.345l.215.643m5.276-3.67a9.012 9.012 0 0 1-5.276 3.67m0 0a9 9 0 0 1-10.275-4.835M15.75 9c0 .896-.393 1.7-1.016 2.25" /> 50 + </svg>`, 51 + label: 'PersonalFeed', 52 + href: '/personalfeed' 45 53 }, 46 54 { 47 55 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
+8
src/lib/bluesky.ts
··· 27 27 const { data } = await agent.app.bsky.actor.getProfile({ actor: did }); 28 28 return data; 29 29 } 30 + export async function getFollows({ did, agent = undefined }: { did: string; agent?: AgentType }) { 31 + if (!agent) { 32 + agent = new AtpBaseClient({ service: 'https://api.bsky.app' }); 33 + } 34 + 35 + const { data } = await agent.app.bsky.graph.getFollows({ actor: did }); 36 + return data; 37 + } 30 38 export async function getAllRated({ did, agent = undefined }: { did: string; agent?: AgentType }) { 31 39 if (!agent) { 32 40 agent = new AtpBaseClient({ service: 'https://bsky.social' });
+5
src/lib/db.ts
··· 81 81 const data = await response.json(); 82 82 return data; 83 83 } 84 + export async function getAuthorDids(): Promise<string[]> { 85 + const response = await fetch(`${env.BACKEND_URL}/api/author-dids`); 86 + const data = await response.json(); 87 + return data; 88 + }
+118 -37
src/routes/game/[kind]/[id]/[kind2]/[id2]/[counter]/+page.server.ts
··· 8 8 getPersonDetails, 9 9 getCombinedCredits 10 10 } from '$lib/server/movies'; 11 - import { error } from '@sveltejs/kit'; 11 + import { error, redirect } from '@sveltejs/kit'; 12 12 13 13 /** @type {import('./$types').PageServerLoad} */ 14 14 export async function load(event) { ··· 16 16 const id2 = parseInt(event.params.id2.split('-')[0]); 17 17 const kind = event.params.kind; 18 18 const kind2= event.params.kind2; 19 - const couter=0; 20 - 21 - if (kind !== 'movie' && kind !== 'tv') { 19 + const ids=id; 20 + const ids2=id2; 21 + const couter=event.params.counter; 22 + if(kind==kind2&&id==id2){ 23 + redirect(302,"/win/"+couter); 24 + } 25 + 26 + if (kind !== 'movie' && kind !== 'tv' && kind!=='cast') { 27 + return error(404, 'Not found'); 28 + } 29 + if (kind2 !== 'movie' && kind2 !== 'tv' && kind2!=='cast') { 22 30 return error(404, 'Not found'); 23 31 } 24 32 25 33 if (!id) { 26 34 return error(404, 'Not found'); 27 35 } 28 - const resultPromise = getDetails(id, kind); 29 - const resultPromise2 = getDetails(id2, kind2); 36 + if (!id2) { 37 + return error(404, 'Not found'); 38 + } 39 + var temp; 40 + var temp2; 41 + var casting; 42 + var casting2; 43 + var person; 44 + var person2; 45 + var credits; 46 + var credits2; 47 + if(kind==="cast"){ 48 + temp = getDetails(68730,"movie"); 49 + casting=getCast(68730,"movie"); 50 + person=getPersonDetails(id); 51 + person2=getPersonDetails(id2); 52 + credits=id; 53 + credits2=id2; 54 + } 55 + else{ 56 + temp = getDetails(id, kind); 57 + casting=getCast(id,kind); 58 + person=getPersonDetails(37625); 59 + person2=getPersonDetails(37625); 60 + credits=37625; 61 + credits2=37625; 30 62 31 - const trailerPromise = getTrailer(id, kind); 63 + } 64 + if(kind2==="cast"){ 65 + temp2 = getDetails(68730,"movie"); 66 + casting2=getCast(68730,"movie"); 67 + person2=getPersonDetails(id2); 68 + credits2=id2; 69 + } 70 + else{ 71 + temp2 = getDetails(id2, kind2); 72 + casting2=getCast(id2,kind2); 73 + person2=getPersonDetails(37625); 74 + credits2=37625; 32 75 33 - const recommendationsPromise = getRecommendations(id, kind); 76 + } 77 + const resultPromise = temp; 78 + const personPromise=person; 79 + const person2Promise=person2; 80 + //if(kind==="cast"){ 81 + // const resultPromise = getDetails(68730,"movie") 82 + //} 83 + //else{ 84 + // const resultPromise = getDetails(id, kind); 85 + //} 86 + const resultPromise2 = temp2; 34 87 35 - const watchProvidersPromise = getWatchProviders(id, kind); 88 + const castPromise = casting; 89 + const castPromise2 = casting2; 90 + const creditsPromise= credits; 91 + const creditsPromise2= credits2; 92 + const combinedCredits = await getCombinedCredits(credits); 93 + const creditsSet = new Set<string>(); 94 + const combinedCredits2 = await getCombinedCredits(credits2); 95 + const creditsSet2 = new Set<string>(); 96 + combinedCredits.cast = combinedCredits.cast 97 + .filter((item: { id: string; poster_path: string }) => { 98 + if (creditsSet.has(item.id)) { 99 + return false; 100 + } 101 + creditsSet.add(item.id); 102 + return item.poster_path; 103 + }) 104 + .sort((a: { order: number }, b: { order: number }) => b.order - a.order) 105 + .map((item: { id: string; media_type: string }) => { 106 + return { 107 + ...item, 108 + movieId: item.media_type === 'movie' ? item.id : undefined, 109 + showId: item.media_type === 'tv' ? item.id : undefined 110 + }; 111 + }); 112 + combinedCredits2.cast = combinedCredits2.cast 113 + .filter((item: { id: string; poster_path: string }) => { 114 + if (creditsSet2.has(item.id)) { 115 + return false; 116 + } 117 + creditsSet2.add(item.id); 118 + return item.poster_path; 119 + }) 120 + .sort((a: { order: number }, b: { order: number }) => b.order - a.order) 121 + .map((item: { id: string; media_type: string }) => { 122 + return { 123 + ...item, 124 + movieId: item.media_type === 'movie' ? item.id : undefined, 125 + showId: item.media_type === 'tv' ? item.id : undefined 126 + }; 127 + }); 36 128 37 - const ratingsPromise = getRecentRecordsForItem({ 38 - ref: 'tmdb:' + (kind === 'movie' ? 'm' : 's'), 39 - value: id.toString() 40 - }); 41 - 42 - const castPromise = getCast(id, kind); 43 - const castPromise2 = getCast(id2, kind2); 44 - 45 - const [result,result2, trailer, recommendations, watchProviders, ratings, cast,cast2] = await Promise.all([ 129 + const [result,result2, cast,cast2,personDetails,person2Details,creditDetails,creditDetails2] = await Promise.all([ 46 130 resultPromise, 47 131 resultPromise2, 48 - trailerPromise, 49 - recommendationsPromise, 50 - watchProvidersPromise, 51 - ratingsPromise, 52 132 castPromise, 53 - castPromise2 133 + castPromise2, 134 + personPromise, 135 + person2Promise, 136 + creditsPromise, 137 + creditsPromise2 54 138 ]); 55 139 56 140 if (!result || result.success === false) { ··· 65 149 }, 66 150 result2: { 67 151 ...result2, 68 - movieId: kind === 'movie' ? id : undefined, 69 - showId: kind === 'tv' ? id : undefined 152 + movieId: kind2 === 'movie' ? id2 : undefined, 153 + showId: kind2 === 'tv' ? id2 : undefined 70 154 }, 71 - trailer, 72 - // @ts-expect-error - TODO: fix this 73 - recommendations: recommendations.map((item) => { 74 - if (kind === 'movie') { 75 - return { ...item, movieId: item.id }; 76 - } else { 77 - return { ...item, showId: item.id }; 78 - } 79 - }), 80 155 kind, 81 156 kind2, 82 - watchProviders, 83 - ratings, 84 157 cast, 85 158 cast2, 86 - couter 159 + couter, 160 + personDetails, 161 + person2Details, 162 + creditDetails, 163 + creditDetails2, 164 + ids, 165 + ids2, 166 + combinedCredits: combinedCredits.cast, 167 + combinedCredits2: combinedCredits2.cast 87 168 }; 88 169 }
+65 -71
src/routes/game/[kind]/[id]/[kind2]/[id2]/[counter]/+page.svelte
··· 4 4 import { rateMovieModal, videoPlayer, watchedItems } from '$lib/state.svelte'; 5 5 6 6 import Container from '$lib/Components/Container.svelte'; 7 - import ItemsList from '$lib/Components/ItemsList.svelte'; 8 - import ReviewList from '$lib/Components/ReviewList.svelte'; 9 7 import Avatar from '$lib/Components/Avatar.svelte'; 10 8 import { page } from '$app/stores'; 11 9 ··· 72 70 {/if} 73 71 {/snippet} 74 72 75 - <img 76 - src="https://image.tmdb.org/t/p/w780{data.result.backdrop_path}" 77 - alt="" 78 - class="fixed h-full w-full object-cover object-center opacity-20" 79 - /> 73 + 80 74 <div class="fixed inset-0 h-full w-full bg-black/50"></div> 81 75 82 76 <Container class="relative z-10 pb-8 pt-4"> 83 77 <div class="flex gap-4 px-4 pt-8"> 78 + 79 + {#if data.kind === "cast"} 80 + <Avatar 81 + src={data.personDetails.profile_path 82 + ? 'https://image.tmdb.org/t/p/w500' + data.personDetails.profile_path 83 + : undefined} 84 + size="size-44" 85 + /> 86 + {:else} 84 87 <img 85 88 src="https://image.tmdb.org/t/p/w500{data.result.poster_path}" 86 89 alt="" 87 90 class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44" 88 91 style:--name={`poster-${data.result.id}`} 89 92 /> 90 - 93 + {/if} 94 + {#if data.kind2 === "cast"} 95 + <Avatar 96 + src={data.person2Details.profile_path 97 + ? 'https://image.tmdb.org/t/p/w500' + data.person2Details.profile_path 98 + : undefined} 99 + size="size-44" 100 + /> 101 + {:else} 91 102 <img 92 103 src="https://image.tmdb.org/t/p/w500{data.result2.poster_path}" 93 104 alt="" 94 105 class="poster h-36 w-24 shrink-0 rounded-lg border border-white/10 sm:h-64 sm:w-44" 95 106 style:--name={`poster-${data.result2.id}`} 96 107 /> 97 - <div class="flex flex-col gap-4"> 98 - <div 99 - class="title max-w-xl text-3xl font-semibold text-white sm:text-4xl" 100 - style:--name={`title-${data.result.id}`} 101 - > 102 - {data.result.title ?? data.result.name} 103 - </div> 104 - 105 - {#if data.settings?.streaming_region?.code && data.watchProviders[data.settings.streaming_region.code]?.flatrate} 106 - <div class="mt-2 text-sm text-white sm:mt-4"> 107 - <div class="mb-2 flex flex-wrap gap-4 text-xs font-medium"> 108 - stream on 109 - <span class="flex items-center gap-2 text-base-400" 110 - >from <a 111 - href="https://www.justwatch.com" 112 - target="_blank" 113 - class="text-base-300 hover:text-accent-300" 114 - > 115 - <img src="/justwatch_logo.svg" alt="justwatch" class="h-3" /> 116 - </a></span 117 - > 118 - </div> 119 - <a 120 - href={data.watchProviders[data.settings.streaming_region.code].link} 121 - target="_blank" 122 - class="flex flex-wrap gap-2" 123 - > 124 - {#each data.watchProviders[data.settings.streaming_region.code].flatrate as provider} 125 - <img 126 - src="https://image.tmdb.org/t/p/w500{provider.logo_path}" 127 - alt={provider.provider_name} 128 - class="size-8 rounded-md border border-base-800 md:size-12" 129 - /> 130 - {/each} 131 - </a> 132 - </div> 133 - {/if} 134 - <div class="hidden gap-2 sm:flex"> 135 - {@render buttons()} 136 - </div> 137 - </div> 108 + {/if} 109 + 138 110 </div> 139 111 140 112 <div class="px-4 pt-4 text-sm text-white"> 141 113 <div class="mb-4 flex gap-2 sm:hidden"> 142 114 {@render buttons()} 143 115 </div> 144 - <div class="mb-4 max-w-2xl text-sm text-white"> 145 - <div class="mb-2 text-lg font-semibold">overview</div> 146 - {data.result.overview} 147 - </div> 148 116 </div> 149 - 150 - {#if data.ratings.length > 0} 151 - <div class="mt-8 px-4 text-lg font-semibold">reviews</div> 152 - 153 - <ReviewList reviews={data.ratings} showMovieDetails={false} class="" /> 154 - {/if} 155 - 156 - {#if data.recommendations.length > 0} 157 - <div class="px-4 pt-4 text-sm text-white"> 158 - <div class="mb-2 text-lg font-semibold">recommendations</div> 159 - 160 - <ItemsList items={data.recommendations} showMark={!!data.user} /> 161 - </div> 162 - {/if} 163 - 117 + 164 118 {#if data.cast.length > 0} 165 119 <div class="flex flex-col gap-x-6 px-4 pb-8 pt-4 text-sm text-white"> 166 120 <!--<div class="mb-2 text-lg font-semibold">cast</div>*/--> 167 - 121 + 122 + <!--{#if data.kind === "cast"}--> 123 + <!--{/if}--> 124 + {#if data.kind==="cast" } 125 + <div class={cn('flex gap-x-6 overflow-x-auto')}> 126 + {#each data.combinedCredits as castMember} 127 + <a 128 + href={`/game/movie/${castMember.id}-${castMember.title ?? castMember.name}/${data.kind2}/${data.ids2}-/${parseInt(data.couter)+1}`} 129 + class="flex flex-col items-center gap-1" 130 + > 131 + <img 132 + src="https://image.tmdb.org/t/p/w342{castMember.poster_path}" 133 + alt="movie poster for {castMember.title ?? castMember.name}" 134 + class="poster size-32 object-center object-cover lg:size-full w-32" 135 + /> 136 + <div class="text-center size-32 text-sm font-medium">{castMember.title ?? castMember.name}</div> 137 + </a> 138 + 139 + {/each} 140 + </div> 141 + {:else} 168 142 <div class={'flex gap-x-6 overflow-x-auto'}> 169 143 {#each data.cast as castMember} 170 144 <a 171 - href={`/cast/${castMember.id}-${nameToId(castMember.name)}`} 145 + href={`/game/cast/${castMember.id}-${nameToId(castMember.name)}/${data.kind2}/${data.ids2}-/${parseInt(data.couter)+1}`} 172 146 class="flex flex-col items-center gap-1" 173 147 > 174 148 <Avatar ··· 182 156 </a> 183 157 {/each} 184 158 </div> 159 + {/if} 160 + {#if data.kind2==="cast" } 161 + <div class={cn('flex gap-x-6 overflow-x-auto')}> 162 + {#each data.combinedCredits2 as castMember} 163 + <a 164 + href={`/game/${data.kind}/${data.ids}-/movie/${castMember.id}-${castMember.title ?? castMember.name}/${parseInt(data.couter)+1}`} 165 + class="flex flex-col items-center gap-1" 166 + > 167 + <img 168 + src="https://image.tmdb.org/t/p/w342{castMember.poster_path}" 169 + alt="movie poster for {castMember.title ?? castMember.name}" 170 + class="poster size-32 object-center object-cover lg:size-full w-32" 171 + /> 172 + <div class="text-center size-32 text-sm font-medium">{castMember.title ?? castMember.name}</div> 173 + </a> 174 + 175 + {/each} 176 + </div> 177 + {:else} 185 178 <div class={'flex gap-x-6 overflow-x-auto'}> 186 179 {#each data.cast2 as castMember} 187 180 <a 188 - href={`/cast/${castMember.id}-${nameToId(castMember.name)}`} 181 + href={`/game/${data.kind}/${data.ids}-/cast/${castMember.id}-${castMember.title ?? castMember.name}/${parseInt(data.couter)+1}`} 189 182 class="flex flex-col items-center gap-1" 190 183 > 191 184 <Avatar ··· 199 192 </a> 200 193 {/each} 201 194 </div> 195 + {/if} 202 196 </div> 203 197 {/if} 204 198 </Container>
-6
src/routes/login/+page.svelte
··· 50 50 class="flex w-full justify-center rounded-xl bg-accent-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-accent-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500" 51 51 >login</button 52 52 > 53 - <button 54 - formaction="?/register" 55 - type="submit" 56 - class="mt-4 flex w-full justify-center rounded-xl bg-white/10 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-white/20 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500" 57 - >register</button 58 - > 59 53 </div> 60 54 </form> 61 55 </div>
+23
src/routes/logout/+page.server.ts
··· 1 + import { redirect } from '@sveltejs/kit'; 2 + 3 + import type { Actions, PageServerLoad } from './$types'; 4 + import { register } from '$lib/Components/relative-time'; 5 + 6 + export const load: PageServerLoad = async (event) => { 7 + if (event.cookies.get('sid')) { 8 + const sid = event.cookies.get('sid'); 9 + 10 + 11 + } 12 + return {}; 13 + }; 14 + 15 + export const actions: Actions = { 16 + login: async (event) => 17 + {event.cookies.delete('sid', { path: '/' }); 18 + return redirect(302, '/');}, 19 + register: async (event) => 20 + {event.cookies.delete('sid', { path: '/' }); 21 + return redirect(302, '/');} 22 + 23 + };
+26
src/routes/logout/+page.svelte
··· 1 + <script lang="ts"> 2 + import type { ActionData } from './$types'; 3 + import Logo from '$lib/Components/Logo.svelte'; 4 + 5 + let { form }: { form: ActionData } = $props(); 6 + </script> 7 + 8 + <div class="flex min-h-screen flex-col items-center justify-center px-6 py-12 lg:px-8"> 9 + <div class="sm:mx-auto sm:w-full sm:max-w-sm"> 10 + <Logo class="mx-auto size-10" /> 11 + <h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-white">Are you sure you want to logout</h2> 12 + </div> 13 + 14 + <div class="mt-10 w-full sm:mx-auto sm:max-w-sm"> 15 + <form class="space-y-6" action="?/login" method="POST"> 16 + 17 + <div> 18 + <button 19 + type="submit" 20 + class="flex w-full justify-center rounded-xl bg-accent-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-accent-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500" 21 + >logout</button 22 + > 23 + </div> 24 + </form> 25 + </div> 26 + </div>
+18
src/routes/personalfeed/+page.server.ts
··· 1 + import { getProfile, resolveHandle,getFollows } from '$lib/bluesky.js'; 2 + import {getRecentRecords,getRecentRecordOfUser,getAuthorDids } from '$lib/db'; 3 + import { settings, user } from '$lib/state.svelte'; 4 + 5 + /** @type {import('./$types').PageServerLoad} */ 6 + export async function load(event) { 7 + const feed = await getRecentRecords(); 8 + const did = event.locals.user.did; 9 + var filt=feed; 10 + const followsPromise = getFollows({ did }); 11 + const authors=await getAuthorDids(); 12 + const [follows] = await Promise.all([followsPromise]); 13 + filt=feed.filter((review)=>(follows.follows.some((person)=>(person.did==review.author.did)))); 14 + console.log(filt); 15 + 16 + const truefeed=filt; 17 + return { truefeed }; 18 + }
+27
src/routes/personalfeed/+page.svelte
··· 1 + <script lang="ts"> 2 + import Container from '$lib/Components/Container.svelte'; 3 + import { onMount } from 'svelte'; 4 + import { type PageData } from './$types'; 5 + import ReviewList from '$lib/Components/ReviewList.svelte'; 6 + import BaseHeadTags from '$lib/Components/BaseHeadTags.svelte'; 7 + 8 + let { data }: { data: PageData } = $props(); 9 + </script> 10 + 11 + <svelte:head> 12 + <title>feed | skywatched</title> 13 + </svelte:head> 14 + 15 + <BaseHeadTags /> 16 + 17 + <Container class="relative z-10 w-full py-8 sm:py-16"> 18 + <div class="mx-auto w-full max-w-2xl"> 19 + <div class="flex flex-col gap-4 px-4"> 20 + <h1 class="pb-4 text-2xl font-bold">Recent reviews</h1> 21 + </div> 22 + 23 + {#if data.truefeed.length > 0} 24 + <ReviewList reviews={data.truefeed} /> 25 + {/if} 26 + </div> 27 + </Container>
+16 -5
src/routes/user/[handle]/+page.server.ts
··· 1 - import { getProfile, resolveHandle } from '$lib/bluesky.js'; 2 - import { getRecentRecordOfUser } from '$lib/db'; 1 + import { getProfile, resolveHandle,getFollows } from '$lib/bluesky.js'; 2 + import { getRecentRecordOfUser,getAuthorDids } from '$lib/db'; 3 3 4 4 /** @type {import('./$types').PageServerLoad} */ 5 5 export async function load(event) { ··· 9 9 10 10 const profilePromise = getProfile({ did }); 11 11 const itemsPromise = getRecentRecordOfUser({ did }); 12 - 13 - const [profile, items] = await Promise.all([profilePromise, itemsPromise]); 12 + const followsPromise = getFollows({ did }); 13 + const authors=await getAuthorDids(); 14 + const [profile, items,follows] = await Promise.all([profilePromise, itemsPromise,followsPromise]); 15 + var i=follows.follows.length-1; 16 + while(i>=0){ 17 + if(authors.includes(follows.follows[i].did)){ 18 + } 19 + else{ 20 + follows.follows.splice(i,1); 21 + } 22 + i--; 23 + 24 + } 14 25 15 - return { isUser: event.locals.user?.did === did, username: event.params.handle, profile, items }; 26 + return { isUser: event.locals.user?.did === did, username: event.params.handle, profile, items,follows }; 16 27 }
+36 -3
src/routes/user/[handle]/+page.svelte
··· 1 1 <script lang="ts"> 2 + import { getFollows } from '$lib/bluesky.js'; 3 + import Avatar from '$lib/Components/Avatar.svelte'; 2 4 import BaseHeadTags from '$lib/Components/BaseHeadTags.svelte'; 3 5 import Container from '$lib/Components/Container.svelte'; 4 6 import Profile from '$lib/Components/Profile.svelte'; ··· 14 16 <BaseHeadTags /> 15 17 16 18 <Container class="max-w-full lg:max-w-full"> 17 - <Profile profile={data.profile} /> 19 + 20 + <Profile profile={data.profile} /> 21 + {#if data.isUser} 22 + <a href={"/logout/"} class="button flex w-full justify-center rounded-xl bg-accent-500 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-accent-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-500">logout</a> 23 + 24 + {/if} 25 + 26 + {#if data.items.length > 0} 27 + <div class={'flex gap-x-6 overflow-x-auto mx-auto mt-8 max-w-2xl'}> 28 + 29 + {#each data.follows.follows as castMember} 30 + {#if castMember.did} 31 + <a 32 + href={`/user/`+castMember.handle} 33 + class="flex flex-col items-center gap-1" 34 + > 35 + <Avatar 36 + src={castMember.avatar 37 + ? castMember.avatar 38 + : undefined} 39 + size="size-32" 40 + /> 41 + <div class="text-center text-xs font-medium">{castMember.displayName}</div> 42 + <div class="text-center text-xs text-base-400">{castMember.handle}</div> 43 + </a> 44 + {/if} 45 + 46 + {/each} 47 + </div> 48 + <div><ReviewList reviews={data.items} class="mx-auto mt-8 max-w-2xl" /></div> 49 + 18 50 19 - {#if data.items.length > 0} 20 - <ReviewList reviews={data.items} class="mx-auto mt-8 max-w-2xl" /> 51 + 21 52 {:else} 22 53 <p class="py-8 text-center text-base-500">No movies or tv shows rated yet.</p> 23 54 {/if} 24 55 </Container> 56 + 57 +
+14
src/routes/win/[counter]/+page.server.ts
··· 1 + import { redirect } from '@sveltejs/kit'; 2 + 3 + import type { Actions, PageServerLoad } from './$types'; 4 + import { register } from '$lib/Components/relative-time'; 5 + 6 + export const load: PageServerLoad = async (event) => { 7 + if (event.cookies.get('sid')) { 8 + const sid = event.cookies.get('sid'); 9 + 10 + 11 + } 12 + return {}; 13 + }; 14 +
+13
src/routes/win/[counter]/+page.svelte
··· 1 + <script lang="ts"> 2 + import type { ActionData } from './$types'; 3 + import Logo from '$lib/Components/Logo.svelte'; 4 + 5 + let { form }: { form: ActionData } = $props(); 6 + </script> 7 + 8 + <div class="flex min-h-screen flex-col items-center justify-center px-6 py-12 lg:px-8"> 9 + <div class="sm:mx-auto sm:w-full sm:max-w-sm"> 10 + <Logo class="mx-auto size-10" /> 11 + <h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight text-white">You have Won. Congratulations</h2> 12 + </div> 13 + </div>
+43
static/game-controller-svgrepo-com.svg
··· 1 + <?xml version="1.0" encoding="iso-8859-1"?> 2 + <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> 3 + <svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 4 + viewBox="0 0 470.001 470.001" xml:space="preserve"> 5 + <g> 6 + <g> 7 + <g> 8 + <path d="M469.413,349.114l-31.938-223.211c-2.786-19.467-12.513-37.35-27.387-50.355c-15.022-13.134-34.296-20.367-54.271-20.367 9 + c-28.272,0-54.562,14.564-69.647,38.296H183.831c-15.086-23.732-41.375-38.296-69.647-38.296 10 + c-19.976,0-39.249,7.233-54.271,20.367c-14.874,13.005-24.601,30.888-27.387,50.355L0.587,349.114 11 + C0.197,351.837,0,354.598,0,357.32c0,31.706,25.794,57.5,57.5,57.5c23.729,0,45.335-14.904,53.764-37.086l28.159-74.133 12 + c6.787,5.304,15.316,8.476,24.578,8.476c15.783,0,29.457-9.191,35.96-22.5h70.079c6.503,13.309,20.177,22.5,35.96,22.5 13 + c9.261,0,17.79-3.173,24.578-8.476l28.16,74.134c8.428,22.182,30.034,37.085,53.763,37.085c31.706,0,57.5-25.794,57.5-57.5 14 + C470,354.598,469.803,351.837,469.413,349.114z M164,297.077c-13.785,0-25-11.215-25-25c0-13.785,11.215-25,25-25 15 + c13.785,0,25,11.215,25,25C189,285.862,177.785,297.077,164,297.077z M306,297.077c-13.785,0-25-11.215-25-25 16 + c0-13.785,11.215-25,25-25c13.785,0,25,11.215,25,25C331,285.862,319.785,297.077,306,297.077z M412.5,399.82 17 + c-17.54,0-33.511-11.017-39.74-27.413l-31.181-82.086c2.819-5.475,4.421-11.674,4.421-18.244c0-22.056-17.944-40-40-40 18 + c-22.056,0-40,17.944-40,40c0,0.84,0.035,1.672,0.086,2.5h-62.172c0.051-0.828,0.086-1.66,0.086-2.5c0-22.056-17.944-40-40-40 19 + c-22.056,0-40,17.944-40,40c0,6.57,1.602,12.769,4.421,18.244l-31.18,82.086C91.011,388.803,75.04,399.82,57.5,399.82 20 + c-23.435,0-42.5-19.065-42.5-42.5c0-2.013,0.146-4.059,0.437-6.081l1.383-9.662l64.232,13.398c0.517,0.108,1.031,0.16,1.539,0.16 21 + c3.477,0,6.596-2.432,7.334-5.97c0.846-4.055-1.756-8.028-5.811-8.874l-65.165-13.593l21.704-151.683 22 + c13.65,26.774,41.475,45.165,73.532,45.165c45.49,0,82.5-37.009,82.5-82.5c0-2.46-0.109-4.939-0.325-7.369 23 + c-0.367-4.126-4.008-7.169-8.135-6.807c-4.126,0.367-7.173,4.009-6.807,8.135c0.177,1.989,0.267,4.021,0.267,6.041 24 + c0,37.22-30.28,67.5-67.5,67.5c-37.22,0-67.5-30.28-67.5-67.5c0-0.801-0.128-1.572-0.361-2.296l1.053-7.357 25 + c4.719-32.978,33.44-57.847,66.809-57.847c24.348,0,46.902,13.209,58.861,34.473c1.328,2.362,3.827,3.823,6.537,3.823H290.42 26 + c2.71,0,5.209-1.461,6.537-3.823c11.959-21.264,34.514-34.473,58.861-34.473c33.368,0,62.09,24.869,66.809,57.847l1.053,7.357 27 + c-0.233,0.724-0.361,1.495-0.361,2.296c0,37.22-30.28,67.5-67.5,67.5c-37.22,0-67.5-30.28-67.5-67.5 28 + c0-2.019,0.09-4.051,0.267-6.041c0.366-4.126-2.681-7.768-6.807-8.135c-4.108-0.363-7.768,2.681-8.135,6.807 29 + c-0.216,2.429-0.325,4.908-0.325,7.369c0,45.491,37.01,82.5,82.5,82.5c32.057,0,59.882-18.391,73.532-45.165l21.704,151.683 30 + l-65.164,13.594c-4.055,0.846-6.656,4.818-5.811,8.874c0.738,3.539,3.856,5.97,7.334,5.97c0.508,0,1.023-0.052,1.539-0.16 31 + l64.231-13.399l1.383,9.662c0.29,2.022,0.437,4.068,0.437,6.081C455,380.754,435.935,399.82,412.5,399.82z"/> 32 + <path d="M149.184,130.18h-27.5v-27.5c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v27.5h-27.5c-4.143,0-7.5,3.358-7.5,7.5 33 + c0,4.142,3.357,7.5,7.5,7.5h27.5v27.5c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5v-27.5h27.5c4.143,0,7.5-3.358,7.5-7.5 34 + C156.684,133.538,153.326,130.18,149.184,130.18z"/> 35 + <path d="M355.816,180.18c23.435,0,42.5-19.065,42.5-42.5s-19.065-42.5-42.5-42.5s-42.5,19.065-42.5,42.5 36 + S332.382,180.18,355.816,180.18z M355.816,110.18c15.163,0,27.5,12.336,27.5,27.5s-12.337,27.5-27.5,27.5 37 + c-15.163,0-27.5-12.336-27.5-27.5S340.653,110.18,355.816,110.18z"/> 38 + <path d="M246.781,244.577H223.22c-4.143,0-7.5,3.358-7.5,7.5c0,4.142,3.357,7.5,7.5,7.5h23.562c4.143,0,7.5-3.358,7.5-7.5 39 + C254.282,247.935,250.924,244.577,246.781,244.577z"/> 40 + </g> 41 + </g> 42 + </g> 43 + </svg>