@streamplace/components 0.7.34 → 0.8.0

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 (64) hide show
  1. package/dist/components/content-metadata/content-metadata-form.js +404 -0
  2. package/dist/components/content-metadata/content-rights.js +78 -0
  3. package/dist/components/content-metadata/content-warnings.js +68 -0
  4. package/dist/components/content-metadata/index.js +11 -0
  5. package/dist/components/dashboard/header.js +16 -2
  6. package/dist/components/dashboard/problems.js +29 -28
  7. package/dist/components/mobile-player/player.js +4 -0
  8. package/dist/components/mobile-player/ui/report-modal.js +3 -2
  9. package/dist/components/mobile-player/ui/viewer-context-menu.js +44 -1
  10. package/dist/components/ui/button.js +9 -9
  11. package/dist/components/ui/checkbox.js +87 -0
  12. package/dist/components/ui/dialog.js +188 -83
  13. package/dist/components/ui/dropdown.js +15 -10
  14. package/dist/components/ui/icons.js +6 -0
  15. package/dist/components/ui/primitives/button.js +0 -7
  16. package/dist/components/ui/primitives/input.js +13 -1
  17. package/dist/components/ui/primitives/modal.js +2 -2
  18. package/dist/components/ui/select.js +89 -0
  19. package/dist/components/ui/textarea.js +23 -4
  20. package/dist/components/ui/toast.js +464 -114
  21. package/dist/components/ui/tooltip.js +103 -0
  22. package/dist/index.js +2 -0
  23. package/dist/lib/metadata-constants.js +157 -0
  24. package/dist/lib/theme/theme.js +5 -3
  25. package/dist/lib/theme/tokens.js +9 -0
  26. package/dist/streamplace-provider/index.js +14 -4
  27. package/dist/streamplace-store/content-metadata-actions.js +118 -0
  28. package/dist/streamplace-store/graph.js +195 -0
  29. package/dist/streamplace-store/streamplace-store.js +18 -5
  30. package/dist/streamplace-store/user.js +67 -7
  31. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  32. package/package.json +3 -3
  33. package/src/components/content-metadata/content-metadata-form.tsx +761 -0
  34. package/src/components/content-metadata/content-rights.tsx +104 -0
  35. package/src/components/content-metadata/content-warnings.tsx +100 -0
  36. package/src/components/content-metadata/index.tsx +18 -0
  37. package/src/components/dashboard/header.tsx +37 -3
  38. package/src/components/dashboard/index.tsx +1 -1
  39. package/src/components/dashboard/problems.tsx +57 -46
  40. package/src/components/mobile-player/player.tsx +5 -0
  41. package/src/components/mobile-player/ui/report-modal.tsx +13 -7
  42. package/src/components/mobile-player/ui/viewer-context-menu.tsx +100 -1
  43. package/src/components/ui/button.tsx +10 -13
  44. package/src/components/ui/checkbox.tsx +147 -0
  45. package/src/components/ui/dialog.tsx +319 -99
  46. package/src/components/ui/dropdown.tsx +27 -13
  47. package/src/components/ui/icons.tsx +14 -0
  48. package/src/components/ui/primitives/button.tsx +0 -7
  49. package/src/components/ui/primitives/input.tsx +19 -2
  50. package/src/components/ui/primitives/modal.tsx +4 -2
  51. package/src/components/ui/select.tsx +175 -0
  52. package/src/components/ui/textarea.tsx +47 -29
  53. package/src/components/ui/toast.tsx +785 -179
  54. package/src/components/ui/tooltip.tsx +131 -0
  55. package/src/index.tsx +3 -0
  56. package/src/lib/metadata-constants.ts +180 -0
  57. package/src/lib/theme/theme.tsx +10 -6
  58. package/src/lib/theme/tokens.ts +9 -0
  59. package/src/streamplace-provider/index.tsx +20 -2
  60. package/src/streamplace-store/content-metadata-actions.tsx +142 -0
  61. package/src/streamplace-store/graph.tsx +232 -0
  62. package/src/streamplace-store/streamplace-store.tsx +30 -4
  63. package/src/streamplace-store/user.tsx +71 -7
  64. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,131 @@
