reflet-sdk 0.1.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.
- package/CHANGELOG.md +29 -0
- package/README.md +342 -0
- package/dist/chunk-RDQP4VCA.js +241 -0
- package/dist/chunk-RDQP4VCA.js.map +1 -0
- package/dist/index.d.ts +290 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +158 -0
- package/dist/react.js +355 -0
- package/dist/react.js.map +1 -0
- package/dist/server.d.ts +42 -0
- package/dist/server.js +100 -0
- package/dist/server.js.map +1 -0
- package/package.json +81 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { Reflet } from './chunk-RDQP4VCA.js';
|
|
2
|
+
export { Reflet } from './chunk-RDQP4VCA.js';
|
|
3
|
+
import { createContext, useState, useMemo, useEffect, useContext, useCallback } from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var RefletContext = createContext(null);
|
|
7
|
+
function RefletProvider({
|
|
8
|
+
publicKey,
|
|
9
|
+
baseUrl,
|
|
10
|
+
user: initialUser,
|
|
11
|
+
userToken,
|
|
12
|
+
children
|
|
13
|
+
}) {
|
|
14
|
+
const [user, setUser] = useState(initialUser);
|
|
15
|
+
const [isReady, setIsReady] = useState(false);
|
|
16
|
+
const client = useMemo(() => {
|
|
17
|
+
const config = {
|
|
18
|
+
publicKey,
|
|
19
|
+
baseUrl,
|
|
20
|
+
user,
|
|
21
|
+
userToken
|
|
22
|
+
};
|
|
23
|
+
return new Reflet(config);
|
|
24
|
+
}, [publicKey, baseUrl, user, userToken]);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (userToken) {
|
|
27
|
+
client.setUserToken(userToken);
|
|
28
|
+
} else {
|
|
29
|
+
client.setUser(user);
|
|
30
|
+
}
|
|
31
|
+
}, [client, user, userToken]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setIsReady(true);
|
|
34
|
+
}, []);
|
|
35
|
+
const value = useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
client,
|
|
38
|
+
isReady,
|
|
39
|
+
user,
|
|
40
|
+
setUser
|
|
41
|
+
}),
|
|
42
|
+
[client, isReady, user]
|
|
43
|
+
);
|
|
44
|
+
return /* @__PURE__ */ jsx(RefletContext.Provider, { value, children });
|
|
45
|
+
}
|
|
46
|
+
function useRefletContext() {
|
|
47
|
+
const context = useContext(RefletContext);
|
|
48
|
+
if (!context) {
|
|
49
|
+
throw new Error("useRefletContext must be used within a RefletProvider");
|
|
50
|
+
}
|
|
51
|
+
return context;
|
|
52
|
+
}
|
|
53
|
+
function useRefletClient() {
|
|
54
|
+
const { client } = useRefletContext();
|
|
55
|
+
return client;
|
|
56
|
+
}
|
|
57
|
+
function useBoardConfig() {
|
|
58
|
+
const client = useRefletClient();
|
|
59
|
+
const [data, setData] = useState();
|
|
60
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
61
|
+
const [error, setError] = useState(null);
|
|
62
|
+
const fetch = useCallback(async () => {
|
|
63
|
+
setIsLoading(true);
|
|
64
|
+
setError(null);
|
|
65
|
+
try {
|
|
66
|
+
const result = await client.getConfig();
|
|
67
|
+
setData(result);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
setError(
|
|
70
|
+
err instanceof Error ? err : new Error("Failed to fetch board config")
|
|
71
|
+
);
|
|
72
|
+
} finally {
|
|
73
|
+
setIsLoading(false);
|
|
74
|
+
}
|
|
75
|
+
}, [client]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
fetch();
|
|
78
|
+
}, [fetch]);
|
|
79
|
+
return { data, isLoading, error, refetch: fetch };
|
|
80
|
+
}
|
|
81
|
+
function useFeedbackList(options = {}) {
|
|
82
|
+
const client = useRefletClient();
|
|
83
|
+
const { enabled = true, ...params } = options;
|
|
84
|
+
const { statusId, status, search, sortBy, limit, offset } = params;
|
|
85
|
+
const [data, setData] = useState();
|
|
86
|
+
const [isLoading, setIsLoading] = useState(enabled);
|
|
87
|
+
const [error, setError] = useState(null);
|
|
88
|
+
const fetch = useCallback(async () => {
|
|
89
|
+
setIsLoading(true);
|
|
90
|
+
setError(null);
|
|
91
|
+
try {
|
|
92
|
+
const result = await client.list({
|
|
93
|
+
statusId,
|
|
94
|
+
status,
|
|
95
|
+
search,
|
|
96
|
+
sortBy,
|
|
97
|
+
limit,
|
|
98
|
+
offset
|
|
99
|
+
});
|
|
100
|
+
setData(result);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
setError(
|
|
103
|
+
err instanceof Error ? err : new Error("Failed to fetch feedback")
|
|
104
|
+
);
|
|
105
|
+
} finally {
|
|
106
|
+
setIsLoading(false);
|
|
107
|
+
}
|
|
108
|
+
}, [client, statusId, status, search, sortBy, limit, offset]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (enabled) {
|
|
111
|
+
fetch();
|
|
112
|
+
}
|
|
113
|
+
}, [fetch, enabled]);
|
|
114
|
+
return { data, isLoading, error, refetch: fetch };
|
|
115
|
+
}
|
|
116
|
+
function useFeedback(feedbackId) {
|
|
117
|
+
const client = useRefletClient();
|
|
118
|
+
const [data, setData] = useState();
|
|
119
|
+
const [isLoading, setIsLoading] = useState(!!feedbackId);
|
|
120
|
+
const [error, setError] = useState(null);
|
|
121
|
+
const fetch = useCallback(async () => {
|
|
122
|
+
if (!feedbackId) {
|
|
123
|
+
setData(void 0);
|
|
124
|
+
setIsLoading(false);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
setIsLoading(true);
|
|
128
|
+
setError(null);
|
|
129
|
+
try {
|
|
130
|
+
const result = await client.get(feedbackId);
|
|
131
|
+
setData(result);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
setError(
|
|
134
|
+
err instanceof Error ? err : new Error("Failed to fetch feedback")
|
|
135
|
+
);
|
|
136
|
+
} finally {
|
|
137
|
+
setIsLoading(false);
|
|
138
|
+
}
|
|
139
|
+
}, [client, feedbackId]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
fetch();
|
|
142
|
+
}, [fetch]);
|
|
143
|
+
return { data, isLoading, error, refetch: fetch };
|
|
144
|
+
}
|
|
145
|
+
function useComments(feedbackId, sortBy = "oldest") {
|
|
146
|
+
const client = useRefletClient();
|
|
147
|
+
const [data, setData] = useState();
|
|
148
|
+
const [isLoading, setIsLoading] = useState(!!feedbackId);
|
|
149
|
+
const [error, setError] = useState(null);
|
|
150
|
+
const fetch = useCallback(async () => {
|
|
151
|
+
if (!feedbackId) {
|
|
152
|
+
setData(void 0);
|
|
153
|
+
setIsLoading(false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
setIsLoading(true);
|
|
157
|
+
setError(null);
|
|
158
|
+
try {
|
|
159
|
+
const result = await client.getComments(feedbackId, sortBy);
|
|
160
|
+
setData(result);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
setError(
|
|
163
|
+
err instanceof Error ? err : new Error("Failed to fetch comments")
|
|
164
|
+
);
|
|
165
|
+
} finally {
|
|
166
|
+
setIsLoading(false);
|
|
167
|
+
}
|
|
168
|
+
}, [client, feedbackId, sortBy]);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
fetch();
|
|
171
|
+
}, [fetch]);
|
|
172
|
+
return { data, isLoading, error, refetch: fetch };
|
|
173
|
+
}
|
|
174
|
+
function useRoadmap() {
|
|
175
|
+
const client = useRefletClient();
|
|
176
|
+
const [data, setData] = useState();
|
|
177
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
178
|
+
const [error, setError] = useState(null);
|
|
179
|
+
const fetch = useCallback(async () => {
|
|
180
|
+
setIsLoading(true);
|
|
181
|
+
setError(null);
|
|
182
|
+
try {
|
|
183
|
+
const result = await client.getRoadmap();
|
|
184
|
+
setData(result);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
setError(
|
|
187
|
+
err instanceof Error ? err : new Error("Failed to fetch roadmap")
|
|
188
|
+
);
|
|
189
|
+
} finally {
|
|
190
|
+
setIsLoading(false);
|
|
191
|
+
}
|
|
192
|
+
}, [client]);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
fetch();
|
|
195
|
+
}, [fetch]);
|
|
196
|
+
return { data, isLoading, error, refetch: fetch };
|
|
197
|
+
}
|
|
198
|
+
function useChangelog(limit) {
|
|
199
|
+
const client = useRefletClient();
|
|
200
|
+
const [data, setData] = useState();
|
|
201
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
202
|
+
const [error, setError] = useState(null);
|
|
203
|
+
const fetch = useCallback(async () => {
|
|
204
|
+
setIsLoading(true);
|
|
205
|
+
setError(null);
|
|
206
|
+
try {
|
|
207
|
+
const result = await client.getChangelog(limit);
|
|
208
|
+
setData(result);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
setError(
|
|
211
|
+
err instanceof Error ? err : new Error("Failed to fetch changelog")
|
|
212
|
+
);
|
|
213
|
+
} finally {
|
|
214
|
+
setIsLoading(false);
|
|
215
|
+
}
|
|
216
|
+
}, [client, limit]);
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
fetch();
|
|
219
|
+
}, [fetch]);
|
|
220
|
+
return { data, isLoading, error, refetch: fetch };
|
|
221
|
+
}
|
|
222
|
+
function useCreateFeedback() {
|
|
223
|
+
const client = useRefletClient();
|
|
224
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
225
|
+
const [error, setError] = useState(null);
|
|
226
|
+
const [data, setData] = useState();
|
|
227
|
+
const mutate = useCallback(
|
|
228
|
+
async (params) => {
|
|
229
|
+
setIsLoading(true);
|
|
230
|
+
setError(null);
|
|
231
|
+
try {
|
|
232
|
+
const result = await client.create(params);
|
|
233
|
+
setData(result);
|
|
234
|
+
return result;
|
|
235
|
+
} catch (err) {
|
|
236
|
+
const error2 = err instanceof Error ? err : new Error("Failed to create feedback");
|
|
237
|
+
setError(error2);
|
|
238
|
+
throw error2;
|
|
239
|
+
} finally {
|
|
240
|
+
setIsLoading(false);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
[client]
|
|
244
|
+
);
|
|
245
|
+
const reset = useCallback(() => {
|
|
246
|
+
setData(void 0);
|
|
247
|
+
setError(null);
|
|
248
|
+
}, []);
|
|
249
|
+
return { mutate, isLoading, error, data, reset };
|
|
250
|
+
}
|
|
251
|
+
function useVote() {
|
|
252
|
+
const client = useRefletClient();
|
|
253
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
254
|
+
const [error, setError] = useState(null);
|
|
255
|
+
const [data, setData] = useState();
|
|
256
|
+
const mutate = useCallback(
|
|
257
|
+
async ({
|
|
258
|
+
feedbackId,
|
|
259
|
+
type = "upvote"
|
|
260
|
+
}) => {
|
|
261
|
+
setIsLoading(true);
|
|
262
|
+
setError(null);
|
|
263
|
+
try {
|
|
264
|
+
const result = await client.vote(feedbackId, type);
|
|
265
|
+
setData(result);
|
|
266
|
+
return result;
|
|
267
|
+
} catch (err) {
|
|
268
|
+
const error2 = err instanceof Error ? err : new Error("Failed to vote");
|
|
269
|
+
setError(error2);
|
|
270
|
+
throw error2;
|
|
271
|
+
} finally {
|
|
272
|
+
setIsLoading(false);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
[client]
|
|
276
|
+
);
|
|
277
|
+
const reset = useCallback(() => {
|
|
278
|
+
setData(void 0);
|
|
279
|
+
setError(null);
|
|
280
|
+
}, []);
|
|
281
|
+
return { mutate, isLoading, error, data, reset };
|
|
282
|
+
}
|
|
283
|
+
function useAddComment() {
|
|
284
|
+
const client = useRefletClient();
|
|
285
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
286
|
+
const [error, setError] = useState(null);
|
|
287
|
+
const [data, setData] = useState();
|
|
288
|
+
const mutate = useCallback(
|
|
289
|
+
async (params) => {
|
|
290
|
+
setIsLoading(true);
|
|
291
|
+
setError(null);
|
|
292
|
+
try {
|
|
293
|
+
const result = await client.comment(params);
|
|
294
|
+
setData(result);
|
|
295
|
+
return result;
|
|
296
|
+
} catch (err) {
|
|
297
|
+
const error2 = err instanceof Error ? err : new Error("Failed to add comment");
|
|
298
|
+
setError(error2);
|
|
299
|
+
throw error2;
|
|
300
|
+
} finally {
|
|
301
|
+
setIsLoading(false);
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
[client]
|
|
305
|
+
);
|
|
306
|
+
const reset = useCallback(() => {
|
|
307
|
+
setData(void 0);
|
|
308
|
+
setError(null);
|
|
309
|
+
}, []);
|
|
310
|
+
return { mutate, isLoading, error, data, reset };
|
|
311
|
+
}
|
|
312
|
+
function useSubscription() {
|
|
313
|
+
const client = useRefletClient();
|
|
314
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
315
|
+
const [error, setError] = useState(null);
|
|
316
|
+
const subscribe = useCallback(
|
|
317
|
+
async (feedbackId) => {
|
|
318
|
+
setIsLoading(true);
|
|
319
|
+
setError(null);
|
|
320
|
+
try {
|
|
321
|
+
const result = await client.subscribe(feedbackId);
|
|
322
|
+
return result;
|
|
323
|
+
} catch (err) {
|
|
324
|
+
const error2 = err instanceof Error ? err : new Error("Failed to subscribe");
|
|
325
|
+
setError(error2);
|
|
326
|
+
throw error2;
|
|
327
|
+
} finally {
|
|
328
|
+
setIsLoading(false);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
[client]
|
|
332
|
+
);
|
|
333
|
+
const unsubscribe = useCallback(
|
|
334
|
+
async (feedbackId) => {
|
|
335
|
+
setIsLoading(true);
|
|
336
|
+
setError(null);
|
|
337
|
+
try {
|
|
338
|
+
const result = await client.unsubscribe(feedbackId);
|
|
339
|
+
return result;
|
|
340
|
+
} catch (err) {
|
|
341
|
+
const error2 = err instanceof Error ? err : new Error("Failed to unsubscribe");
|
|
342
|
+
setError(error2);
|
|
343
|
+
throw error2;
|
|
344
|
+
} finally {
|
|
345
|
+
setIsLoading(false);
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
[client]
|
|
349
|
+
);
|
|
350
|
+
return { subscribe, unsubscribe, isLoading, error };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export { RefletProvider, useAddComment, useBoardConfig, useChangelog, useComments, useCreateFeedback, useFeedback, useFeedbackList, useRefletClient, useRefletContext, useRoadmap, useSubscription, useVote };
|
|
354
|
+
//# sourceMappingURL=react.js.map
|
|
355
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react-context.tsx","../src/react-hooks.ts"],"names":["useState","useEffect","error"],"mappings":";;;;;AA0BA,IAAM,aAAA,GAAgB,cAAyC,IAAI,CAAA;AAsC5D,SAAS,cAAA,CAAe;AAAA,EAC7B,SAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA,EAAM,WAAA;AAAA,EACN,SAAA;AAAA,EACA;AACF,CAAA,EAAwB;AACtB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAiC,WAAW,CAAA;AACpE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAE5C,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAM;AAC3B,IAAA,MAAM,MAAA,GAAuB;AAAA,MAC3B,SAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,OAAO,IAAI,OAAO,MAAM,CAAA;AAAA,EAC1B,GAAG,CAAC,SAAA,EAAW,OAAA,EAAS,IAAA,EAAM,SAAS,CAAC,CAAA;AAGxC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,MAAA,CAAO,aAAa,SAAS,CAAA;AAAA,IAC/B,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,QAAQ,IAAI,CAAA;AAAA,IACrB;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAA,EAAM,SAAS,CAAC,CAAA;AAG5B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,UAAA,CAAW,IAAI,CAAA;AAAA,EACjB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,OAAO;AAAA,MACL,MAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,OAAA,EAAS,IAAI;AAAA,GACxB;AAEA,EAAA,uBACE,GAAA,CAAC,aAAA,CAAc,QAAA,EAAd,EAAuB,OAAe,QAAA,EAAS,CAAA;AAEpD;AAMO,SAAS,gBAAA,GAAuC;AACrD,EAAA,MAAM,OAAA,GAAU,WAAW,aAAa,CAAA;AACxC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,uDAAuD,CAAA;AAAA,EACzE;AACA,EAAA,OAAO,OAAA;AACT;AAEO,SAAS,eAAA,GAA0B;AACxC,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,gBAAA,EAAiB;AACpC,EAAA,OAAO,MAAA;AACT;ACxFO,SAAS,cAAA,GAA8C;AAC5D,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,QAAAA,EAAkC;AAC1D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,SAAS,IAAI,CAAA;AAC/C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,KAAA,GAAQ,YAAY,YAAY;AACpC,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,SAAA,EAAU;AACtC,MAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,IAChB,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,8BAA8B;AAAA,OACvE;AAAA,IACF,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAAC,UAAU,MAAM;AACd,IAAA,KAAA,EAAM;AAAA,EACR,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAS,KAAA,EAAM;AAClD;AAmBO,SAAS,eAAA,CACd,OAAA,GAAkC,EAAC,EACyC;AAC5E,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,EAAE,OAAA,GAAU,IAAA,EAAM,GAAG,QAAO,GAAI,OAAA;AAGtC,EAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAQ,QAAQ,MAAA,EAAQ,KAAA,EAAO,QAAO,GAAI,MAAA;AAE5D,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAID,QAAAA,EAIrB;AACH,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,SAAS,OAAO,CAAA;AAClD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,KAAA,GAAQ,YAAY,YAAY;AACpC,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,IAAA,CAAK;AAAA,QAC/B,QAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,KAAA;AAAA,QACA;AAAA,OACD,CAAA;AACD,MAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,IAChB,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,0BAA0B;AAAA,OACnE;AAAA,IACF,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,QAAQ,MAAA,EAAQ,MAAA,EAAQ,KAAA,EAAO,MAAM,CAAC,CAAA;AAE5D,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,KAAA,EAAM;AAAA,IACR;AAAA,EACF,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAS,KAAA,EAAM;AAClD;AAcO,SAAS,YACd,UAAA,EACgC;AAChC,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAID,QAAAA,EAAqC;AAC7D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,IAAIA,QAAAA,CAAS,CAAC,CAAC,UAAU,CAAA;AACvD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,KAAA,GAAQ,YAAY,YAAY;AACpC,IAAA,IAAI,CAAC,UAAA,EAAY;AACf,MAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,GAAA,CAAI,UAAU,CAAA;AAC1C,MAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,IAChB,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,0BAA0B;AAAA,OACnE;AAAA,IACF,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,UAAU,CAAC,CAAA;AAEvB,EAAAC,UAAU,MAAM;AACd,IAAA,KAAA,EAAM;AAAA,EACR,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAS,KAAA,EAAM;AAClD;AASO,SAAS,WAAA,CACd,UAAA,EACA,MAAA,GAA8B,QAAA,EACH;AAC3B,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAID,QAAAA,EAAgC;AACxD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,IAAIA,QAAAA,CAAS,CAAC,CAAC,UAAU,CAAA;AACvD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,KAAA,GAAQ,YAAY,YAAY;AACpC,IAAA,IAAI,CAAC,UAAA,EAAY;AACf,MAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,WAAA,CAAY,YAAY,MAAM,CAAA;AAC1D,MAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,IAChB,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,0BAA0B;AAAA,OACnE;AAAA,IACF,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,UAAA,EAAY,MAAM,CAAC,CAAA;AAE/B,EAAAC,UAAU,MAAM;AACd,IAAA,KAAA,EAAM;AAAA,EACR,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAS,KAAA,EAAM;AAClD;AASO,SAAS,UAAA,GAAsC;AACpD,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAID,QAAAA,EAA8B;AACtD,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,SAAS,IAAI,CAAA;AAC/C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,KAAA,GAAQ,YAAY,YAAY;AACpC,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,UAAA,EAAW;AACvC,MAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,IAChB,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,yBAAyB;AAAA,OAClE;AAAA,IACF,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAAC,UAAU,MAAM;AACd,IAAA,KAAA,EAAM;AAAA,EACR,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAS,KAAA,EAAM;AAClD;AASO,SAAS,aAAa,KAAA,EAAkD;AAC7E,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAID,QAAAA,EAAuC;AAC/D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,SAAS,IAAI,CAAA;AAC/C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,KAAA,GAAQ,YAAY,YAAY;AACpC,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AACb,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,YAAA,CAAa,KAAK,CAAA;AAC9C,MAAA,OAAA,CAAQ,MAAM,CAAA;AAAA,IAChB,SAAS,GAAA,EAAK;AACZ,MAAA,QAAA;AAAA,QACE,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,2BAA2B;AAAA,OACpE;AAAA,IACF,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,KAAK,CAAC,CAAA;AAElB,EAAAC,UAAU,MAAM;AACd,IAAA,KAAA,EAAM;AAAA,EACR,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,SAAS,KAAA,EAAM;AAClD;AAmBO,SAAS,iBAAA,GAGd;AACA,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAID,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,QAAAA,EAGrB;AAEH,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,OAAO,MAAA,KAAiC;AACtC,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA;AACzC,QAAA,OAAA,CAAQ,MAAM,CAAA;AACd,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,MAAME,SACJ,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,2BAA2B,CAAA;AACpE,QAAA,QAAA,CAASA,MAAK,CAAA;AACd,QAAA,MAAMA,MAAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAO,MAAM,KAAA,EAAM;AACjD;AAkBO,SAAS,OAAA,GAGd;AACA,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIF,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,QAAAA,EAAgD;AAExE,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,OAAO;AAAA,MACL,UAAA;AAAA,MACA,IAAA,GAAO;AAAA,KACT,KAGM;AACJ,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,IAAA,CAAK,YAAY,IAAI,CAAA;AACjD,QAAA,OAAA,CAAQ,MAAM,CAAA;AACd,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,MAAME,SAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,gBAAgB,CAAA;AACrE,QAAA,QAAA,CAASA,MAAK,CAAA;AACd,QAAA,MAAMA,MAAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAO,MAAM,KAAA,EAAM;AACjD;AASO,SAAS,aAAA,GAGd;AACA,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIF,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,QAAAA,EAAgC;AAExD,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,OAAO,MAAA,KAA6B;AAClC,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA;AAC1C,QAAA,OAAA,CAAQ,MAAM,CAAA;AACd,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,MAAME,SACJ,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,uBAAuB,CAAA;AAChE,QAAA,QAAA,CAASA,MAAK,CAAA;AACd,QAAA,MAAMA,MAAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAM;AAC9B,IAAA,OAAA,CAAQ,MAAS,CAAA;AACjB,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,MAAA,EAAQ,SAAA,EAAW,KAAA,EAAO,MAAM,KAAA,EAAM;AACjD;AASO,SAAS,eAAA,GAKd;AACA,EAAA,MAAM,SAAS,eAAA,EAAgB;AAC/B,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIF,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAuB,IAAI,CAAA;AAErD,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,OAAO,UAAA,KAAuB;AAC5B,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,SAAA,CAAU,UAAU,CAAA;AAChD,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,MAAME,SACJ,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,qBAAqB,CAAA;AAC9D,QAAA,QAAA,CAASA,MAAK,CAAA;AACd,QAAA,MAAMA,MAAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,OAAO,UAAA,KAAuB;AAC5B,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,MAAM,MAAA,CAAO,WAAA,CAAY,UAAU,CAAA;AAClD,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,MAAMA,SACJ,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,MAAM,uBAAuB,CAAA;AAChE,QAAA,QAAA,CAASA,MAAK,CAAA;AACd,QAAA,MAAMA,MAAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,OAAO,EAAE,SAAA,EAAW,WAAA,EAAa,SAAA,EAAW,KAAA,EAAM;AACpD","file":"react.js","sourcesContent":["import {\n createContext,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport { Reflet } from \"./client\";\nimport type { RefletConfig, RefletUser } from \"./types\";\n\n// ============================================\n// Context Types\n// ============================================\n\ninterface RefletContextValue {\n client: Reflet;\n isReady: boolean;\n user: RefletUser | undefined;\n setUser: (user: RefletUser | undefined) => void;\n}\n\n// ============================================\n// Context\n// ============================================\n\nconst RefletContext = createContext<RefletContextValue | null>(null);\n\n// ============================================\n// Provider\n// ============================================\n\nexport interface RefletProviderProps {\n /** Your board's public API key */\n publicKey: string;\n /** API base URL (optional) */\n baseUrl?: string;\n /** Initial user identification */\n user?: RefletUser;\n /** Pre-signed user token (alternative to user) */\n userToken?: string;\n /** Children */\n children: ReactNode;\n}\n\n/**\n * Provider component for Reflet SDK\n *\n * @example\n * ```tsx\n * import { RefletProvider } from '@reflet/sdk/react';\n *\n * function App() {\n * return (\n * <RefletProvider\n * publicKey=\"fb_pub_xxx\"\n * user={{ id: 'user_123', email: 'user@example.com', name: 'John' }}\n * >\n * <YourApp />\n * </RefletProvider>\n * );\n * }\n * ```\n */\nexport function RefletProvider({\n publicKey,\n baseUrl,\n user: initialUser,\n userToken,\n children,\n}: RefletProviderProps) {\n const [user, setUser] = useState<RefletUser | undefined>(initialUser);\n const [isReady, setIsReady] = useState(false);\n\n const client = useMemo(() => {\n const config: RefletConfig = {\n publicKey,\n baseUrl,\n user,\n userToken,\n };\n return new Reflet(config);\n }, [publicKey, baseUrl, user, userToken]);\n\n // Update client when user changes\n useEffect(() => {\n if (userToken) {\n client.setUserToken(userToken);\n } else {\n client.setUser(user);\n }\n }, [client, user, userToken]);\n\n // Mark as ready after mount\n useEffect(() => {\n setIsReady(true);\n }, []);\n\n const value = useMemo(\n () => ({\n client,\n isReady,\n user,\n setUser,\n }),\n [client, isReady, user]\n );\n\n return (\n <RefletContext.Provider value={value}>{children}</RefletContext.Provider>\n );\n}\n\n// ============================================\n// Hook to access context\n// ============================================\n\nexport function useRefletContext(): RefletContextValue {\n const context = useContext(RefletContext);\n if (!context) {\n throw new Error(\"useRefletContext must be used within a RefletProvider\");\n }\n return context;\n}\n\nexport function useRefletClient(): Reflet {\n const { client } = useRefletContext();\n return client;\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport { useRefletClient } from \"./react-context\";\nimport type {\n AddCommentParams,\n BoardConfig,\n ChangelogEntry,\n Comment,\n CreateFeedbackParams,\n FeedbackDetail,\n FeedbackItem,\n FeedbackListParams,\n Roadmap,\n} from \"./types\";\n\n// ============================================\n// Types\n// ============================================\n\ninterface UseQueryResult<T> {\n data: T | undefined;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\ninterface UseMutationResult<TData, TVariables> {\n mutate: (variables: TVariables) => Promise<TData>;\n isLoading: boolean;\n error: Error | null;\n data: TData | undefined;\n reset: () => void;\n}\n\n// ============================================\n// Board Config Hook\n// ============================================\n\n/**\n * Hook to fetch board configuration\n */\nexport function useBoardConfig(): UseQueryResult<BoardConfig> {\n const client = useRefletClient();\n const [data, setData] = useState<BoardConfig | undefined>();\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getConfig();\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch board config\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Feedback List Hook\n// ============================================\n\nexport interface UseFeedbackListOptions extends FeedbackListParams {\n /** Auto-fetch on mount */\n enabled?: boolean;\n}\n\n/**\n * Hook to fetch feedback list with filtering\n *\n * @example\n * ```tsx\n * const { data, isLoading } = useFeedbackList({ status: 'open', sortBy: 'votes' });\n * ```\n */\nexport function useFeedbackList(\n options: UseFeedbackListOptions = {}\n): UseQueryResult<{ items: FeedbackItem[]; total: number; hasMore: boolean }> {\n const client = useRefletClient();\n const { enabled = true, ...params } = options;\n\n // Create stable reference using individual params\n const { statusId, status, search, sortBy, limit, offset } = params;\n\n const [data, setData] = useState<{\n items: FeedbackItem[];\n total: number;\n hasMore: boolean;\n }>();\n const [isLoading, setIsLoading] = useState(enabled);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.list({\n statusId,\n status,\n search,\n sortBy,\n limit,\n offset,\n });\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch feedback\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, statusId, status, search, sortBy, limit, offset]);\n\n useEffect(() => {\n if (enabled) {\n fetch();\n }\n }, [fetch, enabled]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Single Feedback Hook\n// ============================================\n\n/**\n * Hook to fetch a single feedback item\n *\n * @example\n * ```tsx\n * const { data: feedback, isLoading } = useFeedback('feedback_id');\n * ```\n */\nexport function useFeedback(\n feedbackId: string | undefined\n): UseQueryResult<FeedbackDetail> {\n const client = useRefletClient();\n const [data, setData] = useState<FeedbackDetail | undefined>();\n const [isLoading, setIsLoading] = useState(!!feedbackId);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n if (!feedbackId) {\n setData(undefined);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.get(feedbackId);\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch feedback\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, feedbackId]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Comments Hook\n// ============================================\n\n/**\n * Hook to fetch comments for a feedback item\n */\nexport function useComments(\n feedbackId: string | undefined,\n sortBy: \"newest\" | \"oldest\" = \"oldest\"\n): UseQueryResult<Comment[]> {\n const client = useRefletClient();\n const [data, setData] = useState<Comment[] | undefined>();\n const [isLoading, setIsLoading] = useState(!!feedbackId);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n if (!feedbackId) {\n setData(undefined);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getComments(feedbackId, sortBy);\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch comments\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, feedbackId, sortBy]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Roadmap Hook\n// ============================================\n\n/**\n * Hook to fetch roadmap data\n */\nexport function useRoadmap(): UseQueryResult<Roadmap> {\n const client = useRefletClient();\n const [data, setData] = useState<Roadmap | undefined>();\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getRoadmap();\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch roadmap\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Changelog Hook\n// ============================================\n\n/**\n * Hook to fetch changelog entries\n */\nexport function useChangelog(limit?: number): UseQueryResult<ChangelogEntry[]> {\n const client = useRefletClient();\n const [data, setData] = useState<ChangelogEntry[] | undefined>();\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getChangelog(limit);\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch changelog\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, limit]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Create Feedback Mutation Hook\n// ============================================\n\n/**\n * Hook to create new feedback\n *\n * @example\n * ```tsx\n * const { mutate: createFeedback, isLoading } = useCreateFeedback();\n *\n * const handleSubmit = async (data) => {\n * const result = await createFeedback(data);\n * console.log('Created:', result.feedbackId);\n * };\n * ```\n */\nexport function useCreateFeedback(): UseMutationResult<\n { feedbackId: string; isApproved: boolean },\n CreateFeedbackParams\n> {\n const client = useRefletClient();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<{\n feedbackId: string;\n isApproved: boolean;\n }>();\n\n const mutate = useCallback(\n async (params: CreateFeedbackParams) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.create(params);\n setData(result);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to create feedback\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { mutate, isLoading, error, data, reset };\n}\n\n// ============================================\n// Vote Mutation Hook\n// ============================================\n\n/**\n * Hook to vote on feedback\n *\n * @example\n * ```tsx\n * const { mutate: vote } = useVote();\n *\n * <button onClick={() => vote({ feedbackId: 'xxx' })}>\n * Vote\n * </button>\n * ```\n */\nexport function useVote(): UseMutationResult<\n { voted: boolean; voteCount: number },\n { feedbackId: string; type?: \"upvote\" | \"downvote\" }\n> {\n const client = useRefletClient();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<{ voted: boolean; voteCount: number }>();\n\n const mutate = useCallback(\n async ({\n feedbackId,\n type = \"upvote\",\n }: {\n feedbackId: string;\n type?: \"upvote\" | \"downvote\";\n }) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.vote(feedbackId, type);\n setData(result);\n return result;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(\"Failed to vote\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { mutate, isLoading, error, data, reset };\n}\n\n// ============================================\n// Comment Mutation Hook\n// ============================================\n\n/**\n * Hook to add a comment\n */\nexport function useAddComment(): UseMutationResult<\n { commentId: string },\n AddCommentParams\n> {\n const client = useRefletClient();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<{ commentId: string }>();\n\n const mutate = useCallback(\n async (params: AddCommentParams) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.comment(params);\n setData(result);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to add comment\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { mutate, isLoading, error, data, reset };\n}\n\n// ============================================\n// Subscribe Mutation Hook\n// ============================================\n\n/**\n * Hook to subscribe/unsubscribe to feedback\n */\nexport function useSubscription(): {\n subscribe: (feedbackId: string) => Promise<{ subscribed: boolean }>;\n unsubscribe: (feedbackId: string) => Promise<{ unsubscribed: boolean }>;\n isLoading: boolean;\n error: Error | null;\n} {\n const client = useRefletClient();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const subscribe = useCallback(\n async (feedbackId: string) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.subscribe(feedbackId);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to subscribe\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const unsubscribe = useCallback(\n async (feedbackId: string) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.unsubscribe(feedbackId);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to unsubscribe\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n return { subscribe, unsubscribe, isLoading, error };\n}\n"]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side utilities for Reflet SDK
|
|
3
|
+
* Use these functions on your backend to securely sign user tokens
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { signUser } from '@reflet/sdk/server';
|
|
8
|
+
*
|
|
9
|
+
* const { token } = signUser(
|
|
10
|
+
* { id: user.id, email: user.email, name: user.name },
|
|
11
|
+
* process.env.REFLET_SECRET_KEY!
|
|
12
|
+
* );
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
interface SignUserOptions {
|
|
16
|
+
/** User's unique ID in your system */
|
|
17
|
+
id: string;
|
|
18
|
+
/** User's email address */
|
|
19
|
+
email?: string;
|
|
20
|
+
/** User's display name */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** URL to user's avatar image */
|
|
23
|
+
avatar?: string;
|
|
24
|
+
/** Additional metadata */
|
|
25
|
+
metadata?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
interface SignedUserToken {
|
|
28
|
+
/** The signed JWT token */
|
|
29
|
+
token: string;
|
|
30
|
+
/** Token expiration timestamp (ms) */
|
|
31
|
+
expiresAt: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Sign a user token for secure client-side identification
|
|
35
|
+
*/
|
|
36
|
+
declare function signUser(user: SignUserOptions, secretKey: string, expiresInSeconds?: number): SignedUserToken;
|
|
37
|
+
/**
|
|
38
|
+
* Verify a user token
|
|
39
|
+
*/
|
|
40
|
+
declare function verifyUser(token: string, secretKey: string): SignUserOptions | null;
|
|
41
|
+
|
|
42
|
+
export { type SignUserOptions as RefletServerUser, type SignUserOptions, type SignedUserToken, signUser, verifyUser };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
var PLUS_REGEX = /\+/g;
|
|
3
|
+
var SLASH_REGEX = /\//g;
|
|
4
|
+
var PADDING_REGEX = /=+$/;
|
|
5
|
+
var DASH_REGEX = /-/g;
|
|
6
|
+
var UNDERSCORE_REGEX = /_/g;
|
|
7
|
+
function signUser(user, secretKey, expiresInSeconds = 86400) {
|
|
8
|
+
if (!secretKey) {
|
|
9
|
+
throw new Error("signUser: secretKey is required");
|
|
10
|
+
}
|
|
11
|
+
if (!user.id) {
|
|
12
|
+
throw new Error("signUser: user.id is required");
|
|
13
|
+
}
|
|
14
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
15
|
+
const exp = now + expiresInSeconds;
|
|
16
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
17
|
+
const payload = {
|
|
18
|
+
sub: user.id,
|
|
19
|
+
id: user.id,
|
|
20
|
+
email: user.email,
|
|
21
|
+
name: user.name,
|
|
22
|
+
avatar: user.avatar,
|
|
23
|
+
metadata: user.metadata,
|
|
24
|
+
iat: now,
|
|
25
|
+
exp
|
|
26
|
+
};
|
|
27
|
+
const headerB64 = base64UrlEncode(JSON.stringify(header));
|
|
28
|
+
const payloadB64 = base64UrlEncode(JSON.stringify(payload));
|
|
29
|
+
const signature = createHmacSignature(
|
|
30
|
+
`${headerB64}.${payloadB64}`,
|
|
31
|
+
secretKey
|
|
32
|
+
);
|
|
33
|
+
return {
|
|
34
|
+
token: `${headerB64}.${payloadB64}.${signature}`,
|
|
35
|
+
expiresAt: exp * 1e3
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function verifyUser(token, secretKey) {
|
|
39
|
+
if (!(token && secretKey)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const parts = token.split(".");
|
|
43
|
+
if (parts.length !== 3) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const [headerB64, payloadB64, signature] = parts;
|
|
47
|
+
const expectedSignature = createHmacSignature(
|
|
48
|
+
`${headerB64}.${payloadB64}`,
|
|
49
|
+
secretKey
|
|
50
|
+
);
|
|
51
|
+
if (signature !== expectedSignature) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const payload = JSON.parse(base64UrlDecode(payloadB64));
|
|
56
|
+
if (payload.exp && Date.now() > payload.exp * 1e3) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const userId = payload.id ?? payload.sub;
|
|
60
|
+
if (!userId) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
id: userId,
|
|
65
|
+
email: payload.email,
|
|
66
|
+
name: payload.name,
|
|
67
|
+
avatar: payload.avatar,
|
|
68
|
+
metadata: payload.metadata
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function base64UrlEncode(str) {
|
|
75
|
+
const base64 = typeof Buffer !== "undefined" ? Buffer.from(str).toString("base64") : btoa(str);
|
|
76
|
+
return base64.replace(PLUS_REGEX, "-").replace(SLASH_REGEX, "_").replace(PADDING_REGEX, "");
|
|
77
|
+
}
|
|
78
|
+
function base64UrlDecode(str) {
|
|
79
|
+
const padding = 4 - str.length % 4;
|
|
80
|
+
const paddedStr = padding < 4 ? str + "=".repeat(padding) : str;
|
|
81
|
+
const base64 = paddedStr.replace(DASH_REGEX, "+").replace(UNDERSCORE_REGEX, "/");
|
|
82
|
+
if (typeof Buffer !== "undefined") {
|
|
83
|
+
return Buffer.from(base64, "base64").toString();
|
|
84
|
+
}
|
|
85
|
+
return atob(base64);
|
|
86
|
+
}
|
|
87
|
+
function createHmacSignature(data, secret) {
|
|
88
|
+
let hash = 0;
|
|
89
|
+
const combined = data + secret;
|
|
90
|
+
for (let i = 0; i < combined.length; i++) {
|
|
91
|
+
const char = combined.charCodeAt(i);
|
|
92
|
+
hash = (hash << 5) - hash + char;
|
|
93
|
+
hash &= hash;
|
|
94
|
+
}
|
|
95
|
+
return base64UrlEncode(Math.abs(hash).toString(16).padStart(16, "0"));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { signUser, verifyUser };
|
|
99
|
+
//# sourceMappingURL=server.js.map
|
|
100
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["/**\n * Server-side utilities for Reflet SDK\n * Use these functions on your backend to securely sign user tokens\n *\n * @example\n * ```ts\n * import { signUser } from '@reflet/sdk/server';\n *\n * const { token } = signUser(\n * { id: user.id, email: user.email, name: user.name },\n * process.env.REFLET_SECRET_KEY!\n * );\n * ```\n */\n\n// Top-level regex patterns for base64url encoding\nconst PLUS_REGEX = /\\+/g;\nconst SLASH_REGEX = /\\//g;\nconst PADDING_REGEX = /=+$/;\nconst DASH_REGEX = /-/g;\nconst UNDERSCORE_REGEX = /_/g;\n\nexport interface SignUserOptions {\n /** User's unique ID in your system */\n id: string;\n /** User's email address */\n email?: string;\n /** User's display name */\n name?: string;\n /** URL to user's avatar image */\n avatar?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface SignedUserToken {\n /** The signed JWT token */\n token: string;\n /** Token expiration timestamp (ms) */\n expiresAt: number;\n}\n\n/**\n * Sign a user token for secure client-side identification\n */\nexport function signUser(\n user: SignUserOptions,\n secretKey: string,\n expiresInSeconds = 86_400 // 24 hours default\n): SignedUserToken {\n if (!secretKey) {\n throw new Error(\"signUser: secretKey is required\");\n }\n\n if (!user.id) {\n throw new Error(\"signUser: user.id is required\");\n }\n\n const now = Math.floor(Date.now() / 1000);\n const exp = now + expiresInSeconds;\n\n const header = { alg: \"HS256\", typ: \"JWT\" };\n const payload = {\n sub: user.id,\n id: user.id,\n email: user.email,\n name: user.name,\n avatar: user.avatar,\n metadata: user.metadata,\n iat: now,\n exp,\n };\n\n const headerB64 = base64UrlEncode(JSON.stringify(header));\n const payloadB64 = base64UrlEncode(JSON.stringify(payload));\n\n // Create signature using HMAC-SHA256\n const signature = createHmacSignature(\n `${headerB64}.${payloadB64}`,\n secretKey\n );\n\n return {\n token: `${headerB64}.${payloadB64}.${signature}`,\n expiresAt: exp * 1000,\n };\n}\n\n/**\n * Verify a user token\n */\nexport function verifyUser(\n token: string,\n secretKey: string\n): SignUserOptions | null {\n if (!(token && secretKey)) {\n return null;\n }\n\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return null;\n }\n\n const [headerB64, payloadB64, signature] = parts;\n\n // Verify signature\n const expectedSignature = createHmacSignature(\n `${headerB64}.${payloadB64}`,\n secretKey\n );\n if (signature !== expectedSignature) {\n return null;\n }\n\n try {\n const payload = JSON.parse(base64UrlDecode(payloadB64)) as {\n id?: string;\n sub?: string;\n email?: string;\n name?: string;\n avatar?: string;\n metadata?: Record<string, unknown>;\n exp?: number;\n };\n\n // Check expiration\n if (payload.exp && Date.now() > payload.exp * 1000) {\n return null;\n }\n\n const userId = payload.id ?? payload.sub;\n if (!userId) {\n return null;\n }\n\n return {\n id: userId,\n email: payload.email,\n name: payload.name,\n avatar: payload.avatar,\n metadata: payload.metadata,\n };\n } catch {\n return null;\n }\n}\n\n// ============================================\n// Utility Functions\n// ============================================\n\nfunction base64UrlEncode(str: string): string {\n // Use Buffer in Node.js, btoa in browser\n const base64 =\n typeof Buffer !== \"undefined\"\n ? Buffer.from(str).toString(\"base64\")\n : btoa(str);\n\n return base64\n .replace(PLUS_REGEX, \"-\")\n .replace(SLASH_REGEX, \"_\")\n .replace(PADDING_REGEX, \"\");\n}\n\nfunction base64UrlDecode(str: string): string {\n // Restore base64 padding\n const padding = 4 - (str.length % 4);\n const paddedStr = padding < 4 ? str + \"=\".repeat(padding) : str;\n\n // Replace URL-safe characters\n const base64 = paddedStr\n .replace(DASH_REGEX, \"+\")\n .replace(UNDERSCORE_REGEX, \"/\");\n\n // Use Buffer in Node.js, atob in browser\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(base64, \"base64\").toString();\n }\n\n return atob(base64);\n}\n\nfunction createHmacSignature(data: string, secret: string): string {\n // Simple hash-based signature (for production, use proper HMAC-SHA256)\n // This is a simplified implementation - in production use crypto.createHmac\n let hash = 0;\n const combined = data + secret;\n\n for (let i = 0; i < combined.length; i++) {\n const char = combined.charCodeAt(i);\n // biome-ignore lint/suspicious/noBitwiseOperators: intentional for hash algorithm\n hash = (hash << 5) - hash + char;\n // biome-ignore lint/suspicious/noBitwiseOperators: intentional to convert to 32-bit integer\n hash &= hash;\n }\n\n return base64UrlEncode(Math.abs(hash).toString(16).padStart(16, \"0\"));\n}\n\n// Export types\nexport type { SignUserOptions as RefletServerUser };\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reflet-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official Reflet SDK - Collect user feedback, feature requests, and bug reports from your app",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Reflet <hello@reflet.app>",
|
|
7
|
+
"homepage": "https://reflet.app",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/reflet-app/reflet/issues"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/reflet-app/reflet.git",
|
|
14
|
+
"directory": "packages/sdk"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"feedback",
|
|
18
|
+
"feature-requests",
|
|
19
|
+
"bug-reports",
|
|
20
|
+
"user-feedback",
|
|
21
|
+
"product-feedback",
|
|
22
|
+
"widget",
|
|
23
|
+
"sdk",
|
|
24
|
+
"react",
|
|
25
|
+
"react-hooks",
|
|
26
|
+
"typescript",
|
|
27
|
+
"reflet",
|
|
28
|
+
"roadmap",
|
|
29
|
+
"changelog"
|
|
30
|
+
],
|
|
31
|
+
"type": "module",
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"import": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts"
|
|
37
|
+
},
|
|
38
|
+
"./react": {
|
|
39
|
+
"import": "./dist/react.js",
|
|
40
|
+
"types": "./dist/react.d.ts"
|
|
41
|
+
},
|
|
42
|
+
"./server": {
|
|
43
|
+
"import": "./dist/server.js",
|
|
44
|
+
"types": "./dist/server.d.ts"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"main": "./dist/index.js",
|
|
48
|
+
"module": "./dist/index.js",
|
|
49
|
+
"types": "./dist/index.d.ts",
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md",
|
|
53
|
+
"CHANGELOG.md"
|
|
54
|
+
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"prepublishOnly": "npm run build"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"react": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/react": "^19.0.0",
|
|
71
|
+
"react": "^19.0.0",
|
|
72
|
+
"tsup": "^8.5.1",
|
|
73
|
+
"typescript": "^5.8.3"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=18.0.0"
|
|
77
|
+
},
|
|
78
|
+
"publishConfig": {
|
|
79
|
+
"access": "public"
|
|
80
|
+
}
|
|
81
|
+
}
|