-14
README.md
-14
README.md
···
1
-
# ATmosphereConf
2
-
3
-
Main website: https://atmosphereconf.org (currently redirects to our ticketing landing page)
4
-
5
-
News & Updates: <https://news.atprotocol.org> (powered by Leaflet!)
6
-
Save the Date with an RSVP on [Smoke Signal](https://smokesignal.events/did:plc:lehcqqkwzcwvjvw66uthu5oq/3lte3c7x43l2e)
7
-
8
-
We will be building out a conference website with a handful of ATProto specific features.
9
-
10
-
For open discussion, and for those who are joining as active volunteers, please visit the [community forum](https://discourse.atprotocol.community/c/atmosphereconf/25/none).
11
-
12
-
## Conference Profile
13
-
For starters, we're going to have a custom conference profile. Attendees and speakers (and anyone else!) can login and create an extended profile. Inspired by [Discover Toronto](https://discover.toronto.inc/), we had an [initial discussion in the forum](https://discourse.atprotocol.community/t/conference-profiles/186) and are going to work on fleshing this out here with detailed issues.
14
-
15
1
# Astro ATProto OAuth Starter
16
2
17
3
A minimal [Astro](https://astro.build) starter template demonstrating OAuth authentication with AT Protocol (ATProto), the decentralized social networking protocol used by Bluesky and other services.
+3
-9
lexicons/profile.json
+3
-9
lexicons/profile.json
···
14
14
"maxGraphemes": 64,
15
15
"maxLength": 640
16
16
},
17
-
"about": {
18
-
"type": "string",
19
-
"description": "Conference attendee extended about text",
20
-
"maxGraphemes": 1024,
21
-
"maxLength": 10240
22
-
},
23
-
"shortbio": {
17
+
"description": {
24
18
"type": "string",
25
-
"description": "If you are a speaker, this is displayed as your bio. Use about for extended text",
19
+
"description": "Free-form profile description text.",
26
20
"maxGraphemes": 256,
27
21
"maxLength": 2560
28
22
},
29
23
"avatar": {
30
24
"type": "blob",
31
-
"description": "Profile picture for conference attendee. Defaults to Bluesky profile pic on first login. If you are speaking, this is used as your speaker headshot",
25
+
"description": "Profile picture for conference attendee",
32
26
"accept": ["image/png", "image/jpeg"],
33
27
"maxSize": 1000000
34
28
},
+19
-33
src/components/ProfileForm.astro
+19
-33
src/components/ProfileForm.astro
···
1
1
---
2
2
interface Props {
3
3
displayName?: string
4
-
about?: string
4
+
description?: string
5
5
avatar?: string
6
6
banner?: string
7
7
submitLabel?: string
···
10
10
11
11
const {
12
12
displayName = '',
13
-
about = '',
13
+
description = '',
14
14
avatar = '',
15
15
banner = '',
16
16
submitLabel = 'Create Profile',
···
26
26
>
27
27
<div class="form-control">
28
28
<label class="label">
29
-
<span class="label-text">Attendee Name</span>
29
+
<span class="label-text">Display Name</span>
30
30
<span class="label-text-alt">Max 64 characters</span>
31
31
</label>
32
32
<input
33
33
type="text"
34
34
name="displayName"
35
-
placeholder="This is the display name that will be shown as your attendee name."
35
+
placeholder="Enter your display name"
36
36
class="input input-bordered w-full"
37
37
value={displayName}
38
38
maxlength="64"
···
42
42
43
43
<div class="form-control">
44
44
<label class="label">
45
-
<span class="label-text">Attendee Image</span>
46
-
<span class="label-text-alt">Your public attendee image. PNG or JPEG, max 1MB</span>
45
+
<span class="label-text">Description</span>
46
+
<span class="label-text-alt">Max 256 characters</span>
47
+
</label>
48
+
<textarea
49
+
name="description"
50
+
placeholder="Tell us about yourself"
51
+
class="textarea textarea-bordered h-24"
52
+
maxlength="256"
53
+
>{description}</textarea>
54
+
</div>
55
+
56
+
<div class="form-control">
57
+
<label class="label">
58
+
<span class="label-text">Avatar</span>
59
+
<span class="label-text-alt">PNG or JPEG, max 1MB</span>
47
60
</label>
48
61
{avatar && (
49
62
<div class="avatar mb-2">
···
59
72
class="file-input file-input-bordered w-full"
60
73
/>
61
74
</div>
62
-
63
-
<div class="form-control">
64
-
<label class="label">
65
-
<span class="label-text">Bio</span>
66
-
<span class="label-text-alt">A short biography, displayed as speaker bios. Max 256 characters</span>
67
-
</label><br />
68
-
<textarea
69
-
name="shortbio"
70
-
placeholder="If you are a speaker, this is displayed as your bio. Use About for extended background"
71
-
class="textarea textarea-bordered h-24 w-full"
72
-
maxlength="256"
73
-
>{about}</textarea>
74
-
</div>
75
-
76
-
<div class="form-control">
77
-
<label class="label">
78
-
<span class="label-text">About</span>
79
-
<span class="label-text-alt">Max 1024 characters</span>
80
-
</label><br />
81
-
<textarea
82
-
name="about"
83
-
placeholder="Tell us about yourself"
84
-
class="textarea textarea-bordered h-24 w-full"
85
-
maxlength="1024"
86
-
>{about}</textarea>
87
-
</div>
88
-
89
75
90
76
<div class="form-control">
91
77
<label class="label">
+46
-27
src/lexicon/lexicons.ts
+46
-27
src/lexicon/lexicons.ts
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
-
import { type LexiconDoc, Lexicons, ValidationError, type ValidationResult } from '@atproto/lexicon'
4
+
import {
5
+
type LexiconDoc,
6
+
Lexicons,
7
+
ValidationError,
8
+
type ValidationResult,
9
+
} from '@atproto/lexicon'
5
10
import { type $Typed, is$typed, maybe$typed } from './util.js'
6
11
7
12
export const schemaDict = {
···
11
16
defs: {
12
17
label: {
13
18
type: 'object',
14
-
description: 'Metadata tag on an atproto resource (eg, repo or record).',
19
+
description:
20
+
'Metadata tag on an atproto resource (eg, repo or record).',
15
21
required: ['src', 'uri', 'val', 'cts'],
16
22
properties: {
17
23
ver: {
···
26
32
uri: {
27
33
type: 'string',
28
34
format: 'uri',
29
-
description: 'AT URI of the record, repository (account), or other resource that this label applies to.',
35
+
description:
36
+
'AT URI of the record, repository (account), or other resource that this label applies to.',
30
37
},
31
38
cid: {
32
39
type: 'string',
33
40
format: 'cid',
34
-
description: "Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
41
+
description:
42
+
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
35
43
},
36
44
val: {
37
45
type: 'string',
38
46
maxLength: 128,
39
-
description: 'The short string name of the value or type of this label.',
47
+
description:
48
+
'The short string name of the value or type of this label.',
40
49
},
41
50
neg: {
42
51
type: 'boolean',
43
-
description: 'If true, this is a negation label, overwriting a previous label.',
52
+
description:
53
+
'If true, this is a negation label, overwriting a previous label.',
44
54
},
45
55
cts: {
46
56
type: 'string',
···
50
60
exp: {
51
61
type: 'string',
52
62
format: 'datetime',
53
-
description: 'Timestamp at which this label expires (no longer applies).',
63
+
description:
64
+
'Timestamp at which this label expires (no longer applies).',
54
65
},
55
66
sig: {
56
67
type: 'bytes',
···
60
71
},
61
72
selfLabels: {
62
73
type: 'object',
63
-
description: 'Metadata tags on an atproto record, published by the author within the record.',
74
+
description:
75
+
'Metadata tags on an atproto record, published by the author within the record.',
64
76
required: ['values'],
65
77
properties: {
66
78
values: {
···
82
94
val: {
83
95
type: 'string',
84
96
maxLength: 128,
85
-
description: 'The short string name of the value or type of this label.',
97
+
description:
98
+
'The short string name of the value or type of this label.',
86
99
},
87
100
},
88
101
},
89
102
labelValueDefinition: {
90
103
type: 'object',
91
-
description: 'Declares a label value and its expected interpretations and behaviors.',
104
+
description:
105
+
'Declares a label value and its expected interpretations and behaviors.',
92
106
required: ['identifier', 'severity', 'blurs', 'locales'],
93
107
properties: {
94
108
identifier: {
···
118
132
},
119
133
adultOnly: {
120
134
type: 'boolean',
121
-
description: 'Does the user need to have adult content enabled in order to configure this label?',
135
+
description:
136
+
'Does the user need to have adult content enabled in order to configure this label?',
122
137
},
123
138
locales: {
124
139
type: 'array',
···
131
146
},
132
147
labelValueDefinitionStrings: {
133
148
type: 'object',
134
-
description: 'Strings which describe the label in the UI, localized into a specific language.',
149
+
description:
150
+
'Strings which describe the label in the UI, localized into a specific language.',
135
151
required: ['lang', 'name', 'description'],
136
152
properties: {
137
153
lang: {
138
154
type: 'string',
139
-
description: 'The code of the language these strings are written in.',
155
+
description:
156
+
'The code of the language these strings are written in.',
140
157
format: 'language',
141
158
},
142
159
name: {
···
147
164
},
148
165
description: {
149
166
type: 'string',
150
-
description: 'A longer description of what the label means and why it might be applied.',
167
+
description:
168
+
'A longer description of what the label means and why it might be applied.',
151
169
maxGraphemes: 10000,
152
170
maxLength: 100000,
153
171
},
···
187
205
maxGraphemes: 64,
188
206
maxLength: 640,
189
207
},
190
-
about: {
208
+
description: {
191
209
type: 'string',
192
-
description: 'Conference attendee extended about text',
193
-
maxGraphemes: 512,
194
-
maxLength: 5120,
195
-
},
196
-
shortbio: {
197
-
type: 'string',
198
-
description: 'If you are a speaker, this is displayed as your bio. Use about for extended text',
210
+
description: 'Free-form profile description text.',
199
211
maxGraphemes: 256,
200
212
maxLength: 2560,
201
213
},
202
214
avatar: {
203
215
type: 'blob',
204
-
description:
205
-
'Profile picture for conference attendee. Defaults to Bluesky profile pic on first login. If you are speaking, this is used as your speaker headshot',
216
+
description: 'Profile picture for conference attendee',
206
217
accept: ['image/png', 'image/jpeg'],
207
218
maxSize: 1000000,
208
219
},
209
220
banner: {
210
221
type: 'blob',
211
-
description: 'Larger horizontal image to display behind profile view.',
222
+
description:
223
+
'Larger horizontal image to display behind profile view.',
212
224
accept: ['image/png', 'image/jpeg'],
213
225
maxSize: 1000000,
214
226
},
···
263
275
hash: string,
264
276
requiredType?: false,
265
277
): ValidationResult<T>
266
-
export function validate(v: unknown, id: string, hash: string, requiredType?: boolean): ValidationResult {
278
+
export function validate(
279
+
v: unknown,
280
+
id: string,
281
+
hash: string,
282
+
requiredType?: boolean,
283
+
): ValidationResult {
267
284
return (requiredType ? is$typed : maybe$typed)(v, id, hash)
268
285
? lexicons.validate(`${id}#${hash}`, v)
269
286
: {
270
287
success: false,
271
-
error: new ValidationError(`Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`),
288
+
error: new ValidationError(
289
+
`Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`,
290
+
),
272
291
}
273
292
}
274
293
+6
-4
src/lexicon/types/org/atmosphereconf/profile.ts
+6
-4
src/lexicon/types/org/atmosphereconf/profile.ts
···
14
14
export interface Main {
15
15
$type: 'org.atmosphereconf.profile'
16
16
displayName?: string
17
-
/** Bio for speakers */
18
-
shortbio?: string
19
17
/** Free-form profile description text. */
20
-
about?: string
18
+
description?: string
21
19
/** Profile picture for conference attendee */
22
20
avatar?: BlobRef
23
21
/** Larger horizontal image to display behind profile view. */
···
37
35
return validate<Main & V>(v, id, hashMain, true)
38
36
}
39
37
40
-
export { type Main as Record, isMain as isRecord, validateMain as validateRecord }
38
+
export {
39
+
type Main as Record,
40
+
isMain as isRecord,
41
+
validateMain as validateRecord,
42
+
}
+2
-2
src/pages/index.astro
+2
-2
src/pages/index.astro
···
34
34
const displayName = profile?.displayName || agent?.assertDid || 'User'
35
35
const handle = profile?.handle || agent?.assertDid || ''
36
36
const avatar = profile?.avatar
37
-
const shortbio = profile?.shortbio
37
+
const description = profile?.description
38
38
---
39
39
40
40
<html lang="en" data-theme="dracula">
···
64
64
)}
65
65
<p class="text-lg font-semibold">{displayName}</p>
66
66
<p class="text-sm opacity-70">{handle}</p>
67
-
{shortbio && <p class="text-sm mt-2 opacity-80">{shortbio}</p>}
67
+
{description && <p class="text-sm mt-2 opacity-80">{description}</p>}
68
68
</div>
69
69
<div class="space-y-2 w-full">
70
70
<a href={`/profile/${handle}`} class="btn btn-primary w-full">
+3
-3
src/pages/profile/[handle].astro
+3
-3
src/pages/profile/[handle].astro
···
80
80
}
81
81
82
82
const displayName = conferenceProfile?.displayName || profile?.displayName || handle
83
-
const shortbio = conferenceProfile?.shortbio || profile?.description || ''
83
+
const description = conferenceProfile?.description || profile?.description || ''
84
84
85
85
// Handle both blob refs and direct URLs
86
86
let avatar = ''
···
159
159
)}
160
160
</div>
161
161
162
-
{shortbio && (
163
-
<p class="mt-4 text-base whitespace-pre-wrap">{shortbio}</p>
162
+
{description && (
163
+
<p class="mt-4 text-base whitespace-pre-wrap">{description}</p>
164
164
)}
165
165
166
166
<div class="mt-4">
+2
-2
src/pages/profile/create.astro
+2
-2
src/pages/profile/create.astro
···
43
43
}
44
44
45
45
const displayName = existingProfile?.displayName || ''
46
-
const about = existingProfile?.about || ''
46
+
const description = existingProfile?.description || ''
47
47
---
48
48
49
49
<html lang="en" data-theme="dracula">
···
71
71
72
72
<ProfileForm
73
73
displayName={displayName}
74
-
about={about}
74
+
description={description}
75
75
submitLabel={existingProfile ? 'Update Profile' : 'Create Profile'}
76
76
/>
77
77