your personal website on atproto - mirror blento.app

event stuff

Florian 50224195 043df1e8

+66 -145
+13 -13
src/lib/cards/social/EventCard/CreateEventCardModal.svelte
··· 2 2 import { Alert, Button, Input, Subheading } from '@foxui/core'; 3 3 import Modal from '$lib/components/modal/Modal.svelte'; 4 4 import type { CreationModalComponentProps } from '../../types'; 5 + import { getRecord } from '$lib/atproto/methods'; 6 + import type { Did } from '@atcute/lexicons'; 5 7 6 8 const EVENT_COLLECTION = 'community.lexicon.calendar.event'; 7 9 ··· 38 40 throw new Error('Invalid URL format'); 39 41 } 40 42 41 - // Validate the event exists by fetching it 42 - const response = await fetch( 43 - `https://smokesignal.events/xrpc/community.lexicon.calendar.GetEvent?repository=${encodeURIComponent(parsed.did)}&record_key=${encodeURIComponent(parsed.rkey)}` 44 - ); 43 + // Validate the event exists by fetching the record directly 44 + const record = await getRecord({ 45 + did: parsed.did as Did, 46 + collection: EVENT_COLLECTION, 47 + rkey: parsed.rkey 48 + }); 45 49 46 - if (!response.ok) { 50 + if (!record?.value) { 47 51 throw new Error('Event not found'); 48 52 } 49 53 ··· 55 59 errorMessage = 56 60 err instanceof Error && err.message === 'Event not found' 57 61 ? "Couldn't find that event. Please check the URL and try again." 58 - : 'Invalid URL. Please enter a valid smokesignal.events URL or AT URI.'; 62 + : 'Invalid URL. Please enter a valid event AT URI or smokesignal.events URL.'; 59 63 return false; 60 64 } finally { 61 65 isValidating = false; ··· 70 74 }} 71 75 class="flex flex-col gap-2" 72 76 > 73 - <Subheading>Enter a Smoke Signal event URL</Subheading> 77 + <Subheading>Enter an event URL</Subheading> 74 78 <Input 75 79 bind:value={eventUrl} 76 - placeholder="https://smokesignal.events/did:.../..." 80 + placeholder="at://did:.../community.lexicon.calendar.event/..." 77 81 class="mt-4" 78 82 /> 79 83 ··· 82 86 {/if} 83 87 84 88 <p class="text-base-500 dark:text-base-400 mt-2 text-xs"> 85 - Paste a URL from <a 86 - href="https://smokesignal.events" 87 - class="text-accent-800 dark:text-accent-300" 88 - target="_blank">smokesignal.events</a 89 - > or an AT URI for a calendar event. 89 + Paste an AT URI for a calendar event or a smokesignal.events URL. 90 90 </p> 91 91 92 92 <div class="mt-4 flex justify-end gap-2">
+4 -53
src/lib/cards/social/EventCard/EventCard.svelte
··· 91 91 } 92 92 93 93 let eventUrl = $derived(() => { 94 - if (eventData?.url) return eventData.url; 95 94 if (parsedUri) { 96 - return `https://smokesignal.events/${parsedUri.repo}/${parsedUri.rkey}`; 95 + return `/${parsedUri.repo}/events/${parsedUri.rkey}`; 97 96 } 98 97 return '#'; 99 98 }); ··· 144 143 </div> 145 144 146 145 {#if isMobile() ? item.mobileW > 4 : item.w > 2} 147 - <Button href={eventUrl()} target="_blank" rel="noopener noreferrer" class="z-50" 148 - >View event</Button 149 - > 146 + <Button href={eventUrl()} target="_blank" class="z-50">View event</Button> 150 147 {/if} 151 148 </div> 152 149 ··· 211 208 {eventData.description} 212 209 </p> 213 210 {/if} 214 - 215 - {#if (eventData.countGoing !== undefined || eventData.countInterested !== undefined) && ((isMobile() && item.mobileH >= 4) || (!isMobile() && item.h >= 3))} 216 - <div 217 - class="text-base-600 dark:text-base-400 accent:text-base-800 flex flex-wrap gap-3 text-xs" 218 - > 219 - {#if eventData.countGoing !== undefined} 220 - <div class="flex items-center gap-1"> 221 - <svg 222 - xmlns="http://www.w3.org/2000/svg" 223 - fill="none" 224 - viewBox="0 0 24 24" 225 - stroke-width="1.5" 226 - stroke="currentColor" 227 - class="size-4" 228 - > 229 - <path 230 - stroke-linecap="round" 231 - stroke-linejoin="round" 232 - d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" 233 - /> 234 - </svg> 235 - <span>{eventData.countGoing} going</span> 236 - </div> 237 - {/if} 238 - {#if eventData.countInterested !== undefined} 239 - <div class="flex items-center gap-1"> 240 - <svg 241 - xmlns="http://www.w3.org/2000/svg" 242 - fill="none" 243 - viewBox="0 0 24 24" 244 - stroke-width="1.5" 245 - stroke="currentColor" 246 - class="size-4" 247 - > 248 - <path 249 - stroke-linecap="round" 250 - stroke-linejoin="round" 251 - d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" 252 - /> 253 - </svg> 254 - <span>{eventData.countInterested} interested</span> 255 - </div> 256 - {/if} 257 - </div> 258 - {/if} 259 211 </div> 260 212 261 213 {#if showImage} ··· 267 219 268 220 <a 269 221 href={eventUrl()} 270 - class="absolute inset-0 h-full w-full" 271 222 target="_blank" 272 - rel="noopener noreferrer" 223 + class="absolute inset-0 h-full w-full" 273 224 use:qrOverlay={{ 274 225 context: { 275 226 title: eventData?.name ?? '' 276 227 } 277 228 }} 278 229 > 279 - <span class="sr-only">View event on smokesignal.events</span> 230 + <span class="sr-only">View event</span> 280 231 </a> 281 232 {:else if isLoaded} 282 233 <div class="flex h-full w-full items-center justify-center">
+11 -11
src/lib/cards/social/EventCard/index.ts
··· 1 - import { parseUri } from '$lib/atproto'; 1 + import { parseUri, getRecord } from '$lib/atproto'; 2 2 import type { CardDefinition } from '../../types'; 3 3 import CreateEventCardModal from './CreateEventCardModal.svelte'; 4 4 import EventCard from './EventCard.svelte'; 5 + import type { Did } from '@atcute/lexicons'; 5 6 6 7 const EVENT_COLLECTION = 'community.lexicon.calendar.event'; 7 8 ··· 39 40 uri: string; 40 41 name?: string; 41 42 }>; 42 - countGoing?: number; 43 - countInterested?: number; 44 - url: string; 43 + url?: string; 45 44 }; 46 45 47 46 export const EventCardDefinition = { ··· 66 65 if (!parsedUri || !parsedUri.rkey || !parsedUri.repo) continue; 67 66 68 67 try { 69 - const response = await fetch( 70 - `https://smokesignal.events/xrpc/community.lexicon.calendar.GetEvent?repository=${encodeURIComponent(parsedUri.repo)}&record_key=${encodeURIComponent(parsedUri.rkey)}` 71 - ); 68 + const record = await getRecord({ 69 + did: parsedUri.repo as Did, 70 + collection: EVENT_COLLECTION, 71 + rkey: parsedUri.rkey 72 + }); 72 73 73 - if (response.ok) { 74 - const data = await response.json(); 75 - eventDataMap[item.id] = data as EventData; 74 + if (record?.value) { 75 + eventDataMap[item.id] = record.value as EventData; 76 76 } 77 77 } catch (error) { 78 78 console.error('Failed to fetch event data:', error); ··· 118 118 119 119 name: 'Event', 120 120 121 - keywords: ['calendar', 'meetup', 'schedule', 'date', 'rsvp'], 121 + keywords: ['calendar', 'meetup', 'schedule', 'date', 'rsvp', 'smokesignal'], 122 122 groups: ['Social'], 123 123 icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" /></svg>` 124 124 } as CardDefinition & { type: 'event' };
+11 -11
src/routes/[[actor=actor]]/e/+page.server.ts src/routes/[[actor=actor]]/events/+page.server.ts
··· 1 1 import { error } from '@sveltejs/kit'; 2 2 import type { EventData } from '$lib/cards/social/EventCard'; 3 - import { getBlentoOrBskyProfile } from '$lib/atproto/methods.js'; 3 + import { getBlentoOrBskyProfile, listRecords } from '$lib/atproto/methods.js'; 4 4 import { createCache, type CachedProfile } from '$lib/cache'; 5 5 import type { Did } from '@atcute/lexicons'; 6 6 import { getActor } from '$lib/actor.js'; ··· 15 15 } 16 16 17 17 try { 18 - const [eventsResponse, hostProfile] = await Promise.all([ 19 - fetch( 20 - `https://smokesignal.events/xrpc/community.lexicon.calendar.searchEvents?repository=${encodeURIComponent(did)}&query=upcoming` 21 - ), 18 + const [records, hostProfile] = await Promise.all([ 19 + listRecords({ 20 + did: did as Did, 21 + collection: 'community.lexicon.calendar.event', 22 + limit: 100 23 + }), 22 24 cache 23 25 ? cache.getProfile(did as Did).catch(() => null) 24 26 : getBlentoOrBskyProfile({ did: did as Did }) ··· 35 37 .catch(() => null) 36 38 ]); 37 39 38 - if (!eventsResponse.ok) { 39 - throw error(404, 'Events not found'); 40 - } 41 - 42 - const data: { results: EventData[] } = await eventsResponse.json(); 43 - const events = data.results; 40 + const events = records.map((r) => ({ 41 + ...(r.value as EventData), 42 + rkey: r.uri.split('/').pop() as string 43 + })); 44 44 45 45 return { 46 46 events,
+4 -14
src/routes/[[actor=actor]]/e/+page.svelte src/routes/[[actor=actor]]/events/+page.svelte
··· 6 6 7 7 let { data } = $props(); 8 8 9 - let events: EventData[] = $derived(data.events); 9 + let events: (EventData & { rkey: string })[] = $derived(data.events); 10 10 let did: string = $derived(data.did); 11 11 let hostProfile = $derived(data.hostProfile); 12 12 ··· 74 74 return { url, alt: media.alt || event.name }; 75 75 } 76 76 77 - function getRkey(event: EventData): string { 78 - return event.url.split('/').pop() || ''; 79 - } 80 - 81 77 let actorPrefix = $derived(data.hostProfile?.handle ? `/${data.hostProfile.handle}` : `/${did}`); 82 78 </script> 83 79 ··· 98 94 <h1 class="text-base-900 dark:text-base-50 mb-2 text-2xl font-bold sm:text-3xl"> 99 95 Upcoming events 100 96 </h1> 101 - <div class="flex items-center gap-2 mt-4"> 97 + <div class="mt-4 flex items-center gap-2"> 102 98 <span class="text-base-500 dark:text-base-400 text-sm">Hosted by</span> 103 99 <a 104 100 href={hostUrl} ··· 116 112 <p class="text-base-500 dark:text-base-400 py-12 text-center">No events found.</p> 117 113 {:else} 118 114 <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"> 119 - {#each events as event (event.url)} 115 + {#each events as event (event.rkey)} 120 116 {@const thumbnail = getThumbnail(event)} 121 117 {@const location = getLocationString(event.locations)} 122 - {@const rkey = getRkey(event)} 118 + {@const rkey = event.rkey} 123 119 <a 124 120 href="{actorPrefix}/e/{rkey}" 125 121 class="border-base-200 dark:border-base-800 hover:border-base-300 dark:hover:border-base-700 group block overflow-hidden rounded-xl border transition-colors" ··· 168 164 <span class="text-base-500 dark:text-base-400 truncate text-xs">{location}</span> 169 165 {/if} 170 166 </div> 171 - 172 - {#if event.countGoing && event.countGoing > 0} 173 - <p class="text-base-500 dark:text-base-400 mt-2 text-xs"> 174 - {event.countGoing} going 175 - </p> 176 - {/if} 177 167 </div> 178 168 </a> 179 169 {/each}
+12 -17
src/routes/[[actor=actor]]/e/[rkey]/+page.server.ts src/routes/[[actor=actor]]/events/[rkey]/+page.server.ts
··· 1 1 import { error } from '@sveltejs/kit'; 2 2 import type { EventData } from '$lib/cards/social/EventCard'; 3 - import { getBlentoOrBskyProfile, getRecord, resolveHandle } from '$lib/atproto/methods.js'; 4 - import { isHandle } from '@atcute/lexicons/syntax'; 3 + import { getBlentoOrBskyProfile, getRecord } from '$lib/atproto/methods.js'; 5 4 import { createCache, type CachedProfile } from '$lib/cache'; 6 - import type { ActorIdentifier, Did } from '@atcute/lexicons'; 7 - import { env as publicEnv } from '$env/dynamic/public'; 5 + import type { Did } from '@atcute/lexicons'; 8 6 import { getActor } from '$lib/actor'; 9 7 10 8 export async function load({ params, platform, request }) { ··· 19 17 } 20 18 21 19 try { 22 - const [eventResponse, hostProfile, eventRecord] = await Promise.all([ 23 - fetch( 24 - `https://smokesignal.events/xrpc/community.lexicon.calendar.GetEvent?repository=${encodeURIComponent(did)}&record_key=${encodeURIComponent(rkey)}` 25 - ), 20 + const [eventRecord, hostProfile] = await Promise.all([ 21 + getRecord({ 22 + did: did as Did, 23 + collection: 'community.lexicon.calendar.event', 24 + rkey 25 + }), 26 26 cache 27 27 ? cache.getProfile(did as Did).catch(() => null) 28 28 : getBlentoOrBskyProfile({ did: did as Did }) ··· 36 36 url: p.url 37 37 }) 38 38 ) 39 - .catch(() => null), 40 - getRecord({ 41 - did: did as Did, 42 - collection: 'community.lexicon.calendar.event', 43 - rkey 44 - }).catch(() => null) 39 + .catch(() => null) 45 40 ]); 46 41 47 - if (!eventResponse.ok) { 42 + if (!eventRecord?.value) { 48 43 throw error(404, 'Event not found'); 49 44 } 50 45 51 - const eventData: EventData = await eventResponse.json(); 46 + const eventData: EventData = eventRecord.value as EventData; 52 47 53 48 return { 54 49 eventData, 55 50 did, 56 51 rkey, 57 52 hostProfile: hostProfile ?? null, 58 - eventCid: (eventRecord?.cid as string) ?? null 53 + eventCid: (eventRecord.cid as string) ?? null 59 54 }; 60 55 } catch (e) { 61 56 if (e && typeof e === 'object' && 'status' in e) throw e;
+2 -16
src/routes/[[actor=actor]]/e/[rkey]/+page.svelte src/routes/[[actor=actor]]/events/[rkey]/+page.svelte
··· 88 88 return { url, alt: media.alt || eventData.name }; 89 89 }); 90 90 91 - let eventUrl = $derived(eventData.url || `https://smokesignal.events/${did}/${rkey}`); 91 + let smokesignalUrl = $derived(`https://smokesignal.events/${did}/${rkey}`); 92 92 let eventUri = $derived(`at://${did}/community.lexicon.calendar.event/${rkey}`); 93 93 94 94 let ogImageUrl = $derived(`${page.url.origin}${page.url.pathname}/og.png`); ··· 247 247 </a> 248 248 </div> 249 249 250 - {#if (eventData.countGoing && eventData.countGoing > 0) || (eventData.countInterested && eventData.countInterested > 0)} 251 - <!-- Counts --> 252 - <div 253 - class="text-base-900 dark:text-base-100 order-4 space-y-2.5 text-base font-medium md:order-0 md:col-start-1" 254 - > 255 - {#if eventData.countGoing && eventData.countGoing > 0} 256 - <p>{eventData.countGoing} Going</p> 257 - {/if} 258 - {#if eventData.countInterested && eventData.countInterested > 0} 259 - <p>{eventData.countInterested} Interested</p> 260 - {/if} 261 - </div> 262 - {/if} 263 - 264 250 {#if eventData.uris && eventData.uris.length > 0} 265 251 <!-- Links --> 266 252 <div class="order-5 md:order-0 md:col-start-1"> ··· 300 286 301 287 <!-- View on Smoke Signal link --> 302 288 <a 303 - href={eventUrl} 289 + href={smokesignalUrl} 304 290 target="_blank" 305 291 rel="noopener noreferrer" 306 292 class="text-base-500 dark:text-base-400 hover:text-base-700 dark:hover:text-base-200 order-6 inline-flex items-center gap-1.5 text-sm transition-colors md:order-0 md:col-start-2"
src/routes/[[actor=actor]]/e/[rkey]/EventRsvp.svelte src/routes/[[actor=actor]]/events/[rkey]/EventRsvp.svelte
+9 -9
src/routes/[[actor=actor]]/e/[rkey]/og.png/+server.ts src/routes/[[actor=actor]]/events/[rkey]/og.png/+server.ts
··· 1 - import { getCDNImageBlobUrl, resolveHandle } from '$lib/atproto/methods.js'; 2 - import { env as publicEnv } from '$env/dynamic/public'; 1 + import { getCDNImageBlobUrl, getRecord } from '$lib/atproto/methods.js'; 3 2 4 - import type { ActorIdentifier } from '@atcute/lexicons'; 5 - import { isHandle } from '@atcute/lexicons/syntax'; 3 + import type { Did } from '@atcute/lexicons'; 6 4 import type { EventData } from '$lib/cards/social/EventCard'; 7 5 import { ImageResponse } from '@ethercorps/sveltekit-og'; 8 6 import { error } from '@sveltejs/kit'; ··· 29 27 let eventData: EventData; 30 28 31 29 try { 32 - const eventResponse = await fetch( 33 - `https://smokesignal.events/xrpc/community.lexicon.calendar.GetEvent?repository=${encodeURIComponent(did)}&record_key=${encodeURIComponent(rkey)}` 34 - ); 30 + const eventRecord = await getRecord({ 31 + did: did as Did, 32 + collection: 'community.lexicon.calendar.event', 33 + rkey 34 + }); 35 35 36 - if (!eventResponse.ok) { 36 + if (!eventRecord?.value) { 37 37 throw error(404, 'Event not found'); 38 38 } 39 39 40 - eventData = await eventResponse.json(); 40 + eventData = eventRecord.value as EventData; 41 41 } catch (e) { 42 42 if (e && typeof e === 'object' && 'status' in e) throw e; 43 43 throw error(404, 'Event not found');
-1
src/routes/[[actor=actor]]/e/[rkey]/og.png/EventOgImage.svelte src/routes/[[actor=actor]]/events/[rkey]/og.png/EventOgImage.svelte
··· 58 58 </svg> 59 59 <span class="ml-3 text-2xl text-neutral-300">{dateStr}</span> 60 60 </div> 61 - 62 61 </div> 63 62 </div>