+9
-3
lexicons/profile.json
+9
-3
lexicons/profile.json
···
14
"maxGraphemes": 64,
15
"maxLength": 640
16
},
17
-
"description": {
18
"type": "string",
19
-
"description": "Free-form profile description text.",
20
"maxGraphemes": 256,
21
"maxLength": 2560
22
},
23
"avatar": {
24
"type": "blob",
25
-
"description": "Profile picture for conference attendee",
26
"accept": ["image/png", "image/jpeg"],
27
"maxSize": 1000000
28
},
···
14
"maxGraphemes": 64,
15
"maxLength": 640
16
},
17
+
"about": {
18
"type": "string",
19
+
"description": "Conference attendee extended about text",
20
+
"maxGraphemes": 1024,
21
+
"maxLength": 10240
22
+
},
23
+
"shortbio": {
24
+
"type": "string",
25
+
"description": "If you are a speaker, this is displayed as your bio. Use about for extended text",
26
"maxGraphemes": 256,
27
"maxLength": 2560
28
},
29
"avatar": {
30
"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",
32
"accept": ["image/png", "image/jpeg"],
33
"maxSize": 1000000
34
},
+33
-19
src/components/ProfileForm.astro
+33
-19
src/components/ProfileForm.astro
···
1
---
2
interface Props {
3
displayName?: string
4
-
description?: string
5
avatar?: string
6
banner?: string
7
submitLabel?: string
···
10
11
const {
12
displayName = '',
13
-
description = '',
14
avatar = '',
15
banner = '',
16
submitLabel = 'Create Profile',
···
26
>
27
<div class="form-control">
28
<label class="label">
29
-
<span class="label-text">Display Name</span>
30
<span class="label-text-alt">Max 64 characters</span>
31
</label>
32
<input
33
type="text"
34
name="displayName"
35
-
placeholder="Enter your display name"
36
class="input input-bordered w-full"
37
value={displayName}
38
maxlength="64"
···
42
43
<div class="form-control">
44
<label class="label">
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>
60
</label>
61
{avatar && (
62
<div class="avatar mb-2">
···
72
class="file-input file-input-bordered w-full"
73
/>
74
</div>
75
76
<div class="form-control">
77
<label class="label">
···
1
---
2
interface Props {
3
displayName?: string
4
+
about?: string
5
avatar?: string
6
banner?: string
7
submitLabel?: string
···
10
11
const {
12
displayName = '',
13
+
about = '',
14
avatar = '',
15
banner = '',
16
submitLabel = 'Create Profile',
···
26
>
27
<div class="form-control">
28
<label class="label">
29
+
<span class="label-text">Attendee Name</span>
30
<span class="label-text-alt">Max 64 characters</span>
31
</label>
32
<input
33
type="text"
34
name="displayName"
35
+
placeholder="This is the display name that will be shown as your attendee name."
36
class="input input-bordered w-full"
37
value={displayName}
38
maxlength="64"
···
42
43
<div class="form-control">
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>
47
</label>
48
{avatar && (
49
<div class="avatar mb-2">
···
59
class="file-input file-input-bordered w-full"
60
/>
61
</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
90
<div class="form-control">
91
<label class="label">
+27
-46
src/lexicon/lexicons.ts
+27
-46
src/lexicon/lexicons.ts
···
1
/**
2
* GENERATED CODE - DO NOT MODIFY
3
*/
4
-
import {
5
-
type LexiconDoc,
6
-
Lexicons,
7
-
ValidationError,
8
-
type ValidationResult,
9
-
} from '@atproto/lexicon'
10
import { type $Typed, is$typed, maybe$typed } from './util.js'
11
12
export const schemaDict = {
···
16
defs: {
17
label: {
18
type: 'object',
19
-
description:
20
-
'Metadata tag on an atproto resource (eg, repo or record).',
21
required: ['src', 'uri', 'val', 'cts'],
22
properties: {
23
ver: {
···
32
uri: {
33
type: 'string',
34
format: 'uri',
35
-
description:
36
-
'AT URI of the record, repository (account), or other resource that this label applies to.',
37
},
38
cid: {
39
type: 'string',
40
format: 'cid',
41
-
description:
42
-
"Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
43
},
44
val: {
45
type: 'string',
46
maxLength: 128,
47
-
description:
48
-
'The short string name of the value or type of this label.',
49
},
50
neg: {
51
type: 'boolean',
52
-
description:
53
-
'If true, this is a negation label, overwriting a previous label.',
54
},
55
cts: {
56
type: 'string',
···
60
exp: {
61
type: 'string',
62
format: 'datetime',
63
-
description:
64
-
'Timestamp at which this label expires (no longer applies).',
65
},
66
sig: {
67
type: 'bytes',
···
71
},
72
selfLabels: {
73
type: 'object',
74
-
description:
75
-
'Metadata tags on an atproto record, published by the author within the record.',
76
required: ['values'],
77
properties: {
78
values: {
···
94
val: {
95
type: 'string',
96
maxLength: 128,
97
-
description:
98
-
'The short string name of the value or type of this label.',
99
},
100
},
101
},
102
labelValueDefinition: {
103
type: 'object',
104
-
description:
105
-
'Declares a label value and its expected interpretations and behaviors.',
106
required: ['identifier', 'severity', 'blurs', 'locales'],
107
properties: {
108
identifier: {
···
132
},
133
adultOnly: {
134
type: 'boolean',
135
-
description:
136
-
'Does the user need to have adult content enabled in order to configure this label?',
137
},
138
locales: {
139
type: 'array',
···
146
},
147
labelValueDefinitionStrings: {
148
type: 'object',
149
-
description:
150
-
'Strings which describe the label in the UI, localized into a specific language.',
151
required: ['lang', 'name', 'description'],
152
properties: {
153
lang: {
154
type: 'string',
155
-
description:
156
-
'The code of the language these strings are written in.',
157
format: 'language',
158
},
159
name: {
···
164
},
165
description: {
166
type: 'string',
167
-
description:
168
-
'A longer description of what the label means and why it might be applied.',
169
maxGraphemes: 10000,
170
maxLength: 100000,
171
},
···
205
maxGraphemes: 64,
206
maxLength: 640,
207
},
208
-
description: {
209
type: 'string',
210
-
description: 'Free-form profile description text.',
211
maxGraphemes: 256,
212
maxLength: 2560,
213
},
214
avatar: {
215
type: 'blob',
216
-
description: 'Profile picture for conference attendee',
217
accept: ['image/png', 'image/jpeg'],
218
maxSize: 1000000,
219
},
220
banner: {
221
type: 'blob',
222
-
description:
223
-
'Larger horizontal image to display behind profile view.',
224
accept: ['image/png', 'image/jpeg'],
225
maxSize: 1000000,
226
},
···
275
hash: string,
276
requiredType?: false,
277
): ValidationResult<T>
278
-
export function validate(
279
-
v: unknown,
280
-
id: string,
281
-
hash: string,
282
-
requiredType?: boolean,
283
-
): ValidationResult {
284
return (requiredType ? is$typed : maybe$typed)(v, id, hash)
285
? lexicons.validate(`${id}#${hash}`, v)
286
: {
287
success: false,
288
-
error: new ValidationError(
289
-
`Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`,
290
-
),
291
}
292
}
293
···
1
/**
2
* GENERATED CODE - DO NOT MODIFY
3
*/
4
+
import { type LexiconDoc, Lexicons, ValidationError, type ValidationResult } from '@atproto/lexicon'
5
import { type $Typed, is$typed, maybe$typed } from './util.js'
6
7
export const schemaDict = {
···
11
defs: {
12
label: {
13
type: 'object',
14
+
description: 'Metadata tag on an atproto resource (eg, repo or record).',
15
required: ['src', 'uri', 'val', 'cts'],
16
properties: {
17
ver: {
···
26
uri: {
27
type: 'string',
28
format: 'uri',
29
+
description: 'AT URI of the record, repository (account), or other resource that this label applies to.',
30
},
31
cid: {
32
type: 'string',
33
format: 'cid',
34
+
description: "Optionally, CID specifying the specific version of 'uri' resource this label applies to.",
35
},
36
val: {
37
type: 'string',
38
maxLength: 128,
39
+
description: 'The short string name of the value or type of this label.',
40
},
41
neg: {
42
type: 'boolean',
43
+
description: 'If true, this is a negation label, overwriting a previous label.',
44
},
45
cts: {
46
type: 'string',
···
50
exp: {
51
type: 'string',
52
format: 'datetime',
53
+
description: 'Timestamp at which this label expires (no longer applies).',
54
},
55
sig: {
56
type: 'bytes',
···
60
},
61
selfLabels: {
62
type: 'object',
63
+
description: 'Metadata tags on an atproto record, published by the author within the record.',
64
required: ['values'],
65
properties: {
66
values: {
···
82
val: {
83
type: 'string',
84
maxLength: 128,
85
+
description: 'The short string name of the value or type of this label.',
86
},
87
},
88
},
89
labelValueDefinition: {
90
type: 'object',
91
+
description: 'Declares a label value and its expected interpretations and behaviors.',
92
required: ['identifier', 'severity', 'blurs', 'locales'],
93
properties: {
94
identifier: {
···
118
},
119
adultOnly: {
120
type: 'boolean',
121
+
description: 'Does the user need to have adult content enabled in order to configure this label?',
122
},
123
locales: {
124
type: 'array',
···
131
},
132
labelValueDefinitionStrings: {
133
type: 'object',
134
+
description: 'Strings which describe the label in the UI, localized into a specific language.',
135
required: ['lang', 'name', 'description'],
136
properties: {
137
lang: {
138
type: 'string',
139
+
description: 'The code of the language these strings are written in.',
140
format: 'language',
141
},
142
name: {
···
147
},
148
description: {
149
type: 'string',
150
+
description: 'A longer description of what the label means and why it might be applied.',
151
maxGraphemes: 10000,
152
maxLength: 100000,
153
},
···
187
maxGraphemes: 64,
188
maxLength: 640,
189
},
190
+
about: {
191
+
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',
199
maxGraphemes: 256,
200
maxLength: 2560,
201
},
202
avatar: {
203
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',
206
accept: ['image/png', 'image/jpeg'],
207
maxSize: 1000000,
208
},
209
banner: {
210
type: 'blob',
211
+
description: 'Larger horizontal image to display behind profile view.',
212
accept: ['image/png', 'image/jpeg'],
213
maxSize: 1000000,
214
},
···
263
hash: string,
264
requiredType?: false,
265
): ValidationResult<T>
266
+
export function validate(v: unknown, id: string, hash: string, requiredType?: boolean): ValidationResult {
267
return (requiredType ? is$typed : maybe$typed)(v, id, hash)
268
? lexicons.validate(`${id}#${hash}`, v)
269
: {
270
success: false,
271
+
error: new ValidationError(`Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`),
272
}
273
}
274
+4
-6
src/lexicon/types/org/atmosphereconf/profile.ts
+4
-6
src/lexicon/types/org/atmosphereconf/profile.ts
···
14
export interface Main {
15
$type: 'org.atmosphereconf.profile'
16
displayName?: string
17
/** Free-form profile description text. */
18
-
description?: string
19
/** Profile picture for conference attendee */
20
avatar?: BlobRef
21
/** Larger horizontal image to display behind profile view. */
···
35
return validate<Main & V>(v, id, hashMain, true)
36
}
37
38
-
export {
39
-
type Main as Record,
40
-
isMain as isRecord,
41
-
validateMain as validateRecord,
42
-
}
···
14
export interface Main {
15
$type: 'org.atmosphereconf.profile'
16
displayName?: string
17
+
/** Bio for speakers */
18
+
shortbio?: string
19
/** Free-form profile description text. */
20
+
about?: string
21
/** Profile picture for conference attendee */
22
avatar?: BlobRef
23
/** Larger horizontal image to display behind profile view. */
···
37
return validate<Main & V>(v, id, hashMain, true)
38
}
39
40
+
export { type Main as Record, isMain as isRecord, validateMain as validateRecord }
+2
-2
src/pages/index.astro
+2
-2
src/pages/index.astro
···
34
const displayName = profile?.displayName || agent?.assertDid || 'User'
35
const handle = profile?.handle || agent?.assertDid || ''
36
const avatar = profile?.avatar
37
-
const description = profile?.description
38
---
39
40
<html lang="en" data-theme="dracula">
···
64
)}
65
<p class="text-lg font-semibold">{displayName}</p>
66
<p class="text-sm opacity-70">{handle}</p>
67
-
{description && <p class="text-sm mt-2 opacity-80">{description}</p>}
68
</div>
69
<div class="space-y-2 w-full">
70
<a href={`/profile/${handle}`} class="btn btn-primary w-full">
···
34
const displayName = profile?.displayName || agent?.assertDid || 'User'
35
const handle = profile?.handle || agent?.assertDid || ''
36
const avatar = profile?.avatar
37
+
const shortbio = profile?.shortbio
38
---
39
40
<html lang="en" data-theme="dracula">
···
64
)}
65
<p class="text-lg font-semibold">{displayName}</p>
66
<p class="text-sm opacity-70">{handle}</p>
67
+
{shortbio && <p class="text-sm mt-2 opacity-80">{shortbio}</p>}
68
</div>
69
<div class="space-y-2 w-full">
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
}
81
82
const displayName = conferenceProfile?.displayName || profile?.displayName || handle
83
-
const description = conferenceProfile?.description || profile?.description || ''
84
85
// Handle both blob refs and direct URLs
86
let avatar = ''
···
159
)}
160
</div>
161
162
-
{description && (
163
-
<p class="mt-4 text-base whitespace-pre-wrap">{description}</p>
164
)}
165
166
<div class="mt-4">
···
80
}
81
82
const displayName = conferenceProfile?.displayName || profile?.displayName || handle
83
+
const shortbio = conferenceProfile?.shortbio || profile?.description || ''
84
85
// Handle both blob refs and direct URLs
86
let avatar = ''
···
159
)}
160
</div>
161
162
+
{shortbio && (
163
+
<p class="mt-4 text-base whitespace-pre-wrap">{shortbio}</p>
164
)}
165
166
<div class="mt-4">
+2
-2
src/pages/profile/create.astro
+2
-2
src/pages/profile/create.astro
···
43
}
44
45
const displayName = existingProfile?.displayName || ''
46
-
const description = existingProfile?.description || ''
47
---
48
49
<html lang="en" data-theme="dracula">
···
71
72
<ProfileForm
73
displayName={displayName}
74
-
description={description}
75
submitLabel={existingProfile ? 'Update Profile' : 'Create Profile'}
76
/>
77
···
43
}
44
45
const displayName = existingProfile?.displayName || ''
46
+
const about = existingProfile?.about || ''
47
---
48
49
<html lang="en" data-theme="dracula">
···
71
72
<ProfileForm
73
displayName={displayName}
74
+
about={about}
75
submitLabel={existingProfile ? 'Update Profile' : 'Create Profile'}
76
/>
77