tangled
alpha
login
or
join now
flo-bit.dev
/
blento
21
fork
atom
your personal website on atproto - mirror
blento.app
21
fork
atom
overview
issues
pulls
pipelines
small fixes, move rsvp info to constellation
Florian
3 weeks ago
2a33d4c2
d2ed12fc
+56
-61
4 changed files
expand all
collapse all
unified
split
src
routes
[[actor=actor]]
events
[rkey]
+page.svelte
EventAttendees.svelte
EventRsvp.svelte
api.remote.ts
+3
-3
src/routes/[[actor=actor]]/events/[rkey]/+page.svelte
···
155
155
: null
156
156
);
157
157
158
158
-
let smokesignalUrl = $derived(`https://smokesignal.events/${did}/${rkey}`);
158
158
+
// let smokesignalUrl = $derived(`https://smokesignal.events/${did}/${rkey}`);
159
159
let eventUri = $derived(`at://${did}/community.lexicon.calendar.event/${rkey}`);
160
160
161
161
let ogImageUrl = $derived(`${page.url.origin}${page.url.pathname}/og.png`);
···
252
252
</div>
253
253
{/if}
254
254
255
255
-
<!-- Date row (Luma-style calendar icon) -->
255
255
+
<!-- Date row -->
256
256
<div class="mb-4 flex items-center gap-4">
257
257
<div
258
258
-
class="border-base-200 dark:border-base-700 flex size-12 shrink-0 flex-col items-center justify-center overflow-hidden rounded-xl border"
258
258
+
class="border-base-200 dark:border-base-700 bg-base-100 dark:bg-base-950/30 flex size-12 shrink-0 flex-col items-center justify-center overflow-hidden rounded-xl border"
259
259
>
260
260
<span class="text-base-500 dark:text-base-400 text-[9px] leading-none font-semibold">
261
261
{formatMonth(startDate)}
+8
-6
src/routes/[[actor=actor]]/events/[rkey]/EventAttendees.svelte
···
83
83
{#if goingCount > 0}
84
84
<button
85
85
type="button"
86
86
-
class="hover:bg-base-100 dark:hover:bg-base-800/50 -mx-2 cursor-pointer rounded-xl px-2 py-2 text-left transition-colors"
86
86
+
class="hover:bg-base-100 dark:hover:bg-base-800/50 -mx-2 block w-full cursor-pointer rounded-xl px-2 py-2 text-left transition-colors"
87
87
onclick={() => openModal('going')}
88
88
>
89
89
<p class="text-base-900 dark:text-base-50 mb-2 text-sm">
···
124
124
{#if interestedCount > 0}
125
125
<button
126
126
type="button"
127
127
-
class="hover:bg-base-100 dark:hover:bg-base-800/50 -mx-2 mt-4 cursor-pointer rounded-xl px-2 py-2 text-left transition-colors"
127
127
+
class="hover:bg-base-100 dark:hover:bg-base-800/50 -mx-2 mt-4 block w-full cursor-pointer rounded-xl px-2 py-2 text-left transition-colors"
128
128
onclick={() => openModal('interested')}
129
129
>
130
130
<p class="text-base-900 dark:text-base-50 mb-2 text-sm">
···
164
164
</div>
165
165
{/if}
166
166
167
167
-
<Modal bind:open={modalOpen} closeButton onOpenAutoFocus={(e) => e.preventDefault()}>
168
168
-
<p class="text-base-900 dark:text-base-50 text-lg font-semibold">
167
167
+
<Modal bind:open={modalOpen} closeButton onOpenAutoFocus={(e) => e.preventDefault()} class="p-0">
168
168
+
<p class="text-base-900 dark:text-base-50 px-4 pt-4 text-lg font-semibold">
169
169
{modalTitle}
170
170
<span class="text-base-500 dark:text-base-400 text-sm font-normal">
171
171
({modalAttendees.length})
172
172
</span>
173
173
</p>
174
174
-
<div class="mt-3 max-h-80 space-y-1 overflow-y-auto p-2">
174
174
+
<div
175
175
+
class="dark:bg-base-900/50 bg-base-200/30 mx-4 mb-4 max-h-80 space-y-1 overflow-y-auto rounded-xl p-2"
176
176
+
>
175
177
{#each modalAttendees as person (person.did)}
176
178
<a
177
179
href={person.url}
178
180
target={person.url?.startsWith('/') ? undefined : '_blank'}
179
181
rel={person.url?.startsWith('/') ? undefined : 'noopener noreferrer'}
180
180
-
class="hover:bg-base-100 dark:hover:bg-base-800 flex items-center gap-3 rounded-xl px-2 py-2 transition-colors"
182
182
+
class="hover:bg-base-200 dark:hover:bg-base-900 flex items-center gap-3 rounded-xl px-2 py-2 transition-colors"
181
183
>
182
184
<FoxAvatar
183
185
src={person.avatar}
+38
-48
src/routes/[[actor=actor]]/events/[rkey]/EventRsvp.svelte
···
1
1
<script lang="ts">
2
2
import { user } from '$lib/atproto/auth.svelte';
3
3
+
import { getRecord, putRecord, deleteRecord, createTID } from '$lib/atproto/methods';
3
4
import { loginModalState } from '$lib/atproto/UI/LoginModal.svelte';
4
5
import { Avatar, Button } from '@foxui/core';
6
6
+
import type { Did } from '@atcute/lexicons';
5
7
6
8
let {
7
9
eventUri,
···
30
32
31
33
rsvpLoading = true;
32
34
33
33
-
fetch(
34
34
-
`https://smokesignal.events/xrpc/community.lexicon.calendar.getRSVP?identity=${encodeURIComponent(userDid)}&event=${encodeURIComponent(eventUri)}`
35
35
-
)
35
35
+
const url = `https://constellation.microcosm.blue/xrpc/blue.microcosm.links.getBacklinks?subject=${encodeURIComponent(eventUri)}&source=community.lexicon.calendar.rsvp:subject.uri&did=${encodeURIComponent(userDid)}&limit=1`;
36
36
+
37
37
+
fetch(url)
36
38
.then((res) => {
37
37
-
if (!res.ok) {
38
38
-
rsvpStatus = null;
39
39
-
rsvpRkey = null;
40
40
-
return;
41
41
-
}
39
39
+
if (!res.ok) throw new Error('Failed to fetch backlinks');
42
40
return res.json();
43
41
})
44
44
-
.then((data) => {
45
45
-
if (!data?.record?.status) {
42
42
+
.then(async (data) => {
43
43
+
if (!data?.records?.length) {
46
44
rsvpStatus = null;
47
45
rsvpRkey = null;
48
46
return;
49
47
}
50
50
-
if (data.uri) {
51
51
-
const parts = data.uri.split('/');
52
52
-
rsvpRkey = parts[parts.length - 1];
53
53
-
}
54
54
-
const status = data.record.status as string;
55
55
-
if (status.includes('#going')) rsvpStatus = 'going';
56
56
-
else if (status.includes('#interested')) rsvpStatus = 'interested';
57
57
-
else if (status.includes('#notgoing')) rsvpStatus = 'notgoing';
48
48
+
49
49
+
const backlink = data.records[0];
50
50
+
rsvpRkey = backlink.rkey;
51
51
+
52
52
+
const recordData = await getRecord({
53
53
+
did: backlink.did as Did,
54
54
+
collection: backlink.collection,
55
55
+
rkey: backlink.rkey
56
56
+
});
57
57
+
58
58
+
const status = recordData?.value?.status as string;
59
59
+
if (status?.includes('#going')) rsvpStatus = 'going';
60
60
+
else if (status?.includes('#interested')) rsvpStatus = 'interested';
61
61
+
else if (status?.includes('#notgoing')) rsvpStatus = 'notgoing';
58
62
else rsvpStatus = null;
59
63
})
60
64
.catch(() => {
···
70
74
if (!user.client || !user.did) return;
71
75
rsvpSubmitting = true;
72
76
try {
73
73
-
if (rsvpRkey) {
74
74
-
await user.client.post('com.atproto.repo.deleteRecord', {
75
75
-
input: {
76
76
-
collection: 'community.lexicon.calendar.rsvp',
77
77
-
repo: user.did,
78
78
-
rkey: rsvpRkey
79
79
-
}
80
80
-
});
81
81
-
}
77
77
+
const key = rsvpRkey ?? createTID();
82
78
83
83
-
const response = await user.client.post('com.atproto.repo.createRecord', {
84
84
-
input: {
85
85
-
collection: 'community.lexicon.calendar.rsvp',
86
86
-
repo: user.did,
87
87
-
record: {
88
88
-
$type: 'community.lexicon.calendar.rsvp',
89
89
-
status: `community.lexicon.calendar.rsvp#${status}`,
90
90
-
subject: {
91
91
-
uri: eventUri,
92
92
-
...(eventCid ? { cid: eventCid } : {})
93
93
-
},
94
94
-
createdAt: new Date().toISOString()
95
95
-
}
79
79
+
const response = await putRecord({
80
80
+
collection: 'community.lexicon.calendar.rsvp',
81
81
+
rkey: key,
82
82
+
record: {
83
83
+
$type: 'community.lexicon.calendar.rsvp',
84
84
+
status: `community.lexicon.calendar.rsvp#${status}`,
85
85
+
subject: {
86
86
+
uri: eventUri,
87
87
+
...(eventCid ? { cid: eventCid } : {})
88
88
+
},
89
89
+
createdAt: new Date().toISOString()
96
90
}
97
91
});
98
92
99
93
if (response.ok) {
100
94
rsvpStatus = status;
101
101
-
const parts = response.data.uri.split('/');
102
102
-
rsvpRkey = parts[parts.length - 1];
95
95
+
rsvpRkey = key;
103
96
onrsvp?.(status);
104
97
}
105
98
} catch (e) {
···
113
106
if (!user.client || !user.did || !rsvpRkey) return;
114
107
rsvpSubmitting = true;
115
108
try {
116
116
-
await user.client.post('com.atproto.repo.deleteRecord', {
117
117
-
input: {
118
118
-
collection: 'community.lexicon.calendar.rsvp',
119
119
-
repo: user.did,
120
120
-
rkey: rsvpRkey
121
121
-
}
109
109
+
await deleteRecord({
110
110
+
collection: 'community.lexicon.calendar.rsvp',
111
111
+
rkey: rsvpRkey
122
112
});
123
113
rsvpStatus = null;
124
114
rsvpRkey = null;
+7
-4
src/routes/[[actor=actor]]/events/[rkey]/api.remote.ts
···
133
133
};
134
134
}
135
135
136
136
+
const uniqueGoing = [...new Set(going)];
137
137
+
const uniqueInterested = [...new Set(interested)];
138
138
+
136
139
return {
137
137
-
going: going.map((did) => toAttendeeInfo(did, 'going')),
138
138
-
interested: interested.map((did) => toAttendeeInfo(did, 'interested')),
139
139
-
goingCount: going.length,
140
140
-
interestedCount: interested.length
140
140
+
going: uniqueGoing.map((did) => toAttendeeInfo(did, 'going')),
141
141
+
interested: uniqueInterested.map((did) => toAttendeeInfo(did, 'interested')),
142
142
+
goingCount: uniqueGoing.length,
143
143
+
interestedCount: uniqueInterested.length
141
144
};
142
145
}
143
146
);