@feelflow/ffid-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/README.md +270 -0
- package/dist/chunk-A63MX52D.js +861 -0
- package/dist/chunk-YCMQXJOS.cjs +872 -0
- package/dist/components/index.cjs +22 -0
- package/dist/components/index.d.cts +3 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +1 -0
- package/dist/index-CtBBLbTn.d.cts +322 -0
- package/dist/index-CtBBLbTn.d.ts +322 -0
- package/dist/index.cjs +68 -0
- package/dist/index.d.cts +181 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +31 -0
- package/dist/legal/index.cjs +180 -0
- package/dist/legal/index.d.cts +332 -0
- package/dist/legal/index.d.ts +332 -0
- package/dist/legal/index.js +176 -0
- package/package.json +84 -0
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/constants.ts
|
|
7
|
+
var DEFAULT_API_BASE_URL = "https://id.feelflow.co.jp";
|
|
8
|
+
|
|
9
|
+
// src/client/ffid-client.ts
|
|
10
|
+
var NO_CONTENT_STATUS = 204;
|
|
11
|
+
var SESSION_ENDPOINT = "/api/v1/auth/session";
|
|
12
|
+
var LOGOUT_ENDPOINT = "/api/v1/auth/signout";
|
|
13
|
+
var SDK_LOG_PREFIX = "[FFID SDK]";
|
|
14
|
+
var noopLogger = {
|
|
15
|
+
debug: () => {
|
|
16
|
+
},
|
|
17
|
+
info: () => {
|
|
18
|
+
},
|
|
19
|
+
warn: () => {
|
|
20
|
+
},
|
|
21
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
22
|
+
};
|
|
23
|
+
var consoleLogger = {
|
|
24
|
+
debug: (...args) => console.debug(SDK_LOG_PREFIX, ...args),
|
|
25
|
+
info: (...args) => console.info(SDK_LOG_PREFIX, ...args),
|
|
26
|
+
warn: (...args) => console.warn(SDK_LOG_PREFIX, ...args),
|
|
27
|
+
error: (...args) => console.error(SDK_LOG_PREFIX, ...args)
|
|
28
|
+
};
|
|
29
|
+
var FFID_ERROR_CODES = {
|
|
30
|
+
/** Network request failed (fetch threw) */
|
|
31
|
+
NETWORK_ERROR: "NETWORK_ERROR",
|
|
32
|
+
/** Server returned non-JSON response (e.g., HTML error page) */
|
|
33
|
+
PARSE_ERROR: "PARSE_ERROR",
|
|
34
|
+
/** Server returned error without structured error body */
|
|
35
|
+
UNKNOWN_ERROR: "UNKNOWN_ERROR"
|
|
36
|
+
};
|
|
37
|
+
function createFFIDClient(config) {
|
|
38
|
+
if (!config.serviceCode || !config.serviceCode.trim()) {
|
|
39
|
+
throw new Error("FFID Client: serviceCode \u304C\u672A\u8A2D\u5B9A\u3067\u3059");
|
|
40
|
+
}
|
|
41
|
+
const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
|
|
42
|
+
const logger = config.logger ?? (config.debug ? consoleLogger : noopLogger);
|
|
43
|
+
async function fetchWithAuth(endpoint, options = {}) {
|
|
44
|
+
const url = `${baseUrl}${endpoint}`;
|
|
45
|
+
logger.debug("Fetching:", url);
|
|
46
|
+
let response;
|
|
47
|
+
try {
|
|
48
|
+
response = await fetch(url, {
|
|
49
|
+
...options,
|
|
50
|
+
credentials: "include",
|
|
51
|
+
// Include cookies for authentication
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
...options.headers
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
} catch (error) {
|
|
58
|
+
logger.error("Network error:", error);
|
|
59
|
+
return {
|
|
60
|
+
error: {
|
|
61
|
+
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
62
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
let raw;
|
|
67
|
+
try {
|
|
68
|
+
raw = await response.json();
|
|
69
|
+
} catch (parseError) {
|
|
70
|
+
logger.error("Parse error:", parseError, "Status:", response.status);
|
|
71
|
+
return {
|
|
72
|
+
error: {
|
|
73
|
+
code: FFID_ERROR_CODES.PARSE_ERROR,
|
|
74
|
+
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
logger.debug("Response:", response.status, raw);
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
return {
|
|
81
|
+
error: raw.error ?? {
|
|
82
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
83
|
+
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (raw.data === void 0) {
|
|
88
|
+
return {
|
|
89
|
+
error: {
|
|
90
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
91
|
+
message: "\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u30C7\u30FC\u30BF\u304C\u8FD4\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { data: raw.data };
|
|
96
|
+
}
|
|
97
|
+
async function getSession() {
|
|
98
|
+
return fetchWithAuth(SESSION_ENDPOINT);
|
|
99
|
+
}
|
|
100
|
+
async function signOut() {
|
|
101
|
+
const url = `${baseUrl}${LOGOUT_ENDPOINT}`;
|
|
102
|
+
logger.debug("Fetching:", url);
|
|
103
|
+
let response;
|
|
104
|
+
try {
|
|
105
|
+
response = await fetch(url, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
credentials: "include",
|
|
108
|
+
headers: { "Content-Type": "application/json" }
|
|
109
|
+
});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logger.error("Network error:", error);
|
|
112
|
+
return {
|
|
113
|
+
error: {
|
|
114
|
+
code: FFID_ERROR_CODES.NETWORK_ERROR,
|
|
115
|
+
message: error instanceof Error ? error.message : "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (response.status === NO_CONTENT_STATUS) {
|
|
120
|
+
logger.debug("Response: 204 No Content");
|
|
121
|
+
return { data: void 0 };
|
|
122
|
+
}
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
try {
|
|
125
|
+
const raw = await response.json();
|
|
126
|
+
return {
|
|
127
|
+
error: raw.error ?? {
|
|
128
|
+
code: FFID_ERROR_CODES.UNKNOWN_ERROR,
|
|
129
|
+
message: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F"
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
} catch {
|
|
133
|
+
return {
|
|
134
|
+
error: {
|
|
135
|
+
code: FFID_ERROR_CODES.PARSE_ERROR,
|
|
136
|
+
message: `\u30B5\u30FC\u30D0\u30FC\u304B\u3089\u4E0D\u6B63\u306A\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F (status: ${response.status})`
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
logger.debug("Response:", response.status);
|
|
142
|
+
return { data: void 0 };
|
|
143
|
+
}
|
|
144
|
+
function redirectToLogin() {
|
|
145
|
+
if (typeof window === "undefined") {
|
|
146
|
+
logger.debug("Cannot redirect in SSR context");
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const currentUrl = window.location.href;
|
|
150
|
+
const loginUrl = `${baseUrl}/login?redirect=${encodeURIComponent(currentUrl)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
151
|
+
logger.debug("Redirecting to login:", loginUrl);
|
|
152
|
+
window.location.href = loginUrl;
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
function getLoginUrl(redirectUrl) {
|
|
156
|
+
const redirect = redirectUrl ?? (typeof window !== "undefined" ? window.location.href : "");
|
|
157
|
+
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(config.serviceCode)}`;
|
|
158
|
+
}
|
|
159
|
+
function createError(code, message) {
|
|
160
|
+
return { code, message };
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
getSession,
|
|
164
|
+
signOut,
|
|
165
|
+
redirectToLogin,
|
|
166
|
+
getLoginUrl,
|
|
167
|
+
createError,
|
|
168
|
+
/** Resolved logger instance */
|
|
169
|
+
logger,
|
|
170
|
+
baseUrl,
|
|
171
|
+
serviceCode: config.serviceCode
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
var DEFAULT_REFRESH_INTERVAL = 5 * 60 * 1e3;
|
|
175
|
+
var FFIDContext = react.createContext(null);
|
|
176
|
+
var FFIDClientContext = react.createContext(null);
|
|
177
|
+
function FFIDProvider({
|
|
178
|
+
children,
|
|
179
|
+
serviceCode,
|
|
180
|
+
apiBaseUrl,
|
|
181
|
+
debug = false,
|
|
182
|
+
logger,
|
|
183
|
+
refreshInterval = DEFAULT_REFRESH_INTERVAL,
|
|
184
|
+
onAuthStateChange,
|
|
185
|
+
onError
|
|
186
|
+
}) {
|
|
187
|
+
const [user, setUser] = react.useState(null);
|
|
188
|
+
const [organizations, setOrganizations] = react.useState([]);
|
|
189
|
+
const [subscriptions, setSubscriptions] = react.useState([]);
|
|
190
|
+
const [currentOrganizationId, setCurrentOrganizationId] = react.useState(null);
|
|
191
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
192
|
+
const [error, setError] = react.useState(null);
|
|
193
|
+
const onAuthStateChangeRef = react.useRef(onAuthStateChange);
|
|
194
|
+
const onErrorRef = react.useRef(onError);
|
|
195
|
+
react.useEffect(() => {
|
|
196
|
+
onAuthStateChangeRef.current = onAuthStateChange;
|
|
197
|
+
}, [onAuthStateChange]);
|
|
198
|
+
react.useEffect(() => {
|
|
199
|
+
onErrorRef.current = onError;
|
|
200
|
+
}, [onError]);
|
|
201
|
+
const client = react.useMemo(
|
|
202
|
+
() => createFFIDClient({
|
|
203
|
+
serviceCode,
|
|
204
|
+
apiBaseUrl,
|
|
205
|
+
debug,
|
|
206
|
+
logger
|
|
207
|
+
}),
|
|
208
|
+
[serviceCode, apiBaseUrl, debug, logger]
|
|
209
|
+
);
|
|
210
|
+
const refresh = react.useCallback(async () => {
|
|
211
|
+
client.logger.debug("Refreshing session...");
|
|
212
|
+
try {
|
|
213
|
+
const response = await client.getSession();
|
|
214
|
+
if (response.error) {
|
|
215
|
+
client.logger.debug("Session error:", response.error);
|
|
216
|
+
setUser(null);
|
|
217
|
+
setOrganizations([]);
|
|
218
|
+
setSubscriptions([]);
|
|
219
|
+
setError(response.error);
|
|
220
|
+
onAuthStateChangeRef.current?.(null);
|
|
221
|
+
if (response.error.code !== "SESSION_NOT_FOUND") {
|
|
222
|
+
onErrorRef.current?.(response.error);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (response.data) {
|
|
227
|
+
const { user: sessionUser, organizations: orgs, subscriptions: subs } = response.data;
|
|
228
|
+
setUser(sessionUser);
|
|
229
|
+
setOrganizations(orgs);
|
|
230
|
+
setSubscriptions(subs);
|
|
231
|
+
setError(null);
|
|
232
|
+
setCurrentOrganizationId((prev) => {
|
|
233
|
+
if (!prev && orgs.length > 0) {
|
|
234
|
+
return orgs[0].id;
|
|
235
|
+
}
|
|
236
|
+
return prev;
|
|
237
|
+
});
|
|
238
|
+
onAuthStateChangeRef.current?.(sessionUser);
|
|
239
|
+
client.logger.debug("Session refreshed:", sessionUser.email);
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
client.logger.error("Refresh error:", err);
|
|
243
|
+
const ffidError = client.createError(
|
|
244
|
+
"REFRESH_ERROR",
|
|
245
|
+
err instanceof Error ? err.message : "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
246
|
+
);
|
|
247
|
+
setError(ffidError);
|
|
248
|
+
onErrorRef.current?.(ffidError);
|
|
249
|
+
} finally {
|
|
250
|
+
setIsLoading(false);
|
|
251
|
+
}
|
|
252
|
+
}, [client]);
|
|
253
|
+
const login = react.useCallback(() => {
|
|
254
|
+
client.redirectToLogin();
|
|
255
|
+
}, [client]);
|
|
256
|
+
const logout = react.useCallback(async () => {
|
|
257
|
+
client.logger.debug("Signing out...");
|
|
258
|
+
try {
|
|
259
|
+
const response = await client.signOut();
|
|
260
|
+
if (response.error) {
|
|
261
|
+
client.logger.debug("Logout warning from server:", response.error);
|
|
262
|
+
const ffidError = client.createError(
|
|
263
|
+
"LOGOUT_WARNING",
|
|
264
|
+
`Logged out locally but server returned: ${response.error.message}`
|
|
265
|
+
);
|
|
266
|
+
setError(ffidError);
|
|
267
|
+
onErrorRef.current?.(ffidError);
|
|
268
|
+
}
|
|
269
|
+
setUser(null);
|
|
270
|
+
setOrganizations([]);
|
|
271
|
+
setSubscriptions([]);
|
|
272
|
+
setCurrentOrganizationId(null);
|
|
273
|
+
onAuthStateChangeRef.current?.(null);
|
|
274
|
+
client.logger.debug("Signed out successfully");
|
|
275
|
+
} catch (err) {
|
|
276
|
+
client.logger.error("Logout error:", err);
|
|
277
|
+
const ffidError = client.createError(
|
|
278
|
+
"LOGOUT_ERROR",
|
|
279
|
+
err instanceof Error ? err.message : "\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F"
|
|
280
|
+
);
|
|
281
|
+
setError(ffidError);
|
|
282
|
+
onErrorRef.current?.(ffidError);
|
|
283
|
+
setUser(null);
|
|
284
|
+
setOrganizations([]);
|
|
285
|
+
setSubscriptions([]);
|
|
286
|
+
setCurrentOrganizationId(null);
|
|
287
|
+
onAuthStateChangeRef.current?.(null);
|
|
288
|
+
}
|
|
289
|
+
}, [client]);
|
|
290
|
+
const switchOrganization = react.useCallback(
|
|
291
|
+
(organizationId) => {
|
|
292
|
+
const org = organizations.find((o) => o.id === organizationId);
|
|
293
|
+
if (org) {
|
|
294
|
+
setCurrentOrganizationId(organizationId);
|
|
295
|
+
setError(null);
|
|
296
|
+
client.logger.debug("Switched organization:", org.name);
|
|
297
|
+
} else {
|
|
298
|
+
client.logger.debug("Organization not found:", organizationId);
|
|
299
|
+
const ffidError = client.createError(
|
|
300
|
+
"ORGANIZATION_NOT_FOUND",
|
|
301
|
+
`Organization with ID "${organizationId}" not found`
|
|
302
|
+
);
|
|
303
|
+
setError(ffidError);
|
|
304
|
+
onErrorRef.current?.(ffidError);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
[organizations, client]
|
|
308
|
+
);
|
|
309
|
+
react.useEffect(() => {
|
|
310
|
+
refresh();
|
|
311
|
+
}, [refresh]);
|
|
312
|
+
react.useEffect(() => {
|
|
313
|
+
if (!user || refreshInterval <= 0) return;
|
|
314
|
+
const intervalId = setInterval(() => {
|
|
315
|
+
refresh();
|
|
316
|
+
}, refreshInterval);
|
|
317
|
+
return () => clearInterval(intervalId);
|
|
318
|
+
}, [user, refreshInterval, refresh]);
|
|
319
|
+
const currentOrganization = react.useMemo(
|
|
320
|
+
() => organizations.find((o) => o.id === currentOrganizationId) ?? null,
|
|
321
|
+
[organizations, currentOrganizationId]
|
|
322
|
+
);
|
|
323
|
+
const contextValue = react.useMemo(
|
|
324
|
+
() => ({
|
|
325
|
+
user,
|
|
326
|
+
organizations,
|
|
327
|
+
currentOrganization,
|
|
328
|
+
subscriptions,
|
|
329
|
+
isLoading,
|
|
330
|
+
isAuthenticated: user !== null,
|
|
331
|
+
error,
|
|
332
|
+
login,
|
|
333
|
+
logout,
|
|
334
|
+
switchOrganization,
|
|
335
|
+
refresh
|
|
336
|
+
}),
|
|
337
|
+
[
|
|
338
|
+
user,
|
|
339
|
+
organizations,
|
|
340
|
+
currentOrganization,
|
|
341
|
+
subscriptions,
|
|
342
|
+
isLoading,
|
|
343
|
+
error,
|
|
344
|
+
login,
|
|
345
|
+
logout,
|
|
346
|
+
switchOrganization,
|
|
347
|
+
refresh
|
|
348
|
+
]
|
|
349
|
+
);
|
|
350
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FFIDClientContext.Provider, { value: client, children: /* @__PURE__ */ jsxRuntime.jsx(FFIDContext.Provider, { value: contextValue, children }) });
|
|
351
|
+
}
|
|
352
|
+
function useFFIDContext() {
|
|
353
|
+
const context = react.useContext(FFIDContext);
|
|
354
|
+
if (!context) {
|
|
355
|
+
throw new Error("useFFIDContext must be used within FFIDProvider");
|
|
356
|
+
}
|
|
357
|
+
return context;
|
|
358
|
+
}
|
|
359
|
+
function useFFIDClient() {
|
|
360
|
+
const client = react.useContext(FFIDClientContext);
|
|
361
|
+
if (!client) {
|
|
362
|
+
throw new Error("useFFIDClient must be used within FFIDProvider");
|
|
363
|
+
}
|
|
364
|
+
return client;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/hooks/useFFID.ts
|
|
368
|
+
function useFFID() {
|
|
369
|
+
const context = useFFIDContext();
|
|
370
|
+
return {
|
|
371
|
+
user: context.user,
|
|
372
|
+
organizations: context.organizations,
|
|
373
|
+
currentOrganization: context.currentOrganization,
|
|
374
|
+
isLoading: context.isLoading,
|
|
375
|
+
isAuthenticated: context.isAuthenticated,
|
|
376
|
+
error: context.error,
|
|
377
|
+
login: context.login,
|
|
378
|
+
logout: context.logout,
|
|
379
|
+
switchOrganization: context.switchOrganization,
|
|
380
|
+
refresh: context.refresh
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function FFIDLoginButton({
|
|
384
|
+
children,
|
|
385
|
+
className = "",
|
|
386
|
+
showLoading = true,
|
|
387
|
+
disabled,
|
|
388
|
+
...props
|
|
389
|
+
}) {
|
|
390
|
+
const { login, isLoading, isAuthenticated } = useFFID();
|
|
391
|
+
if (isAuthenticated) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
const handleClick = () => {
|
|
395
|
+
login();
|
|
396
|
+
};
|
|
397
|
+
const isDisabled = disabled || showLoading && isLoading;
|
|
398
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
399
|
+
"button",
|
|
400
|
+
{
|
|
401
|
+
type: "button",
|
|
402
|
+
onClick: handleClick,
|
|
403
|
+
disabled: isDisabled,
|
|
404
|
+
className,
|
|
405
|
+
...props,
|
|
406
|
+
children: showLoading && isLoading ? "\u8AAD\u307F\u8FBC\u307F\u4E2D..." : children ?? "\u30ED\u30B0\u30A4\u30F3"
|
|
407
|
+
}
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/components/styles.ts
|
|
412
|
+
var FONT_SIZE_SM = 14;
|
|
413
|
+
var FONT_SIZE_XS = 12;
|
|
414
|
+
var SPACING_XS = 4;
|
|
415
|
+
var SPACING_SM = 8;
|
|
416
|
+
var SPACING_MD = 12;
|
|
417
|
+
var SPACING_LG = 16;
|
|
418
|
+
var SIZE_ICON_SM = 16;
|
|
419
|
+
var SIZE_AVATAR_MD = 32;
|
|
420
|
+
var BORDER_RADIUS_SM = 6;
|
|
421
|
+
var BORDER_RADIUS_MD = 8;
|
|
422
|
+
var BORDER_RADIUS_PILL = 9999;
|
|
423
|
+
var DROPDOWN_MIN_WIDTH = 200;
|
|
424
|
+
var DROPDOWN_MAX_HEIGHT = 300;
|
|
425
|
+
var Z_INDEX_BACKDROP = 40;
|
|
426
|
+
var Z_INDEX_DROPDOWN = 50;
|
|
427
|
+
var COLOR_TEXT_MUTED = "#6b7280";
|
|
428
|
+
var COLOR_BORDER = "#e5e7eb";
|
|
429
|
+
var COLOR_BG_SELECTED = "#f3f4f6";
|
|
430
|
+
var COLOR_BG_SKELETON = "#e5e7eb";
|
|
431
|
+
var COLOR_PRIMARY = "#6366f1";
|
|
432
|
+
var COLOR_DANGER = "#dc2626";
|
|
433
|
+
var COLOR_BG_WHITE = "white";
|
|
434
|
+
var SHADOW_DROPDOWN = "0 4px 6px -1px rgba(0,0,0,0.1)";
|
|
435
|
+
var BORDER_DEFAULT = `1px solid ${COLOR_BORDER}`;
|
|
436
|
+
function isValidAvatarUrl(url) {
|
|
437
|
+
try {
|
|
438
|
+
const parsed = new URL(url);
|
|
439
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:" && parsed.hostname === "localhost";
|
|
440
|
+
} catch {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function FFIDUserMenu({
|
|
445
|
+
className = "",
|
|
446
|
+
classNames = {},
|
|
447
|
+
renderAvatar,
|
|
448
|
+
menuItems = [],
|
|
449
|
+
showOrganization = true,
|
|
450
|
+
logoutText = "\u30ED\u30B0\u30A2\u30A6\u30C8"
|
|
451
|
+
}) {
|
|
452
|
+
const { user, currentOrganization, logout, isLoading, isAuthenticated } = useFFID();
|
|
453
|
+
const [isOpen, setIsOpen] = react.useState(false);
|
|
454
|
+
if (!isAuthenticated || !user) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
if (isLoading) {
|
|
458
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [className, classNames.container].filter(Boolean).join(" ") || void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
459
|
+
"div",
|
|
460
|
+
{
|
|
461
|
+
className: classNames.avatar,
|
|
462
|
+
style: { width: SIZE_AVATAR_MD, height: SIZE_AVATAR_MD, borderRadius: "50%", background: COLOR_BG_SKELETON }
|
|
463
|
+
}
|
|
464
|
+
) });
|
|
465
|
+
}
|
|
466
|
+
const displayName = user.displayName ?? user.email;
|
|
467
|
+
const handleLogout = async () => {
|
|
468
|
+
setIsOpen(false);
|
|
469
|
+
await logout();
|
|
470
|
+
};
|
|
471
|
+
const defaultAvatar = /* @__PURE__ */ jsxRuntime.jsx(
|
|
472
|
+
"div",
|
|
473
|
+
{
|
|
474
|
+
className: classNames.avatar,
|
|
475
|
+
style: {
|
|
476
|
+
width: SIZE_AVATAR_MD,
|
|
477
|
+
height: SIZE_AVATAR_MD,
|
|
478
|
+
borderRadius: "50%",
|
|
479
|
+
background: COLOR_PRIMARY,
|
|
480
|
+
color: COLOR_BG_WHITE,
|
|
481
|
+
display: "flex",
|
|
482
|
+
alignItems: "center",
|
|
483
|
+
justifyContent: "center",
|
|
484
|
+
fontSize: FONT_SIZE_SM,
|
|
485
|
+
fontWeight: 500
|
|
486
|
+
},
|
|
487
|
+
children: displayName.charAt(0).toUpperCase()
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
const avatarElement = renderAvatar ? renderAvatar({ avatarUrl: user.avatarUrl, displayName: user.displayName, email: user.email }) : user.avatarUrl && isValidAvatarUrl(user.avatarUrl) ? /* @__PURE__ */ jsxRuntime.jsx("img", { className: classNames.avatar, src: user.avatarUrl, alt: displayName, style: { width: SIZE_AVATAR_MD, height: SIZE_AVATAR_MD, borderRadius: "50%" } }) : defaultAvatar;
|
|
491
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [className, classNames.container].filter(Boolean).join(" ") || void 0, style: { position: "relative" }, children: [
|
|
492
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
493
|
+
"button",
|
|
494
|
+
{
|
|
495
|
+
type: "button",
|
|
496
|
+
className: classNames.button,
|
|
497
|
+
onClick: () => setIsOpen(!isOpen),
|
|
498
|
+
style: { background: "none", border: "none", cursor: "pointer", padding: 0 },
|
|
499
|
+
"aria-label": "\u30E6\u30FC\u30B6\u30FC\u30E1\u30CB\u30E5\u30FC",
|
|
500
|
+
"aria-expanded": isOpen,
|
|
501
|
+
children: avatarElement
|
|
502
|
+
}
|
|
503
|
+
),
|
|
504
|
+
isOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
505
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
506
|
+
"div",
|
|
507
|
+
{
|
|
508
|
+
style: {
|
|
509
|
+
position: "fixed",
|
|
510
|
+
inset: 0,
|
|
511
|
+
zIndex: Z_INDEX_BACKDROP
|
|
512
|
+
},
|
|
513
|
+
onClick: () => setIsOpen(false)
|
|
514
|
+
}
|
|
515
|
+
),
|
|
516
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
517
|
+
"div",
|
|
518
|
+
{
|
|
519
|
+
className: classNames.menu,
|
|
520
|
+
style: {
|
|
521
|
+
position: "absolute",
|
|
522
|
+
right: 0,
|
|
523
|
+
top: "100%",
|
|
524
|
+
marginTop: SPACING_SM,
|
|
525
|
+
minWidth: DROPDOWN_MIN_WIDTH,
|
|
526
|
+
background: COLOR_BG_WHITE,
|
|
527
|
+
border: BORDER_DEFAULT,
|
|
528
|
+
borderRadius: BORDER_RADIUS_MD,
|
|
529
|
+
boxShadow: SHADOW_DROPDOWN,
|
|
530
|
+
zIndex: Z_INDEX_DROPDOWN
|
|
531
|
+
},
|
|
532
|
+
children: [
|
|
533
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.userInfo, style: { padding: `${SPACING_MD}px ${SPACING_LG}px`, borderBottom: BORDER_DEFAULT }, children: [
|
|
534
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 500, fontSize: FONT_SIZE_SM }, children: displayName }),
|
|
535
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: COLOR_TEXT_MUTED, fontSize: FONT_SIZE_XS }, children: user.email }),
|
|
536
|
+
showOrganization && currentOrganization && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: COLOR_TEXT_MUTED, fontSize: FONT_SIZE_XS, marginTop: SPACING_XS }, children: currentOrganization.name })
|
|
537
|
+
] }),
|
|
538
|
+
menuItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderBottom: BORDER_DEFAULT }, children: menuItems.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
539
|
+
"button",
|
|
540
|
+
{
|
|
541
|
+
type: "button",
|
|
542
|
+
className: classNames.menuItem,
|
|
543
|
+
onClick: () => {
|
|
544
|
+
setIsOpen(false);
|
|
545
|
+
item.onClick();
|
|
546
|
+
},
|
|
547
|
+
style: {
|
|
548
|
+
width: "100%",
|
|
549
|
+
padding: `${SPACING_SM}px ${SPACING_LG}px`,
|
|
550
|
+
background: "none",
|
|
551
|
+
border: "none",
|
|
552
|
+
textAlign: "left",
|
|
553
|
+
cursor: "pointer",
|
|
554
|
+
fontSize: FONT_SIZE_SM,
|
|
555
|
+
display: "flex",
|
|
556
|
+
alignItems: "center",
|
|
557
|
+
gap: SPACING_SM
|
|
558
|
+
},
|
|
559
|
+
children: [
|
|
560
|
+
item.icon,
|
|
561
|
+
item.label
|
|
562
|
+
]
|
|
563
|
+
},
|
|
564
|
+
item.label
|
|
565
|
+
)) }),
|
|
566
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
567
|
+
"button",
|
|
568
|
+
{
|
|
569
|
+
type: "button",
|
|
570
|
+
className: classNames.logout,
|
|
571
|
+
onClick: handleLogout,
|
|
572
|
+
style: {
|
|
573
|
+
width: "100%",
|
|
574
|
+
padding: `${SPACING_SM}px ${SPACING_LG}px`,
|
|
575
|
+
background: "none",
|
|
576
|
+
border: "none",
|
|
577
|
+
textAlign: "left",
|
|
578
|
+
cursor: "pointer",
|
|
579
|
+
fontSize: FONT_SIZE_SM,
|
|
580
|
+
color: COLOR_DANGER
|
|
581
|
+
},
|
|
582
|
+
children: logoutText
|
|
583
|
+
}
|
|
584
|
+
) })
|
|
585
|
+
]
|
|
586
|
+
}
|
|
587
|
+
)
|
|
588
|
+
] })
|
|
589
|
+
] });
|
|
590
|
+
}
|
|
591
|
+
function FFIDOrganizationSwitcher({
|
|
592
|
+
className = "",
|
|
593
|
+
classNames = {},
|
|
594
|
+
renderOrganization,
|
|
595
|
+
label = "\u7D44\u7E54",
|
|
596
|
+
emptyText = "\u7D44\u7E54\u304C\u3042\u308A\u307E\u305B\u3093",
|
|
597
|
+
onSwitch
|
|
598
|
+
}) {
|
|
599
|
+
const { organizations, currentOrganization, switchOrganization, isAuthenticated } = useFFID();
|
|
600
|
+
const [isOpen, setIsOpen] = react.useState(false);
|
|
601
|
+
if (!isAuthenticated) {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
if (organizations.length === 0) {
|
|
605
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [className, classNames.container].filter(Boolean).join(" ") || void 0, style: { color: COLOR_TEXT_MUTED, fontSize: FONT_SIZE_SM }, children: emptyText });
|
|
606
|
+
}
|
|
607
|
+
const handleSelect = (orgId) => {
|
|
608
|
+
switchOrganization(orgId);
|
|
609
|
+
onSwitch?.(orgId);
|
|
610
|
+
setIsOpen(false);
|
|
611
|
+
};
|
|
612
|
+
if (organizations.length === 1) {
|
|
613
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [className, classNames.container].filter(Boolean).join(" ") || void 0, style: { fontSize: FONT_SIZE_SM }, children: [
|
|
614
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: COLOR_TEXT_MUTED, marginRight: SPACING_XS }, children: [
|
|
615
|
+
label,
|
|
616
|
+
":"
|
|
617
|
+
] }),
|
|
618
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: currentOrganization?.name ?? organizations[0]?.name })
|
|
619
|
+
] });
|
|
620
|
+
}
|
|
621
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [className, classNames.container].filter(Boolean).join(" ") || void 0, style: { position: "relative" }, children: [
|
|
622
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
623
|
+
"button",
|
|
624
|
+
{
|
|
625
|
+
type: "button",
|
|
626
|
+
className: classNames.button,
|
|
627
|
+
onClick: () => setIsOpen(!isOpen),
|
|
628
|
+
style: {
|
|
629
|
+
display: "flex",
|
|
630
|
+
alignItems: "center",
|
|
631
|
+
gap: SPACING_SM,
|
|
632
|
+
padding: `${SPACING_SM}px ${SPACING_MD}px`,
|
|
633
|
+
background: COLOR_BG_WHITE,
|
|
634
|
+
border: BORDER_DEFAULT,
|
|
635
|
+
borderRadius: BORDER_RADIUS_SM,
|
|
636
|
+
cursor: "pointer",
|
|
637
|
+
fontSize: FONT_SIZE_SM
|
|
638
|
+
},
|
|
639
|
+
"aria-expanded": isOpen,
|
|
640
|
+
"aria-haspopup": "listbox",
|
|
641
|
+
children: [
|
|
642
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: currentOrganization?.name ?? label }),
|
|
643
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
644
|
+
"svg",
|
|
645
|
+
{
|
|
646
|
+
width: SIZE_ICON_SM,
|
|
647
|
+
height: SIZE_ICON_SM,
|
|
648
|
+
viewBox: "0 0 16 16",
|
|
649
|
+
fill: "none",
|
|
650
|
+
style: { transform: isOpen ? "rotate(180deg)" : "none" },
|
|
651
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 6L8 10L12 6", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
652
|
+
}
|
|
653
|
+
)
|
|
654
|
+
]
|
|
655
|
+
}
|
|
656
|
+
),
|
|
657
|
+
isOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
658
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
659
|
+
"div",
|
|
660
|
+
{
|
|
661
|
+
style: { position: "fixed", inset: 0, zIndex: Z_INDEX_BACKDROP },
|
|
662
|
+
onClick: () => setIsOpen(false)
|
|
663
|
+
}
|
|
664
|
+
),
|
|
665
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
666
|
+
"div",
|
|
667
|
+
{
|
|
668
|
+
role: "listbox",
|
|
669
|
+
className: classNames.dropdown,
|
|
670
|
+
style: {
|
|
671
|
+
position: "absolute",
|
|
672
|
+
left: 0,
|
|
673
|
+
top: "100%",
|
|
674
|
+
marginTop: SPACING_XS,
|
|
675
|
+
minWidth: DROPDOWN_MIN_WIDTH,
|
|
676
|
+
background: COLOR_BG_WHITE,
|
|
677
|
+
border: BORDER_DEFAULT,
|
|
678
|
+
borderRadius: BORDER_RADIUS_MD,
|
|
679
|
+
boxShadow: SHADOW_DROPDOWN,
|
|
680
|
+
zIndex: Z_INDEX_DROPDOWN,
|
|
681
|
+
maxHeight: DROPDOWN_MAX_HEIGHT,
|
|
682
|
+
overflow: "auto"
|
|
683
|
+
},
|
|
684
|
+
children: organizations.map((org) => {
|
|
685
|
+
const isCurrent = org.id === currentOrganization?.id;
|
|
686
|
+
const optionClassName = isCurrent ? [classNames.option, classNames.optionSelected].filter(Boolean).join(" ") || void 0 : classNames.option;
|
|
687
|
+
if (renderOrganization) {
|
|
688
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
689
|
+
"button",
|
|
690
|
+
{
|
|
691
|
+
type: "button",
|
|
692
|
+
role: "option",
|
|
693
|
+
"aria-selected": isCurrent,
|
|
694
|
+
className: optionClassName,
|
|
695
|
+
onClick: () => handleSelect(org.id),
|
|
696
|
+
style: {
|
|
697
|
+
all: "unset",
|
|
698
|
+
display: "block",
|
|
699
|
+
width: "100%",
|
|
700
|
+
cursor: "pointer",
|
|
701
|
+
boxSizing: "border-box"
|
|
702
|
+
},
|
|
703
|
+
children: renderOrganization(org, isCurrent)
|
|
704
|
+
},
|
|
705
|
+
org.id
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
709
|
+
"button",
|
|
710
|
+
{
|
|
711
|
+
type: "button",
|
|
712
|
+
role: "option",
|
|
713
|
+
"aria-selected": isCurrent,
|
|
714
|
+
className: optionClassName,
|
|
715
|
+
onClick: () => handleSelect(org.id),
|
|
716
|
+
style: {
|
|
717
|
+
width: "100%",
|
|
718
|
+
padding: `10px ${SPACING_LG}px`,
|
|
719
|
+
background: isCurrent ? COLOR_BG_SELECTED : COLOR_BG_WHITE,
|
|
720
|
+
border: "none",
|
|
721
|
+
textAlign: "left",
|
|
722
|
+
cursor: "pointer",
|
|
723
|
+
fontSize: FONT_SIZE_SM,
|
|
724
|
+
display: "flex",
|
|
725
|
+
alignItems: "center",
|
|
726
|
+
justifyContent: "space-between"
|
|
727
|
+
},
|
|
728
|
+
children: [
|
|
729
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
730
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 500 }, children: org.name }),
|
|
731
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: COLOR_TEXT_MUTED, fontSize: FONT_SIZE_XS }, children: org.role === "owner" ? "\u30AA\u30FC\u30CA\u30FC" : org.role === "admin" ? "\u7BA1\u7406\u8005" : "\u30E1\u30F3\u30D0\u30FC" })
|
|
732
|
+
] }),
|
|
733
|
+
isCurrent && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: SIZE_ICON_SM, height: SIZE_ICON_SM, viewBox: "0 0 16 16", fill: COLOR_PRIMARY, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z" }) })
|
|
734
|
+
]
|
|
735
|
+
},
|
|
736
|
+
org.id
|
|
737
|
+
);
|
|
738
|
+
})
|
|
739
|
+
}
|
|
740
|
+
)
|
|
741
|
+
] })
|
|
742
|
+
] });
|
|
743
|
+
}
|
|
744
|
+
function useSubscription() {
|
|
745
|
+
const context = useFFIDContext();
|
|
746
|
+
const client = useFFIDClient();
|
|
747
|
+
const subscription = react.useMemo(() => {
|
|
748
|
+
const subs = context.subscriptions.filter(
|
|
749
|
+
(sub) => sub.serviceCode === client.serviceCode
|
|
750
|
+
);
|
|
751
|
+
const activeSub = subs.find((s) => s.status === "active");
|
|
752
|
+
if (activeSub) return activeSub;
|
|
753
|
+
const trialingSub = subs.find((s) => s.status === "trialing");
|
|
754
|
+
if (trialingSub) return trialingSub;
|
|
755
|
+
return subs[0] ?? null;
|
|
756
|
+
}, [context.subscriptions, client.serviceCode]);
|
|
757
|
+
const planCode = subscription?.planCode ?? null;
|
|
758
|
+
const isActive = subscription?.status === "active";
|
|
759
|
+
const isTrialing = subscription?.status === "trialing";
|
|
760
|
+
const isCanceled = subscription?.status === "canceled";
|
|
761
|
+
const hasPlan = react.useCallback(
|
|
762
|
+
(plans) => {
|
|
763
|
+
if (!planCode) return false;
|
|
764
|
+
const planList = Array.isArray(plans) ? plans : [plans];
|
|
765
|
+
return planList.includes(planCode);
|
|
766
|
+
},
|
|
767
|
+
[planCode]
|
|
768
|
+
);
|
|
769
|
+
const hasAccess = react.useCallback(() => {
|
|
770
|
+
return isActive || isTrialing;
|
|
771
|
+
}, [isActive, isTrialing]);
|
|
772
|
+
return {
|
|
773
|
+
subscription,
|
|
774
|
+
planCode,
|
|
775
|
+
isActive,
|
|
776
|
+
isTrialing,
|
|
777
|
+
isCanceled,
|
|
778
|
+
hasPlan,
|
|
779
|
+
hasAccess
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function withSubscription(Component, options = {}) {
|
|
783
|
+
const WrappedComponent = (props) => {
|
|
784
|
+
const { hasAccess, hasPlan } = useSubscription();
|
|
785
|
+
const { isLoading } = useFFIDContext();
|
|
786
|
+
if (isLoading) {
|
|
787
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: options.loading ?? null });
|
|
788
|
+
}
|
|
789
|
+
if (!hasAccess()) {
|
|
790
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: options.fallback ?? null });
|
|
791
|
+
}
|
|
792
|
+
if (options.plans && options.plans.length > 0 && !hasPlan(options.plans)) {
|
|
793
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: options.fallback ?? null });
|
|
794
|
+
}
|
|
795
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props });
|
|
796
|
+
};
|
|
797
|
+
WrappedComponent.displayName = `withSubscription(${Component.displayName ?? Component.name ?? "Component"})`;
|
|
798
|
+
return WrappedComponent;
|
|
799
|
+
}
|
|
800
|
+
var BADGE_PADDING_X = 10;
|
|
801
|
+
var STATUS_COLORS = {
|
|
802
|
+
active: { bg: "#dcfce7", text: "#166534" },
|
|
803
|
+
trialing: { bg: "#dbeafe", text: "#1e40af" },
|
|
804
|
+
past_due: { bg: "#fef3c7", text: "#92400e" },
|
|
805
|
+
canceled: { bg: "#fee2e2", text: "#991b1b" },
|
|
806
|
+
paused: { bg: "#f3f4f6", text: "#4b5563" },
|
|
807
|
+
none: { bg: "#f3f4f6", text: "#6b7280" }
|
|
808
|
+
};
|
|
809
|
+
var DEFAULT_LABELS = {
|
|
810
|
+
active: "\u5951\u7D04\u4E2D",
|
|
811
|
+
trialing: "\u30C8\u30E9\u30A4\u30A2\u30EB",
|
|
812
|
+
pastDue: "\u652F\u6255\u3044\u9045\u5EF6",
|
|
813
|
+
canceled: "\u89E3\u7D04\u6E08\u307F",
|
|
814
|
+
paused: "\u4E00\u6642\u505C\u6B62",
|
|
815
|
+
none: "\u672A\u5951\u7D04"
|
|
816
|
+
};
|
|
817
|
+
function FFIDSubscriptionBadge({
|
|
818
|
+
className = "",
|
|
819
|
+
classNames = {},
|
|
820
|
+
style,
|
|
821
|
+
showPlanName = false,
|
|
822
|
+
labels = {},
|
|
823
|
+
render
|
|
824
|
+
}) {
|
|
825
|
+
const { subscription } = useSubscription();
|
|
826
|
+
const status = subscription?.status ?? "none";
|
|
827
|
+
const planName = subscription?.planName ?? null;
|
|
828
|
+
if (render) {
|
|
829
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: render(status, planName) });
|
|
830
|
+
}
|
|
831
|
+
const mergedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
832
|
+
const statusToLabelKey = {
|
|
833
|
+
active: "active",
|
|
834
|
+
trialing: "trialing",
|
|
835
|
+
past_due: "pastDue",
|
|
836
|
+
canceled: "canceled",
|
|
837
|
+
paused: "paused"
|
|
838
|
+
};
|
|
839
|
+
const labelKey = statusToLabelKey[status] ?? "none";
|
|
840
|
+
const label = mergedLabels[labelKey];
|
|
841
|
+
const colors = STATUS_COLORS[status] ?? STATUS_COLORS.none;
|
|
842
|
+
const displayText = showPlanName && planName ? `${planName} (${label})` : label;
|
|
843
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
844
|
+
"span",
|
|
845
|
+
{
|
|
846
|
+
className: [className, classNames.badge].filter(Boolean).join(" ") || void 0,
|
|
847
|
+
style: {
|
|
848
|
+
display: "inline-flex",
|
|
849
|
+
alignItems: "center",
|
|
850
|
+
padding: `${SPACING_XS}px ${BADGE_PADDING_X}px`,
|
|
851
|
+
borderRadius: BORDER_RADIUS_PILL,
|
|
852
|
+
fontSize: FONT_SIZE_XS,
|
|
853
|
+
fontWeight: 500,
|
|
854
|
+
backgroundColor: colors.bg,
|
|
855
|
+
color: colors.text,
|
|
856
|
+
...style
|
|
857
|
+
},
|
|
858
|
+
children: displayText
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
|
|
864
|
+
exports.FFIDLoginButton = FFIDLoginButton;
|
|
865
|
+
exports.FFIDOrganizationSwitcher = FFIDOrganizationSwitcher;
|
|
866
|
+
exports.FFIDProvider = FFIDProvider;
|
|
867
|
+
exports.FFIDSubscriptionBadge = FFIDSubscriptionBadge;
|
|
868
|
+
exports.FFIDUserMenu = FFIDUserMenu;
|
|
869
|
+
exports.useFFID = useFFID;
|
|
870
|
+
exports.useFFIDContext = useFFIDContext;
|
|
871
|
+
exports.useSubscription = useSubscription;
|
|
872
|
+
exports.withSubscription = withSubscription;
|