1
+ import { forwardRef, useState } from "react";
2
+ import { StyleSheet, View } from "react-native";
3
+ import { useTheme } from "../../lib/theme/theme";
4
+ import { Text } from "../ui/text";
5
+
6
+ export interface TooltipProps {
7
+ content: string;
8
+ children: React.ReactNode;
9
+ position?: "top" | "bottom" | "left" | "right";
10
+ style?: any;
11
+ }
12
+
13
+ export const Tooltip = forwardRef<any, TooltipProps>(
14
+ ({ content, children, position = "top", style }, ref) => {
15
+ const { theme } = useTheme();
16
+ const [isVisible, setIsVisible] = useState(false);
17
+ const styles = createStyles(theme, position);
18
+
19
+ const handleHoverIn = () => {
20
+ setIsVisible(true);
21
+ };
22
+
23
+ const handleHoverOut = () => {
24
+ setIsVisible(false);
25
+ };
26
+
27
+ return (
28
+ <View
29
+ ref={ref}
30
+ style={[styles.container, style]}
31
+ onPointerEnter={handleHoverIn}
32
+ onPointerLeave={handleHoverOut}
33
+ >
34
+ {children}
35
+ {isVisible && (
36
+ <View style={styles.tooltip}>
37
+ <Text style={styles.tooltipText}>{content}</Text>
38
+ </View>
39
+ )}
40
+ </View>
41
+ );
42
+ },
43
+ );
44
+
45
+ Tooltip.displayName = "Tooltip";
46
+
47
+ function createStyles(theme: any, position: string) {
48
+ const positionStyles = {
49
+ top: {
50
+ tooltip: {
51
+ bottom: "100%",
52
+ left: "50%",
53
+ transform: [{ translateX: -50 }],
54
+ marginBottom: theme.spacing[1],
55
+ },
56
+ arrow: {
57
+ top: "100%",
58
+ left: "50%",
59
+ transform: [{ translateX: -50 }],
60
+ borderTopColor: theme.colors.card,
61
+ },
62
+ },
63
+ bottom: {
64
+ tooltip: {
65
+ top: "100%",
66
+ left: "50%",
67
+ transform: [{ translateX: -50 }],
68
+ marginTop: theme.spacing[1],
69
+ },
70
+ arrow: {
71
+ bottom: "100%",
72
+ left: "50%",
73
+ transform: [{ translateX: -50 }],
74
+ borderBottomColor: theme.colors.card,
75
+ },
76
+ },
77
+ left: {
78
+ tooltip: {
79
+ right: "100%",
80
+ top: "50%",
81
+ transform: [{ translateY: -50 }],
82
+ marginRight: theme.spacing[1],
83
+ },
84
+ arrow: {
85
+ left: "100%",
86
+ top: "50%",
87
+ transform: [{ translateY: -50 }],
88
+ borderLeftColor: theme.colors.card,
89
+ },
90
+ },
91
+ right: {
92
+ tooltip: {
93
+ left: "100%",
94
+ top: "50%",
95
+ transform: [{ translateY: -50 }],
96
+ marginLeft: theme.spacing[1],
97
+ },
98
+ arrow: {
99
+ right: "100%",
100
+ top: "50%",
101
+ transform: [{ translateY: -50 }],
102
+ borderRightColor: theme.colors.card,
103
+ },
104
+ },
105
+ };
106
+
107
+ const currentPosition =
108
+ positionStyles[position as keyof typeof positionStyles];
109
+
110
+ return StyleSheet.create({
111
+ container: {
112
+ position: "relative",
113
+ },
114
+ tooltip: {
115
+ position: "absolute",
116
+ backgroundColor: theme.colors.card,
117
+ borderRadius: theme.borderRadius.md,
118
+ padding: theme.spacing[2],
119
+ maxWidth: 200,
120
+ ...theme.shadows.lg,
121
+ ...currentPosition.tooltip,
122
+ zIndex: 1000,
123
+ },
124
+ tooltipText: {
125
+ color: theme.colors.text,
126
+ fontSize: 12,
127
+ lineHeight: 16,
128
+ textAlign: "left",
129
+ },
130
+ });
131
+ }
package/src/index.tsx CHANGED
@@ -43,3 +43,6 @@ export * as Dashboard from "./components/dashboard";
43
43
  // Storage exports
