@streamplace/components 0.9.1 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/components/chat/mod-view.d.ts.map +1 -1
  2. package/dist/components/chat/mod-view.js +8 -88
  3. package/dist/components/chat/mod-view.js.map +1 -1
  4. package/dist/components/chat/update-stream-title-dialog.d.ts +9 -0
  5. package/dist/components/chat/update-stream-title-dialog.d.ts.map +1 -0
  6. package/dist/components/chat/update-stream-title-dialog.js +74 -0
  7. package/dist/components/chat/update-stream-title-dialog.js.map +1 -0
  8. package/dist/components/content-metadata/content-metadata-form.d.ts.map +1 -1
  9. package/dist/components/content-metadata/content-metadata-form.js +7 -8
  10. package/dist/components/content-metadata/content-metadata-form.js.map +1 -1
  11. package/dist/components/mobile-player/ui/report-modal.d.ts.map +1 -1
  12. package/dist/components/mobile-player/ui/report-modal.js +1 -1
  13. package/dist/components/mobile-player/ui/report-modal.js.map +1 -1
  14. package/dist/components/mobile-player/ui/viewer-loading-overlay.d.ts.map +1 -1
  15. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +0 -1
  16. package/dist/components/mobile-player/ui/viewer-loading-overlay.js.map +1 -1
  17. package/dist/components/ui/admonition.d.ts +14 -0
  18. package/dist/components/ui/admonition.d.ts.map +1 -0
  19. package/dist/components/ui/admonition.js +117 -0
  20. package/dist/components/ui/admonition.js.map +1 -0
  21. package/dist/components/ui/button.d.ts +1 -0
  22. package/dist/components/ui/button.d.ts.map +1 -1
  23. package/dist/components/ui/button.js +2 -2
  24. package/dist/components/ui/button.js.map +1 -1
  25. package/dist/components/ui/index.d.ts +1 -0
  26. package/dist/components/ui/index.d.ts.map +1 -1
  27. package/dist/components/ui/index.js +1 -0
  28. package/dist/components/ui/index.js.map +1 -1
  29. package/dist/components/ui/primitives/button.d.ts +3 -2
  30. package/dist/components/ui/primitives/button.d.ts.map +1 -1
  31. package/dist/components/ui/primitives/button.js +20 -2
  32. package/dist/components/ui/primitives/button.js.map +1 -1
  33. package/dist/components/ui/resizeable.d.ts.map +1 -1
  34. package/dist/components/ui/resizeable.js +2 -1
  35. package/dist/components/ui/resizeable.js.map +1 -1
  36. package/dist/components/ui/text.d.ts +2 -1
  37. package/dist/components/ui/text.d.ts.map +1 -1
  38. package/dist/components/ui/text.js.map +1 -1
  39. package/dist/components/ui/textarea.d.ts.map +1 -1
  40. package/dist/components/ui/textarea.js +3 -1
  41. package/dist/components/ui/textarea.js.map +1 -1
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +2 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/lib/theme/atoms.d.ts +148 -148
  47. package/dist/lib/theme/tokens.d.ts +11 -11
  48. package/dist/lib/theme/tokens.js +11 -11
  49. package/dist/utils/did.d.ts +13 -0
  50. package/dist/utils/did.d.ts.map +1 -0
  51. package/dist/utils/did.js +43 -0
  52. package/dist/utils/did.js.map +1 -0
  53. package/locales/en-US/settings.ftl +2 -1
  54. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  55. package/package.json +2 -2
  56. package/src/components/chat/mod-view.tsx +2 -218
  57. package/src/components/chat/update-stream-title-dialog.tsx +169 -0
  58. package/src/components/content-metadata/content-metadata-form.tsx +37 -10
  59. package/src/components/mobile-player/ui/report-modal.tsx +2 -0
  60. package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +0 -1
  61. package/src/components/ui/admonition.tsx +177 -0
  62. package/src/components/ui/button.tsx +3 -0
  63. package/src/components/ui/index.ts +1 -0
  64. package/src/components/ui/primitives/button.tsx +37 -11
  65. package/src/components/ui/resizeable.tsx +2 -1
  66. package/src/components/ui/text.tsx +11 -1
  67. package/src/components/ui/textarea.tsx +3 -0
  68. package/src/index.tsx +2 -0
  69. package/src/lib/theme/tokens.ts +11 -11
  70. package/src/utils/did.ts +61 -0
