[READ-ONLY] Mirror of https://github.com/flo-bit/skywatched. review movies and tv shows, based on at proto
skywatched.app
5.2 kB
145 lines
1<script lang="ts">
2 import { crosspostModal, rateMovieModal, settings, watchedItems } from '$lib/state.svelte';
3 import { toast } from 'svelte-sonner';
4 import Rating from './Rating.svelte';
5 import SearchCombobox from './SearchCombobox.svelte';
6
7 let rating = $state(rateMovieModal.selectedItem.currentRating ?? 0);
8 let review = $state(rateMovieModal.selectedItem.currentReview ?? '');
9
10 $effect(() => {
11 rating = rateMovieModal.selectedItem.currentRating ?? 0;
12 review = rateMovieModal.selectedItem.currentReview ?? '';
13 });
14
15 let sending = $state(false);
16</script>
17
18{#if rateMovieModal.showModal}
19 <div class="relative z-50" aria-labelledby="modal-title" role="dialog" aria-modal="true">
20 <div
21 class="fixed inset-0 bg-base-950/90 backdrop-blur-sm transition-opacity"
22 onclick={() => (rateMovieModal.showModal = false)}
23 aria-hidden="true"
24 ></div>
25 <div class="pointer-events-none fixed inset-0 z-50 h-[100dvh] w-screen overflow-y-auto">
26 <div class="flex h-[100dvh] items-end justify-center p-4 text-center sm:items-center sm:p-0">
27 <div
28 class="pointer-events-auto relative w-full transform overflow-hidden rounded-lg border border-base-800 bg-base-900 px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:max-w-sm sm:p-6"
29 >
30 <button
31 class="absolute right-2 top-2 rounded-full bg-base-800/50 p-1 hover:bg-base-800/80"
32 onclick={() => (rateMovieModal.showModal = false)}
33 >
34 <svg
35 xmlns="http://www.w3.org/2000/svg"
36 fill="none"
37 viewBox="0 0 24 24"
38 stroke-width="1.5"
39 stroke="currentColor"
40 class="size-4"
41 >
42 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
43 </svg>
44
45 <span class="sr-only">close</span>
46 </button>
47
48 <div>
49 <h3 class="text-md mb-4 font-semibold text-base-50" id="modal-title">
50 Rate and review
51 </h3>
52
53 {#if rateMovieModal.selectedItem.name}
54 <div class="relative flex items-center gap-4">
55 <div
56 class="relative z-20 aspect-[2/3] h-32 w-auto shrink-0 overflow-hidden rounded-md border border-base-800 bg-base-900/50"
57 >
58 {#if rateMovieModal.selectedItem.posterPath}
59 <img
60 src="https://image.tmdb.org/t/p/w154{rateMovieModal.selectedItem.posterPath}"
61 alt="movie poster for {rateMovieModal.selectedItem.name}"
62 class="size-full object-cover object-center lg:size-full"
63 />
64 {/if}
65 </div>
66 <h3
67 class="mb-4 flex flex-col gap-2 text-xl font-semibold text-base-50"
68 id="modal-title"
69 >
70 {rateMovieModal.selectedItem.name}
71
72 <Rating bind:rating canChange size="size-7" />
73 </h3>
74 <!-- <div class="absolute right-2 top-0">
75 <button
76 onclick={() => (rateMovieModal.showModal = false)}
77 class="rounded-full bg-base-800/50 p-1 px-2 text-xs text-base-50 hover:bg-base-800/70 hover:text-base-100"
78 >
79 change
80 </button>
81 </div> -->
82 </div>
83 {:else}
84 <SearchCombobox />
85 {/if}
86
87 <div class="mt-4">
88 <label for="comment" class="block text-xs font-medium text-base-50">review</label>
89 <div class="mt-2">
90 <textarea
91 rows="4"
92 name="comment"
93 id="comment"
94 bind:value={review}
95 placeholder="write a review"
96 class="outline-nonse block w-full rounded-lg border border-base-800 bg-base-950 px-3 py-1.5 text-base text-base-50 -outline-offset-1 placeholder:text-base-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-accent-400 sm:text-sm/6"
97 ></textarea>
98 </div>
99 </div>
100 </div>
101 <div class="mt-5 sm:mt-6">
102 <button
103 onclick={async () => {
104 const response = await fetch(`/api/rate`, {
105 method: 'POST',
106 body: JSON.stringify({
107 rating,
108 review,
109 kind: rateMovieModal.selectedItem.kind,
110 id: rateMovieModal.selectedItem.movieId ?? rateMovieModal.selectedItem.showId
111 })
112 });
113
114 if (!response.ok) {
115 toast.error('Failed to save rating');
116
117 return;
118 }
119 rateMovieModal.showModal = false;
120
121 const data = await response.json();
122
123 if (settings.crosspostEnabled && review.length > 0) {
124 crosspostModal.show(data.uri, review, rating, rateMovieModal.selectedItem.name ?? '');
125 }
126
127 watchedItems.addRated({
128 movieId: rateMovieModal.selectedItem.movieId,
129 showId: rateMovieModal.selectedItem.showId,
130 rating
131 });
132
133 toast.success('Rating saved');
134 }}
135 type="button"
136 disabled={sending || !rateMovieModal.selectedItem.name}
137 class="inline-flex w-full justify-center rounded-md border border-accent-900 bg-accent-950/80 px-3 py-2 text-sm font-semibold text-accent-300 shadow-sm hover:bg-accent-950 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-600 disabled:opacity-50 disabled:cursor-not-allowed"
138 >{sending ? 'Sending...' : 'Review'}</button
139 >
140 </div>
141 </div>
142 </div>
143 </div>
144 </div>
145{/if}