44
44
  export { default as storage } from "./storage";
45
45
  export type { AQStorage } from "./storage/storage.shared";
46
+
47
+ // Content metadata components
48
+ export * from "./components/content-metadata";
@@ -0,0 +1,180 @@
1
+ import { schemas } from "streamplace";
2
+
3
+ // Content warnings derived from lexicon schema
4
+ export const CONTENT_WARNINGS = (() => {
5
+ // Find the content warnings schema
6
+ const contentWarningsSchema = schemas.find(
7
+ (schema) => schema.id === "place.stream.metadata.contentWarnings",
8
+ );
9
+ if (!contentWarningsSchema?.defs) {
10
+ throw new Error(
11
+ "Could not find place.stream.metadata.contentWarnings schema",
12
+ );
13
+ }
14
+
15
+ const contentWarningConstants = [
16
+ { constant: "place.stream.metadata.contentWarnings#death", label: "Death" },
17
+ {
18
+ constant: "place.stream.metadata.contentWarnings#drugUse",
19
+ label: "Drug Use",
20
+ },
21
+ {
22
+ constant: "place.stream.metadata.contentWarnings#fantasyViolence",
23
+ label: "Fantasy Violence",
24
+ },
25
+ {
26
+ constant: "place.stream.metadata.contentWarnings#flashingLights",
27
+ label: "Flashing Lights",
28
+ },
29
+ {
30
+ constant: "place.stream.metadata.contentWarnings#language",
31
+ label: "Language",
32
+ },
33
+ {
34
+ constant: "place.stream.metadata.contentWarnings#nudity",
35
+ label: "Nudity",
36
+ },
37
+ {
38
+ constant: "place.stream.metadata.contentWarnings#PII",
39
+ label: "Personally Identifiable Information",
40
+ },
41
+ {
42
+ constant: "place.stream.metadata.contentWarnings#sexuality",
43
+ label: "Sexuality",
44
+ },
45
+ {
46
+ constant: "place.stream.metadata.contentWarnings#suffering",
47
+ label: "Upsetting or Disturbing",
48
+ },
49
+ {
50
+ constant: "place.stream.metadata.contentWarnings#violence",
51
+ label: "Violence",
52
+ },
53
+ ];
54
+
55
+ return contentWarningConstants.map(({ constant, label }) => {
56
+ // Extract the key from the constant by splitting on '#'
57
+ const key = constant.split("#")[1];
58
+ const def = contentWarningsSchema.defs[key];
59
+ const description = def?.description || `Description for ${label}`;
60
+ return {
61
+ value: constant,
62
+ label: label,
63
+ description: description,
64
+ };
65
+ });
66
+ })();
67
+
68
+ // License options derived from lexicon schema
69
+ export const LICENSE_OPTIONS = (() => {
70
+ // Find the content rights schema
71
+ const contentRightsSchema = schemas.find(
72
+ (schema) => schema.id === "place.stream.metadata.contentRights",
73
+ );
74
+ if (!contentRightsSchema?.defs) {
75
+ throw new Error(
76
+ "Could not find place.stream.metadata.contentRights schema",
77
+ );
78
+ }
79
+
80
+ const licenseConstants = [
81
+ {
82
+ constant: "place.stream.metadata.contentRights#all-rights-reserved",
83
+ label: "All Rights Reserved",
84
+ },
85
+ {
86
+ constant: "place.stream.metadata.contentRights#cc0_1__0",
87
+ label: "CC0 (Public Domain) 1.0",
88
+ },
89
+ {
90
+ constant: "place.stream.metadata.contentRights#cc-by_4__0",
91
+ label: "CC BY 4.0",
92
+ },
93
+ {
94
+ constant: "place.stream.metadata.contentRights#cc-by-sa_4__0",
95
+ label: "CC BY-SA 4.0",
96
+ },
97
+ {
98
+ constant: "place.stream.metadata.contentRights#cc-by-nc_4__0",
99
+ label: "CC BY-NC 4.0",
100
+ },
101
+ {
102
+ constant: "place.stream.metadata.contentRights#cc-by-nc-sa_4__0",
103
+ label: "CC BY-NC-SA 4.0",
104
+ },
105
+ {
106
+ constant: "place.stream.metadata.contentRights#cc-by-nd_4__0",
107
+ label: "CC BY-ND 4.0",
108
+ },
109
+ {
110
+ constant: "place.stream.metadata.contentRights#cc-by-nc-nd_4__0",
111
+ label: "CC BY-NC-ND 4.0",
112
+ },
113
+ ];
114
+
115
+ const options = licenseConstants.map(({ constant, label }) => {
116
+ // Extract the key from the constant by splitting on '#'
117
+ const key = constant.split("#")[1];
118
+ const def = contentRightsSchema.defs[key];
119
+ const description = def?.description || `Description for ${label}`;
120
+ return {
121
+ value: constant,
122
+ label: label,
123
+ description: description,
124
+ };
125
+ });
126
+
127
+ // Add custom license option
128
+ options.push({
129
+ value: "custom",
130
+ label: "Custom License",
131
+ description:
132
+ "Custom license. Define your own terms for how others can use, adapt, or share your content.",
133
+ });
134
+
135
+ return options;
136
+ })();
137
+
138
+ // License URL labels for C2PA manifests
139
+ export const LICENSE_URL_LABELS: Record<string, string> = {
140
+ "http://creativecommons.org/publicdomain/zero/1.0/":
141
+ "CC0 - Public Domain 1.0",
142
+ "http://creativecommons.org/licenses/by/4.0/": "CC BY - Attribution 4.0",
143
+ "http://creativecommons.org/licenses/by-sa/4.0/":
144
+ "CC BY-SA - Attribution ShareAlike 4.0",
145
+ "http://creativecommons.org/licenses/by-nc/4.0/":
146
+ "CC BY-NC - Attribution NonCommercial 4.0",
147
+ "http://creativecommons.org/licenses/by-nc-sa/4.0/":
148
+ "CC BY-NC-SA - Attribution NonCommercial ShareAlike 4.0",
149
+ "http://creativecommons.org/licenses/by-nd/4.0/":
150
+ "CC BY-ND - Attribution NoDerivatives 4.0",
151
+ "http://creativecommons.org/licenses/by-nc-nd/4.0/":
152
+ "CC BY-NC-ND - Attribution NonCommercial NoDerivatives 4.0",
153
+ "All rights reserved": "All Rights Reserved",
154
+ } as const;
155
+
156
+ // C2PA warning labels for content warnings
157
+ export const C2PA_WARNING_LABELS: Record<string, string> = {
158
+ "cwarn:death": "Death",
159
+ "cwarn:drugUse": "Drug Use",
160
+ "cwarn:fantasyViolence": "Fantasy Violence",
161
+ "cwarn:flashingLights": "Flashing Lights",
162
+ "cwarn:language": "Language",
163
+ "cwarn:nudity": "Nudity",
164
+ "cwarn:PII": "Personally Identifiable Information",
165
+ "cwarn:sexuality": "Sexuality",
166
+ "cwarn:suffering": "Upsetting or Disturbing",
167
+ "cwarn:violence": "Violence",
168
+ // Also support lexicon constants for backward compatibility
169
+ "place.stream.metadata.contentWarnings#death": "Death",
170
+ "place.stream.metadata.contentWarnings#drugUse": "Drug Use",
171
+ "place.stream.metadata.contentWarnings#fantasyViolence": "Fantasy Violence",
172
+ "place.stream.metadata.contentWarnings#flashingLights": "Flashing Lights",
173
+ "place.stream.metadata.contentWarnings#language": "Language",
174
+ "place.stream.metadata.contentWarnings#nudity": "Nudity",
175
+ "place.stream.metadata.contentWarnings#PII":
176
+ "Personally Identifiable Information",
177
+ "place.stream.metadata.contentWarnings#sexuality": "Sexuality",
178
+ "place.stream.metadata.contentWarnings#suffering": "Upsetting or Disturbing",
179
+ "place.stream.metadata.contentWarnings#violence": "Violence",
180
+ } as const;
@@ -84,6 +84,10 @@ export interface Theme {
84
84
  warning: string;
85
85
  warningForeground: string;
86
86
 
87
+ // Info colors
88
+ info: string;
89
+ infoForeground: string;
90
+
87
91
  // Border and input colors
88
92
  border: string;
89
93
  input: string;
@@ -344,18 +348,18 @@ function generateThemeColorsFromPalette(
344
348
  accent: isDark ? palette[800] : palette[100],
345
349
  accentForeground: isDark ? palette[50] : palette[900],
346
350
 
347
- destructive:
348
- Platform.OS === "ios" ? colors.ios.systemRed : colors.destructive[500],
351
+ destructive: colors.destructive[700],
349
352
  destructiveForeground: colors.white,
350
353
 
351
- success:
352
- Platform.OS === "ios" ? colors.ios.systemGreen : colors.success[500],
354
+ success: colors.success[700],
353
355
  successForeground: colors.white,
354
356
 
355
- warning:
356
- Platform.OS === "ios" ? colors.ios.systemOrange : colors.warning[500],
357
+ warning: colors.warning[700],
357
358
  warningForeground: colors.white,
358
359
 
360
+ info: colors.blue[700],
361
+ infoForeground: isDark ? palette[50] : palette[900],
362
+
359
363
  border: isDark ? palette[500] + "30" : palette[200] + "30",
360
364
  input: isDark ? palette[800] : palette[200],
361
365
  ring: Platform.OS === "ios" ? colors.ios.systemBlue : colors.primary[500],
@@ -590,52 +590,61 @@ export const typography = {
590
590
  },
591
591
 
592
592
  // Universal typography scale
593
+ // Atkinson's center is weird so the marginBottom is there to correct it?
593
594
  universal: {
594
595
  xs: {
595
596
  fontSize: 12,
596
597
  lineHeight: 16,
598
+ marginBottom: -0.7,
597
599
  fontWeight: "400" as const,
598
600
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
599
601
  },
600
602
  sm: {
601
603
  fontSize: 14,
602
604
  lineHeight: 20,
605
+ marginBottom: -1,
603
606
  fontWeight: "400" as const,
604
607
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
605
608
  },
606
609
  base: {
607
610
  fontSize: 16,
608
611
  lineHeight: 24,
612
+ marginBottom: -1.2,
609
613
  fontWeight: "400" as const,
610
614
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
611
615
  },
612
616
  lg: {
613
617
  fontSize: 18,
614
618
  lineHeight: 28,
619
+ marginBottom: -1.5,
615
620
  fontWeight: "400" as const,
616
621
  fontFamily: "AtkinsonHyperlegibleNext-Regular",
617
622
  },
618
623
  xl: {
619
624
  fontSize: 20,
620
625
  lineHeight: 28,
626
+ marginBottom: -1.75,
621
627
  fontWeight: "500" as const,
622
628
  fontFamily: "AtkinsonHyperlegibleNext-Medium",
623
629
  },
624
630
  "2xl": {
625
631
  fontSize: 24,
626
632
  lineHeight: 32,
633
+ marginBottom: -2,
627
634
  fontWeight: "600" as const,
628
635
  fontFamily: "AtkinsonHyperlegibleNext-SemiBold",
629
636
  },
630
637
  "3xl": {
631
638
  fontSize: 30,
632
639
  lineHeight: 36,
640
+ marginBottom: -2.5,
633
641
  fontWeight: "700" as const,
634
642
  fontFamily: "AtkinsonHyperlegibleNext-Bold",
635
643
  },
636
644
  "4xl": {
637
645
  fontSize: 36,
638
646
  lineHeight: 40,
647
+ marginBottom: -3,
639
648
  fontWeight: "700" as const,
640
649
  fontFamily: "AtkinsonHyperlegibleNext-ExtraBold",
641
650
  },
@@ -1,5 +1,6 @@
1
1
  import { SessionManager } from "@atproto/api/dist/session-manager";
2
2
  import { useEffect, useRef } from "react";
3
+ import { useGetChatProfile } from "../streamplace-store";
3
4
  import { makeStreamplaceStore } from "../streamplace-store/streamplace-store";
4
5
  import { StreamplaceContext } from "./context";
5
6
  import Poller from "./poller";
@@ -13,7 +14,6 @@ export function StreamplaceProvider({
13
14
  url: string;
14
15
  oauthSession?: SessionManager | null;
15
16
  }) {
16
- console.log("session in provider is", oauthSession);
17
17
  // todo: handle url changes?
18
18
  const store = useRef(makeStreamplaceStore({ url })).current;
19
19
 
@@ -27,7 +27,25 @@ export function StreamplaceProvider({
27
27
 
28
28
  return (
29
29
  <StreamplaceContext.Provider value={{ store: store }}>
30
- <Poller>{children}</Poller>
30
+ <ChatProfileCreator oauthSession={oauthSession}>
31
+ <Poller>{children}</Poller>
32
+ </ChatProfileCreator>
31
33
  </StreamplaceContext.Provider>
32
34
  );
33
35
  }
36
+
37
+ export function ChatProfileCreator({
38
+ oauthSession,
39
+ children,
40
+ }: {
41
+ oauthSession?: SessionManager | null;
42
+ children: React.ReactNode;
43
+ }) {
44
+ const getChatProfile = useGetChatProfile();
45
+ useEffect(() => {
46
+ if (oauthSession) {
47
+ getChatProfile();
48
+ }
49
+ }, [oauthSession]);
50
+ return <>{children}</>;
51
+ }
@@ -0,0 +1,142 @@
1
+ import {
2
+ ContentMetadataResult,
3
+ useDID,
4
+ useSetContentMetadata,
5
+ } from "./streamplace-store";
6
+ import { usePDSAgent } from "./xrpc";
7
+
8
+ export const useSaveContentMetadata = () => {
9
+ const pdsAgent = usePDSAgent();
10
+ const did = useDID();
11
+ const setContentMetadata = useSetContentMetadata();
12
+
13
+ return async (params: {
14
+ contentWarnings?: string[];
15
+ distributionPolicy?: { deleteAfter?: number };
16
+ contentRights?: Record<string, any>;
17
+ rkey?: string;
18
+ }) => {
19
+ if (!pdsAgent || !did) {
20
+ throw new Error("No PDS agent or DID available");
21
+ }
22
+
23
+ const metadataRecord = {
24
+ $type: "place.stream.metadata.configuration",
25
+ createdAt: new Date().toISOString(),
26
+ ...(params.contentWarnings &&
27
+ params.contentWarnings.length > 0 && {
28
+ contentWarnings: { warnings: params.contentWarnings },
29
+ }),
30
+ ...(params.distributionPolicy &&
31
+ params.distributionPolicy.deleteAfter && {
32
+ distributionPolicy: params.distributionPolicy,
33
+ }),
34
+ ...(params.contentRights &&
35
+ Object.keys(params.contentRights).length > 0 && {
36
+ contentRights: params.contentRights,
37
+ }),
38
+ };
39
+
40
+ const rkey = params.rkey || "self";
41
+
42
+ try {
43
+ // Try to update existing record first
44
+ const result = await (pdsAgent as any).com.atproto.repo.putRecord({
45
+ repo: did,
46
+ collection: "place.stream.metadata.configuration",
47
+ rkey,
48
+ record: metadataRecord,
49
+ });
50
+
51
+ const contentMetadata: ContentMetadataResult = {
52
+ record: metadataRecord as any,
53
+ uri: result.data.uri,
54
+ cid: result.data.cid || "",
55
+ rkey,
56
+ };
57
+
58
+ setContentMetadata(contentMetadata);
59
+ return contentMetadata;
60
+ } catch (error) {
61
+ // If record doesn't exist, create it
62
+ if (
63
+ error instanceof Error &&
64
+ (error.message?.includes("not found") ||
65
+ error.message?.includes("RecordNotFound") ||
66
+ error.message?.includes("mst: not found") ||
67
+ (error as any)?.status === 404)
68
+ ) {
69
+ const createResult = await (
70
+ pdsAgent as any
71
+ ).com.atproto.repo.createRecord({
72
+ repo: did,
73
+ collection: "place.stream.metadata.configuration",
74
+ rkey,
75
+ record: metadataRecord,
76
+ });
77
+
78
+ const contentMetadata: ContentMetadataResult = {
79
+ record: metadataRecord as any,
80
+ uri: createResult.data.uri,
81
+ cid: createResult.data.cid || "",
82
+ rkey,
83
+ };
84
+
85
+ setContentMetadata(contentMetadata);
86
+ return contentMetadata;
87
+ }
88
+ throw error;
89
+ }
90
+ };
91
+ };
92
+
93
+ // Simple get function
94
+ export const useGetContentMetadata = () => {
95
+ const pdsAgent = usePDSAgent();
96
+ const did = useDID();
97
+ const setContentMetadata = useSetContentMetadata();
98
+
99
+ return async (params?: { userDid?: string; rkey?: string }) => {
100
+ if (!pdsAgent) {
101
+ throw new Error("No PDS agent available");
102
+ }
103
+
104
+ const targetDid = params?.userDid || did;
105
+ if (!targetDid) {
106
+ throw new Error("No DID provided or user not authenticated");
107
+ }
108
+
109
+ try {
110
+ const result = await (pdsAgent as any).com.atproto.repo.getRecord({
111
+ repo: targetDid,
112
+ collection: "place.stream.metadata.configuration",
113
+ rkey: params?.rkey || "self",
114
+ });
115
+
116
+ if (!result.success) {
117
+ throw new Error("Failed to get content metadata record");
118
+ }
119
+
120
+ const contentMetadata: ContentMetadataResult = {
121
+ record: result.data.value,
122
+ uri: result.data.uri,
123
+ cid: result.data.cid || "",
124
+ };
125
+
126
+ setContentMetadata(contentMetadata);
127
+ return contentMetadata;
128
+ } catch (error) {
129
+ // Handle record not found - this is normal for new users
130
+ if (
131
+ error instanceof Error &&
132
+ (error.message?.includes("not found") ||
133
+ error.message?.includes("RecordNotFound") ||
134
+ error.message?.includes("mst: not found") ||
135
+ (error as any)?.status === 404)
136
+ ) {
137
+ return null;
138
+ }
139
+ throw error;
140
+ }
141
+ };
142
+ };