@@ -329,17 +329,17 @@ export declare const colors: {
329
329
  readonly 950: "#052e16";
330
330
  };
331
331
  readonly warning: {
332
- readonly 50: "#fffbeb";
333
- readonly 100: "#fef3c7";
334
- readonly 200: "#fde68a";
335
- readonly 300: "#fcd34d";
336
- readonly 400: "#fbbf24";
337
- readonly 500: "#f59e0b";
338
- readonly 600: "#d97706";
339
- readonly 700: "#b45309";
340
- readonly 800: "#92400e";
341
- readonly 900: "#78350f";
342
- readonly 950: "#451a03";
332
+ readonly 50: "#fffaf0";
333
+ readonly 100: "#ffe6c7";
334
+ readonly 200: "#ffd99e";
335
+ readonly 300: "#ffcc75";
336
+ readonly 400: "#ffb94e";
337
+ readonly 500: "#ff9e1f";
338
+ readonly 600: "#e67e00";
339
+ readonly 700: "#cc6600";
340
+ readonly 800: "#998c00";
341
+ readonly 900: "#664200";
342
+ readonly 950: "#332900";
343
343
  };
344
344
  readonly ios: {
345
345
  readonly systemBlue: "#007AFF";
@@ -335,17 +335,17 @@ exports.colors = {
335
335
  950: "#052e16",
336
336
  },
337
337
  warning: {
338
- 50: "#fffbeb",
339
- 100: "#fef3c7",
340
- 200: "#fde68a",
341
- 300: "#fcd34d",
342
- 400: "#fbbf24",
343
- 500: "#f59e0b",
344
- 600: "#d97706",
345
- 700: "#b45309",
346
- 800: "#92400e",
347
- 900: "#78350f",
348
- 950: "#451a03",
338
+ 50: "#fffaf0",
339
+ 100: "#ffe6c7",
340
+ 200: "#ffd99e",
341
+ 300: "#ffcc75",
342
+ 400: "#ffb94e",
343
+ 500: "#ff9e1f",
344
+ 600: "#e67e00",
345
+ 700: "#cc6600",
346
+ 800: "#998c00",
347
+ 900: "#664200",
348
+ 950: "#332900",
349
349
  },
350
350
  // iOS system colors (adaptive)
351
351
  ios: {
@@ -0,0 +1,13 @@
1
+ export interface DIDDocument {
2
+ id: string;
3
+ service?: Array<{
4
+ id: string;
5
+ type?: string;
6
+ serviceEndpoint?: string;
7
+ }>;
8
+ [key: string]: any;
9
+ }
10
+ export declare function resolveDIDDocument(did: string): Promise<DIDDocument>;
11
+ export declare function getPDSServiceEndpoint(didDoc: DIDDocument): string;
12
+ export declare function getBlob(did: string, cid: string, didDoc?: DIDDocument): Promise<Blob>;
13
+ //# sourceMappingURL=did.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"did.d.ts","sourceRoot":"","sources":["../../src/utils/did.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,KAAK,CAAC;QACd,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC,CAAC;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAsB1E;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAQjE;AAED,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAYf"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveDIDDocument = resolveDIDDocument;
4
+ exports.getPDSServiceEndpoint = getPDSServiceEndpoint;
5
+ exports.getBlob = getBlob;
6
+ async function resolveDIDDocument(did) {
7
+ let didDocUrl;
8
+ if (did.startsWith("did:web:")) {
9
+ // For did:web, construct the URL directly
10
+ const domain = did.replace("did:web:", "").replace(/:/g, "/");
11
+ didDocUrl = `https://${domain}/.well-known/did.json`;
12
+ }
13
+ else if (did.startsWith("did:plc:")) {
14
+ // For did:plc, use plc.directory
15
+ didDocUrl = `https://plc.directory/${did}`;
16
+ }
17
+ else {
18
+ throw new Error(`Unsupported DID method: ${did}`);
19
+ }
20
+ const response = await fetch(didDocUrl);
21
+ if (!response.ok) {
22
+ throw new Error(`Failed to resolve DID document for ${did}: ${response.status}`);
23
+ }
24
+ return response.json();
25
+ }
26
+ function getPDSServiceEndpoint(didDoc) {
27
+ const pdsService = didDoc.service?.find((s) => s.id === "#atproto_pds");
28
+ if (!pdsService?.serviceEndpoint) {
29
+ throw new Error("No PDS service endpoint found in DID document");
30
+ }
31
+ return pdsService.serviceEndpoint;
32
+ }
33
+ async function getBlob(did, cid, didDoc) {
34
+ const doc = didDoc || (await resolveDIDDocument(did));
35
+ const pdsEndpoint = getPDSServiceEndpoint(doc);
36
+ const blobUrl = `${pdsEndpoint}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`;
37
+ const response = await fetch(blobUrl);
38
+ if (!response.ok) {
39
+ throw new Error(`Failed to fetch blob: ${response.status}`);
40
+ }
41
+ return response.blob();
42
+ }
43
+ //# sourceMappingURL=did.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"did.js","sourceRoot":"","sources":["../../src/utils/did.ts"],"names":[],"mappings":";;AAUA,gDAsBC;AAED,sDAQC;AAED,0BAgBC;AAlDM,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAClD,IAAI,SAAiB,CAAC;IAEtB,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,0CAA0C;QAC1C,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9D,SAAS,GAAG,WAAW,MAAM,uBAAuB,CAAC;IACvD,CAAC;SAAM,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACtC,iCAAiC;QACjC,SAAS,GAAG,yBAAyB,GAAG,EAAE,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC;IACxC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,sCAAsC,GAAG,KAAK,QAAQ,CAAC,MAAM,EAAE,CAChE,CAAC;IACJ,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,SAAgB,qBAAqB,CAAC,MAAmB;IACvD,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,cAAc,CAAC,CAAC;IAExE,IAAI,CAAC,UAAU,EAAE,eAAe,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,UAAU,CAAC,eAAe,CAAC;AACpC,CAAC;AAEM,KAAK,UAAU,OAAO,CAC3B,GAAW,EACX,GAAW,EACX,MAAoB;IAEpB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAG,GAAG,WAAW,sCAAsC,GAAG,QAAQ,GAAG,EAAE,CAAC;IAErF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC"}
@@ -121,7 +121,7 @@ inactive = Inactive
121
121
  active = Active
122
122
 
123
123
  ## Multistreaming
124
- multistreaming = Multistreaming
124
+ multistream = Multistreaming
125
125
  multistream-targets = Multistream Targets
126
126
  multistream-description = Automatically push your Streamplace livestreams to other streaming services like Twitch or YouTube.
127
127
  create-multistream-target = Create Multistream Target
@@ -168,6 +168,7 @@ need-setup-live-dashboard = Need to set up streaming first? Visit the live dashb
168
168
  no-languages-found = No languages found
169
169
 
170
170
  ## Branding Administration
171
+ branding = Branding
171
172
  branding-admin = Branding Administration
172
173
  branding-admin-description = Customize your Streamplace instance. Note that settings may take a few hours to propagate.
173
174
  branding-login-required = Please log in to manage branding
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamplace/components",
3
- "version": "0.9.1",
3
+ "version": "0.9.6",
4
4
  "description": "Streamplace React (Native) Components",
5
5
  "main": "dist/index.js",
6
6
  "types": "src/index.tsx",
@@ -80,5 +80,5 @@
80
80
  "i18n:watch": "nodemon --watch 'locales/**/*.ftl' --exec 'node scripts/compile-translations.js'",
81
81
  "i18n:extract": "i18next-cli extract && node scripts/migrate-i18n.js"
82
82
  },
83
- "gitHead": "d56378fdeeb66a2312af250a7dd62ac47f118b1b"
83
+ "gitHead": "5ea7ad20a8cbf83a0f554ba1348972cc34d9b518"
84
84
  }
