+1
-1
frontend-v2/schema.graphql
+1
-1
frontend-v2/schema.graphql
+8
-7
frontend-v2/server/profile-init.ts
+8
-7
frontend-v2/server/profile-init.ts
···
18
export async function initializeUserProfile(
19
userDid: string,
20
userHandle: string,
21
-
tokens: TokenInfo
22
): Promise<void> {
23
if (!API_URL || !SLICE_URI) {
24
console.error("Missing API_URL or VITE_SLICE_URI environment variables");
···
26
}
27
28
try {
29
-
const graphqlUrl = `${API_URL}/graphql?slice=${encodeURIComponent(SLICE_URI)}`;
30
const authHeader = `${tokens.tokenType} ${tokens.accessToken}`;
31
32
// 1. Check if profile already exists
···
132
});
133
134
if (!bskyResponse.ok) {
135
-
throw new Error(`Fetch Bluesky profile failed: ${bskyResponse.statusText}`);
136
}
137
138
const bskyData = await bskyResponse.json();
···
160
) {
161
// Reconstruct blob format for AT Protocol
162
profileInput.avatar = {
163
-
$type: "blob",
164
-
ref: {
165
-
$link: bskyProfile.avatar.ref,
166
-
},
167
mimeType: bskyProfile.avatar.mimeType,
168
size: bskyProfile.avatar.size,
169
};
···
18
export async function initializeUserProfile(
19
userDid: string,
20
userHandle: string,
21
+
tokens: TokenInfo,
22
): Promise<void> {
23
if (!API_URL || !SLICE_URI) {
24
console.error("Missing API_URL or VITE_SLICE_URI environment variables");
···
26
}
27
28
try {
29
+
const graphqlUrl = `${API_URL}/graphql?slice=${
30
+
encodeURIComponent(SLICE_URI)
31
+
}`;
32
const authHeader = `${tokens.tokenType} ${tokens.accessToken}`;
33
34
// 1. Check if profile already exists
···
134
});
135
136
if (!bskyResponse.ok) {
137
+
throw new Error(
138
+
`Fetch Bluesky profile failed: ${bskyResponse.statusText}`,
139
+
);
140
}
141
142
const bskyData = await bskyResponse.json();
···
164
) {
165
// Reconstruct blob format for AT Protocol
166
profileInput.avatar = {
167
+
ref: bskyProfile.avatar.ref,
168
mimeType: bskyProfile.avatar.mimeType,
169
size: bskyProfile.avatar.size,
170
};
+35
-6
frontend-v2/src/__generated__/ProfileSettingsUploadBlobMutation.graphql.ts
+35
-6
frontend-v2/src/__generated__/ProfileSettingsUploadBlobMutation.graphql.ts
···
1
/**
2
-
* @generated SignedSource<<a2334c7e93bb6d5b4748df1211a418ae>>
3
* @lightSyntaxTransform
4
* @nogrep
5
*/
···
15
};
16
export type ProfileSettingsUploadBlobMutation$data = {
17
readonly uploadBlob: {
18
-
readonly blob: any;
19
};
20
};
21
export type ProfileSettingsUploadBlobMutation = {
···
59
{
60
"alias": null,
61
"args": null,
62
-
"kind": "ScalarField",
63
"name": "blob",
64
"storageKey": null
65
}
66
],
···
85
"selections": (v1/*: any*/)
86
},
87
"params": {
88
-
"cacheID": "3a4a6b19d2898f14635b098941614cab",
89
"id": null,
90
"metadata": {},
91
"name": "ProfileSettingsUploadBlobMutation",
92
"operationKind": "mutation",
93
-
"text": "mutation ProfileSettingsUploadBlobMutation(\n $data: String!\n $mimeType: String!\n) {\n uploadBlob(data: $data, mimeType: $mimeType) {\n blob\n }\n}\n"
94
}
95
};
96
})();
97
98
-
(node as any).hash = "76da65b07a282ed7f2dee12b4cac82d6";
99
100
export default node;
···
1
/**
2
+
* @generated SignedSource<<728b9a3525f975b6c58a5cdcd323f89e>>
3
* @lightSyntaxTransform
4
* @nogrep
5
*/
···
15
};
16
export type ProfileSettingsUploadBlobMutation$data = {
17
readonly uploadBlob: {
18
+
readonly blob: {
19
+
readonly mimeType: string;
20
+
readonly ref: string;
21
+
readonly size: number;
22
+
};
23
};
24
};
25
export type ProfileSettingsUploadBlobMutation = {
···
63
{
64
"alias": null,
65
"args": null,
66
+
"concreteType": "Blob",
67
+
"kind": "LinkedField",
68
"name": "blob",
69
+
"plural": false,
70
+
"selections": [
71
+
{
72
+
"alias": null,
73
+
"args": null,
74
+
"kind": "ScalarField",
75
+
"name": "ref",
76
+
"storageKey": null
77
+
},
78
+
{
79
+
"alias": null,
80
+
"args": null,
81
+
"kind": "ScalarField",
82
+
"name": "mimeType",
83
+
"storageKey": null
84
+
},
85
+
{
86
+
"alias": null,
87
+
"args": null,
88
+
"kind": "ScalarField",
89
+
"name": "size",
90
+
"storageKey": null
91
+
}
92
+
],
93
"storageKey": null
94
}
95
],
···
114
"selections": (v1/*: any*/)
115
},
116
"params": {
117
+
"cacheID": "afd8db2ee7590308e81afc0b0e5c86dd",
118
"id": null,
119
"metadata": {},
120
"name": "ProfileSettingsUploadBlobMutation",
121
"operationKind": "mutation",
122
+
"text": "mutation ProfileSettingsUploadBlobMutation(\n $data: String!\n $mimeType: String!\n) {\n uploadBlob(data: $data, mimeType: $mimeType) {\n blob {\n ref\n mimeType\n size\n }\n }\n}\n"
123
}
124
};
125
})();
126
127
+
(node as any).hash = "74a3a8bf43181cd62d2e81c45be384e5";
128
129
export default node;
+18
-13
frontend-v2/src/pages/ProfileSettings.tsx
+18
-13
frontend-v2/src/pages/ProfileSettings.tsx
···
1
-
import { useParams, Link } from "react-router-dom";
2
import { useState } from "react";
3
import { graphql, useLazyLoadQuery, useMutation } from "react-relay";
4
import type { ProfileSettingsQuery } from "../__generated__/ProfileSettingsQuery.graphql.ts";
···
44
where: {
45
actorHandle: { eq: handle },
46
},
47
-
}
48
);
49
50
const profile = data.networkSlicesActorProfiles.edges[0]?.node;
···
59
graphql`
60
mutation ProfileSettingsUploadBlobMutation($data: String!, $mimeType: String!) {
61
uploadBlob(data: $data, mimeType: $mimeType) {
62
-
blob
63
}
64
}
65
-
`
66
);
67
68
const [commitUpdateProfile, isUpdatingProfile] = useMutation(
···
80
}
81
}
82
}
83
-
`
84
);
85
86
const [commitCreateProfile, isCreatingProfile] = useMutation(
···
98
}
99
}
100
}
101
-
`
102
);
103
104
// Helper to convert File to base64
···
108
reader.onload = () => {
109
const arrayBuffer = reader.result as ArrayBuffer;
110
const bytes = new Uint8Array(arrayBuffer);
111
-
const binary = Array.from(bytes).map(b => String.fromCharCode(b)).join('');
112
resolve(btoa(binary));
113
};
114
reader.onerror = reject;
···
129
// Upload new avatar
130
const base64Data = await fileToBase64(avatarFile);
131
132
-
const uploadResult = await new Promise<{ uploadBlob: { blob: unknown } }>((resolve, reject) => {
133
commitUploadBlob({
134
variables: {
135
data: base64Data,
136
mimeType: avatarFile.type,
137
},
138
-
onCompleted: (data) => resolve(data as { uploadBlob: { blob: unknown } }),
139
onError: (error) => reject(error),
140
});
141
});
···
144
} else if (profile?.avatar) {
145
// Keep existing avatar - reconstruct blob with $type field for AT Protocol
146
avatarBlob = {
147
-
$type: "blob",
148
-
ref: {
149
-
$link: profile.avatar.ref,
150
-
},
151
mimeType: profile.avatar.mimeType,
152
size: profile.avatar.size,
153
};
···
1
+
import { Link, useParams } from "react-router-dom";
2
import { useState } from "react";
3
import { graphql, useLazyLoadQuery, useMutation } from "react-relay";
4
import type { ProfileSettingsQuery } from "../__generated__/ProfileSettingsQuery.graphql.ts";
···
44
where: {
45
actorHandle: { eq: handle },
46
},
47
+
},
48
);
49
50
const profile = data.networkSlicesActorProfiles.edges[0]?.node;
···
59
graphql`
60
mutation ProfileSettingsUploadBlobMutation($data: String!, $mimeType: String!) {
61
uploadBlob(data: $data, mimeType: $mimeType) {
62
+
blob {
63
+
ref
64
+
mimeType
65
+
size
66
+
}
67
}
68
}
69
+
`,
70
);
71
72
const [commitUpdateProfile, isUpdatingProfile] = useMutation(
···
84
}
85
}
86
}
87
+
`,
88
);
89
90
const [commitCreateProfile, isCreatingProfile] = useMutation(
···
102
}
103
}
104
}
105
+
`,
106
);
107
108
// Helper to convert File to base64
···
112
reader.onload = () => {
113
const arrayBuffer = reader.result as ArrayBuffer;
114
const bytes = new Uint8Array(arrayBuffer);
115
+
const binary = Array.from(bytes).map((b) => String.fromCharCode(b))
116
+
.join("");
117
resolve(btoa(binary));
118
};
119
reader.onerror = reject;
···
134
// Upload new avatar
135
const base64Data = await fileToBase64(avatarFile);
136
137
+
const uploadResult = await new Promise<
138
+
{ uploadBlob: { blob: unknown } }
139
+
>((resolve, reject) => {
140
commitUploadBlob({
141
variables: {
142
data: base64Data,
143
mimeType: avatarFile.type,
144
},
145
+
onCompleted: (data) =>
146
+
resolve(data as { uploadBlob: { blob: unknown } }),
147
onError: (error) => reject(error),
148
});
149
});
···
152
} else if (profile?.avatar) {
153
// Keep existing avatar - reconstruct blob with $type field for AT Protocol
154
avatarBlob = {
155
+
ref: profile.avatar.ref,
156
mimeType: profile.avatar.mimeType,
157
size: profile.avatar.size,
158
};