@@ -5,7 +5,6 @@ import { usePlayerStore } from "../../player-store";
5
5
  import {
6
6
  useCreateBlockRecord,
7
7
  useCreateHideChatRecord,
8
- useUpdateLivestreamRecord,
9
8
  } from "../../streamplace-store/block";
10
9
  import {
11
10
  ModerationPermissions,
@@ -17,24 +16,19 @@ import { Linking } from "react-native";
17
16
  import { ChatMessageViewHydrated } from "streamplace";
18
17
  import {
19
18
  useDeleteChatMessage,
20
- useLivestream,
21
19
  useLivestreamStore,
22
20
  } from "../../livestream-store";
23
21
  import { useStreamplaceStore } from "../../streamplace-store";
24
22
  import { formatHandle, formatHandleWithAt } from "../../utils/format-handle";
25
23
  import {
26
24
  atoms,
27
- Button,
28
- DialogFooter,
29
25
  DropdownMenu,
30
26
  DropdownMenuGroup,
31
27
  DropdownMenuItem,
32
28
  DropdownMenuTrigger,
33
29
  layout,
34
- ResponsiveDialog,
35
30
  ResponsiveDropdownMenuContent,
36
31
  Text,
37
- Textarea,
38
32
  useToast,
39
33
  View,
40
34
  } from "../ui";
@@ -61,10 +55,6 @@ export const ModView = forwardRef<ModViewRef, ModViewProps>(() => {
61
55
  let [messageRemoved, setMessageRemoved] = useState(false);
62
56
  let { createBlock, isLoading: isBlockLoading } = useCreateBlockRecord();
63
57
  let { createHideChat, isLoading: isHideLoading } = useCreateHideChatRecord();
64
- let { updateLivestream, isLoading: isUpdateTitleLoading } =
65
- useUpdateLivestreamRecord();
66
- const livestream = useLivestream();
67
- const [showUpdateTitleDialog, setShowUpdateTitleDialog] = useState(false);
68
58
 
69
59
  const setReportModalOpen = usePlayerStore((x) => x.setReportModalOpen);
70
60
  const setReportSubject = usePlayerStore((x) => x.setReportSubject);
@@ -95,17 +85,6 @@ export const ModView = forwardRef<ModViewRef, ModViewProps>(() => {
95
85
  }
96
86
  }, [message]);
97
87
 
98
- // Early return AFTER all hooks have been called
99
- if (!agent?.did) {
100
- return <></>;
101
- }
102
-
103
- // Can show moderation actions if user can hide, ban, or manage livestream
104
- const canModerate =
105
- modPermissions.canHide ||
106
- modPermissions.canBan ||
107
- modPermissions.canManageLivestream;
108
-
109
88
  // Check if any moderation actions are actually available for this message
110
89
  // This must match the individual action checks inside the DropdownMenuGroup
111
90
  const hasAvailableActions = !!(
@@ -146,9 +125,6 @@ export const ModView = forwardRef<ModViewRef, ModViewProps>(() => {
146
125
  createHideChat={createHideChat}
147
126
  createBlock={createBlock}
148
127
  toast={toast}
149
- setShowUpdateTitleDialog={setShowUpdateTitleDialog}
150
- isUpdateTitleLoading={isUpdateTitleLoading}
151
- livestream={livestream}
152
128
  setReportModalOpen={setReportModalOpen}
153
129
  setReportSubject={setReportSubject}
154
130
  deleteChatMessage={deleteChatMessage}
@@ -156,17 +132,6 @@ export const ModView = forwardRef<ModViewRef, ModViewProps>(() => {
156
132
  )}
157
133
  </ResponsiveDropdownMenuContent>
158
134
  </DropdownMenu>
159
-
160
- {/* Update Stream Title Dialog - rendered outside dropdown */}
161
- {showUpdateTitleDialog && (
162
- <UpdateStreamTitleDialog
163
- livestream={livestream}
164
- streamerDID={streamerDID}
165
- updateLivestream={updateLivestream}
166
- isLoading={isUpdateTitleLoading}
167
- onClose={() => setShowUpdateTitleDialog(false)}
168
- />
169
- )}
170
135
  </>
171
136
  );
172
137
  });
@@ -184,9 +149,6 @@ interface ModViewContentProps {
184
149
  createHideChat: (uri: string, streamerDID?: string) => Promise<any>;
185
150
  createBlock: (did: string, streamerDID?: string) => Promise<any>;
186
151
  toast: ReturnType<typeof useToast>;
187
- setShowUpdateTitleDialog: (show: boolean) => void;
188
- isUpdateTitleLoading: boolean;
189
- livestream: any;
190
152
  setReportModalOpen: (open: boolean) => void;
191
153
  setReportSubject: (subject: any) => void;
192
154
  deleteChatMessage: (uri: string) => Promise<any>;
@@ -205,9 +167,6 @@ function ModViewContent({
205
167
  createHideChat,
206
168
  createBlock,
207
169
  toast,
208
- setShowUpdateTitleDialog,
209
- isUpdateTitleLoading,
210
- livestream,
211
170
  setReportModalOpen,
212
171
  setReportSubject,
213
172
  deleteChatMessage,
@@ -301,23 +260,6 @@ function ModViewContent({
301
260
  </DropdownMenuGroup>
302
261
  )}
303
262
 
304
- {modPermissions.canManageLivestream && (
305
- <DropdownMenuGroup key="stream-actions" title={`Stream actions`}>
306
- <DropdownMenuItem
307
- onPress={() => {
308
- setShowUpdateTitleDialog(true);
309
- }}
310
- disabled={isUpdateTitleLoading || !livestream}
311
- >
312
- <Text
313
- color={isUpdateTitleLoading || !livestream ? "muted" : "primary"}
314
- >
315
- {isUpdateTitleLoading ? "Updating..." : "Update stream title"}
316
- </Text>
317
- </DropdownMenuItem>
318
- </DropdownMenuGroup>
319
- )}
320
-
321
263
  <DropdownMenuGroup key="user-actions" title={`User actions`}>
322
264
  <DropdownMenuItem
323
265
  onPress={() => {
@@ -328,14 +270,14 @@ function ModViewContent({
328
270
  >
329
271
  <Text color="primary">View user on {BSKY_FRONTEND_DOMAIN}</Text>
330
272
  </DropdownMenuItem>
331
- {message.author.did === agent?.did && (
273
+ {agent?.did && message.author.did === agent.did && (
332
274
  <DeleteButton
333
275
  message={message}
334
276
  deleteChatMessage={deleteChatMessage}
335
277
  onOpenChange={onOpenChange}
336
278
  />
337
279
  )}
338
- {message.author.did !== agent?.did && (
280
+ {(!agent?.did || message.author.did !== agent.did) && (
339
281
  <ReportButton
340
282
  message={message}
341
283
  setReportModalOpen={setReportModalOpen}
@@ -429,161 +371,3 @@ export function ReportButton({
429
371
  </DropdownMenuItem>
430
372
  );
431
373
  }
432
-
433
- interface UpdateStreamTitleDialogProps {
434
- livestream: any;
435
- streamerDID?: string;
436
- updateLivestream: (
437
- livestreamUri: string,
438
- title: string,
439
- streamerDID?: string,
440
- ) => Promise<any>;
441
- isLoading: boolean;
442
- onClose: () => void;
443
- }
444
-
445
- function UpdateStreamTitleDialog({
446
- livestream,
447
- streamerDID,
448
- updateLivestream,
449
- isLoading,
450
- onClose,
451
- }: UpdateStreamTitleDialogProps) {
452
- const [title, setTitle] = useState(livestream?.record?.title || "");
453
- const [error, setError] = useState<string | null>(null);
454
- const toast = useToast();
455
-
456
- useEffect(() => {
457
- if (livestream?.record?.title) {
458
- setTitle(livestream.record.title);
459
- }
460
- }, [livestream?.record?.title]);
461
-
462
- const handleUpdate = async () => {
463
- setError(null);
464
-
465
- if (!title.trim()) {
466
- setError("Please enter a stream title");
467
- return;
468
- }
469
-
470
- if (!livestream?.uri) {
471
- setError("No livestream found");
472
- return;
473
- }
474
-
475
- try {
476
- await updateLivestream(livestream.uri, title.trim(), streamerDID);
477
- toast.show(
478
- "Stream title updated",
479
- "The stream title has been successfully updated.",
480
- { duration: 3 },
481
- );
482
- onClose();
483
- } catch (err) {
484
- setError(
485
- err instanceof Error ? err.message : "Failed to update stream title",
486
- );
487
- }
488
- };
489
-
490
- return (
491
- <ResponsiveDialog
492
- open={true}
493
- onOpenChange={(open) => {
494
- if (!open) {
495
- onClose();
496
- setError(null);
497
- setTitle(livestream?.record?.title || "");
498
- }
499
- }}
500
- title="Update Stream Title"
501
- description="Update the title of the livestream."
502
- size="md"
503
- dismissible={false}
504
- >
505
- <View style={[{ padding: 16, paddingBottom: 0 }]}>
506
- <View style={[{ marginBottom: 16 }]}>
507
- <Text
508
- style={[
509
- { color: atoms.colors.gray[300], fontSize: 13, marginBottom: 8 },
510
- ]}
511
- >
512
- Stream Title
513
- </Text>
514
- <Textarea
515
- value={title}
516
- onChangeText={(text) => {
517
- setTitle(text);
518
- setError(null);
519
- }}
520
- placeholder="Enter stream title..."
521
- maxLength={140}
522
- multiline
523
- style={[
524
- {
525
- padding: 12,
526
- borderRadius: 8,
527
- backgroundColor: atoms.colors.neutral[800],
528
- color: atoms.colors.white,
529
- borderWidth: 1,
530
- borderColor: atoms.colors.neutral[600],
531
- minHeight: 100,
532
- fontSize: 16,
533
- },
534
- ]}
535
- />
536
- <Text
537
- style={[
538
- { color: atoms.colors.gray[400], fontSize: 12, marginTop: 4 },
539
- ]}
540
- >
541
- {title.length}/140 characters
542
- </Text>
543
- </View>
544
-
545
- {error && (
546
- <View
547
- style={[
548
- {
549
- backgroundColor: atoms.colors.red[900],
550
- padding: 12,
551
- borderRadius: 8,
552
- borderWidth: 1,
553
- borderColor: atoms.colors.red[700],
554
- marginBottom: 16,
555
- },
556
- ]}
557
- >
558
- <Text style={[{ color: atoms.colors.red[400], fontSize: 13 }]}>
559
- {error}
560
- </Text>
561
- </View>
562
- )}
563
- </View>
564
-
565
- <DialogFooter>
566
- <Button
567
- width="min"
568
- variant="secondary"
569
- onPress={() => {
570
- onClose();
571
- setError(null);
572
- setTitle(livestream?.record?.title || "");
573
- }}
574
- disabled={isLoading}
575
- >
576
- <Text>Cancel</Text>
577
- </Button>
578
- <Button
579
- variant="primary"
580
- width="min"
581
- onPress={handleUpdate}
582
- disabled={isLoading || !title.trim()}
583
- >
584
- <Text>{isLoading ? "Updating..." : "Update Title"}</Text>
585
- </Button>
586
- </DialogFooter>
587
- </ResponsiveDialog>
588
- );
589
- }
@@ -0,0 +1,169 @@
1
+ import { useEffect, useState } from "react";
2
+ import {
3
+ atoms,
4
+ Button,
5
+ DialogFooter,
6
+ ResponsiveDialog,
7
+ Text,
8
+ Textarea,
9
+ useToast,
10
+ View,
11
+ } from "../ui";
12
+
13
+ export interface UpdateStreamTitleDialogProps {
14
+ livestream: any;
15
+ streamerDID?: string;
16
+ updateLivestream: (
17
+ livestreamUri: string,
18
+ title: string,
19
+ streamerDID?: string,
20
+ ) => Promise<any>;
21
+ isLoading: boolean;
22
+ onClose: () => void;
23
+ }
24
+
25
+ export function UpdateStreamTitleDialog({
26
+ livestream,
27
+ streamerDID,
28
+ updateLivestream,
29
+ isLoading,
30
+ onClose,
31
+ }: UpdateStreamTitleDialogProps) {
32
+ const [title, setTitle] = useState(livestream?.record?.title || "");
33
+ const [error, setError] = useState<string | null>(null);
34
+ const toast = useToast();
35
+
36
+ useEffect(() => {
37
+ if (livestream?.record?.title) {
38
+ setTitle(livestream.record.title);
39
+ }
40
+ }, [livestream?.record?.title]);
41
+
42
+ const handleUpdate = async () => {
43
+ setError(null);
44
+
45
+ if (!title.trim()) {
46
+ setError("Please enter a stream title");
47
+ return;
48
+ }
49
+
50
+ if (!livestream?.uri) {
51
+ setError("No livestream found");
52
+ return;
53
+ }
54
+
55
+ try {
56
+ await updateLivestream(livestream.uri, title.trim(), streamerDID);
57
+ toast.show(
58
+ "Stream title updated",
59
+ "The stream title has been successfully updated.",
60
+ { duration: 3 },
61
+ );
62
+ onClose();
63
+ } catch (err) {
64
+ setError(
65
+ err instanceof Error ? err.message : "Failed to update stream title",
66
+ );
67
+ }
68
+ };
69
+
70
+ return (
71
+ <ResponsiveDialog
72
+ open={true}
73
+ onOpenChange={(open) => {
74
+ if (!open) {
75
+ onClose();
76
+ setError(null);
77
+ setTitle(livestream?.record?.title || "");
78
+ }
79
+ }}
80
+ title="Update Stream Title"
81
+ description="Update the title of the livestream."
82
+ size="md"
83
+ dismissible={false}
84
+ >
85
+ <View style={[{ padding: 16, paddingBottom: 0 }]}>
86
+ <View style={[{ marginBottom: 16 }]}>
87
+ <Text
88
+ style={[
89
+ { color: atoms.colors.gray[300], fontSize: 13, marginBottom: 8 },
90
+ ]}
91
+ >
92
+ Stream Title
93
+ </Text>
94
+ <Textarea
95
+ value={title}
96
+ onChangeText={(text) => {
97
+ setTitle(text);
98
+ setError(null);
99
+ }}
100
+ placeholder="Enter stream title..."
101
+ maxLength={140}
102
+ multiline
103
+ style={[
104
+ {
105
+ padding: 12,
106
+ borderRadius: 8,
107
+ backgroundColor: atoms.colors.neutral[800],
108
+ color: atoms.colors.white,
109
+ borderWidth: 1,
110
+ borderColor: atoms.colors.neutral[600],
111
+ minHeight: 100,
112
+ fontSize: 16,
113
+ },
114
+ ]}
115
+ />
116
+ <Text
117
+ style={[
118
+ { color: atoms.colors.gray[400], fontSize: 12, marginTop: 4 },
119
+ ]}
120
+ >
121
+ {title.length}/140 characters
122
+ </Text>
123
+ </View>
124
+
125
+ {error && (
126
+ <View
127
+ style={[
128
+ {
129
+ backgroundColor: atoms.colors.red[900],
130
+ padding: 12,
131
+ borderRadius: 8,
132
+ borderWidth: 1,
133
+ borderColor: atoms.colors.red[700],
134
+ marginBottom: 16,
135
+ },
136
+ ]}
137
+ >
138
+ <Text style={[{ color: atoms.colors.red[400], fontSize: 13 }]}>
139
+ {error}
140
+ </Text>
141
+ </View>
142
+ )}
143
+ </View>
144
+
145
+ <DialogFooter>
146
+ <Button
147
+ width="min"
148
+ variant="secondary"
149
+ onPress={() => {
150
+ onClose();
151
+ setError(null);
152
+ setTitle(livestream?.record?.title || "");
153
+ }}
154
+ disabled={isLoading}
155
+ >
156
+ <Text>Cancel</Text>
157
+ </Button>
158
+ <Button
159
+ variant="primary"
160
+ width="min"
161
+ onPress={handleUpdate}
162
+ disabled={isLoading || !title.trim()}
163
+ >
164
+ <Text>{isLoading ? "Updating..." : "Update Title"}</Text>
165
+ </Button>
166
+ </DialogFooter>
167
+ </ResponsiveDialog>
168
+ );
169
+ }