@ankhorage/studio 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +1 -1
  2. package/dist/authSettings.d.ts +33 -0
  3. package/dist/authSettings.d.ts.map +1 -0
  4. package/dist/authSettings.js +525 -0
  5. package/dist/authSettings.js.map +1 -0
  6. package/dist/host/auth/projectAuthHealthService.d.ts +30 -0
  7. package/dist/host/auth/projectAuthHealthService.d.ts.map +1 -0
  8. package/dist/host/auth/projectAuthHealthService.js +46 -0
  9. package/dist/host/auth/projectAuthHealthService.js.map +1 -0
  10. package/dist/host/auth/projectAuthService.d.ts +23 -0
  11. package/dist/host/auth/projectAuthService.d.ts.map +1 -0
  12. package/dist/host/auth/projectAuthService.js +63 -0
  13. package/dist/host/auth/projectAuthService.js.map +1 -0
  14. package/dist/host/http/authRoutes.d.ts +7 -0
  15. package/dist/host/http/authRoutes.d.ts.map +1 -0
  16. package/dist/host/http/authRoutes.js +64 -0
  17. package/dist/host/http/authRoutes.js.map +1 -0
  18. package/dist/host/http/secretRoutes.d.ts.map +1 -1
  19. package/dist/host/http/secretRoutes.js +53 -2
  20. package/dist/host/http/secretRoutes.js.map +1 -1
  21. package/dist/host/http/serverWithSecrets.d.ts +6 -0
  22. package/dist/host/http/serverWithSecrets.d.ts.map +1 -0
  23. package/dist/host/http/serverWithSecrets.js +27 -0
  24. package/dist/host/http/serverWithSecrets.js.map +1 -0
  25. package/dist/host/index.d.ts +4 -0
  26. package/dist/host/index.d.ts.map +1 -1
  27. package/dist/host/index.js +4 -0
  28. package/dist/host/index.js.map +1 -1
  29. package/dist/host/layout/layoutGenerator.d.ts.map +1 -1
  30. package/dist/host/layout/layoutGenerator.js +8 -0
  31. package/dist/host/layout/layoutGenerator.js.map +1 -1
  32. package/dist/host/secrets/projectSecretService.d.ts +32 -1
  33. package/dist/host/secrets/projectSecretService.d.ts.map +1 -1
  34. package/dist/host/secrets/projectSecretService.js +83 -10
  35. package/dist/host/secrets/projectSecretService.js.map +1 -1
  36. package/dist/index.d.ts +4 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/projectAuthApi.d.ts +24 -0
  41. package/dist/projectAuthApi.d.ts.map +1 -0
  42. package/dist/projectAuthApi.js +203 -0
  43. package/dist/projectAuthApi.js.map +1 -0
  44. package/dist/projectAuthHealth.d.ts +38 -0
  45. package/dist/projectAuthHealth.d.ts.map +1 -0
  46. package/dist/projectAuthHealth.js +219 -0
  47. package/dist/projectAuthHealth.js.map +1 -0
  48. package/dist/projectSecretApi.d.ts +14 -1
  49. package/dist/projectSecretApi.d.ts.map +1 -1
  50. package/dist/projectSecretApi.js +75 -18
  51. package/dist/projectSecretApi.js.map +1 -1
  52. package/dist/projectSecretUsage.d.ts +19 -0
  53. package/dist/projectSecretUsage.d.ts.map +1 -0
  54. package/dist/projectSecretUsage.js +38 -0
  55. package/dist/projectSecretUsage.js.map +1 -0
  56. package/dist/root.d.ts +3 -0
  57. package/dist/root.d.ts.map +1 -1
  58. package/dist/root.js +3 -0
  59. package/dist/root.js.map +1 -1
  60. package/dist/secretResponseGuard.d.ts +8 -0
  61. package/dist/secretResponseGuard.d.ts.map +1 -0
  62. package/dist/secretResponseGuard.js +46 -0
  63. package/dist/secretResponseGuard.js.map +1 -0
  64. package/dist/studioAdminRouteModel.d.ts +5 -6
  65. package/dist/studioAdminRouteModel.d.ts.map +1 -1
  66. package/dist/studioAdminRouteModel.js.map +1 -1
  67. package/dist/ui/StudioAdminOverlay.d.ts +0 -4
  68. package/dist/ui/StudioAdminOverlay.d.ts.map +1 -1
  69. package/dist/ui/StudioAdminOverlay.js +125 -107
  70. package/dist/ui/StudioAdminOverlay.js.map +1 -1
  71. package/dist/ui/StudioAuthSettingsOverlay.d.ts +9 -0
  72. package/dist/ui/StudioAuthSettingsOverlay.d.ts.map +1 -0
  73. package/dist/ui/StudioAuthSettingsOverlay.js +607 -0
  74. package/dist/ui/StudioAuthSettingsOverlay.js.map +1 -0
  75. package/dist/ui/useStudioAppBarAugmentation.d.ts.map +1 -1
  76. package/dist/ui/useStudioAppBarAugmentation.js +23 -10
  77. package/dist/ui/useStudioAppBarAugmentation.js.map +1 -1
  78. package/package.json +10 -2
@@ -0,0 +1,607 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { DEFAULT_AUTH_FLOW, } from '@ankhorage/contracts';
3
+ import { getSupabaseOAuthProviderDefinition, SUPABASE_OAUTH_PROVIDER_IDS, } from '@ankhorage/supabase-auth';
4
+ import { Heading, IconButton, Text, useZoraTheme } from '@ankhorage/zora';
5
+ import { useRouter } from 'expo-router';
6
+ import React, { useCallback, useEffect, useState } from 'react';
7
+ import { ActivityIndicator, Pressable, ScrollView, StyleSheet, Switch, TextInput, View, } from 'react-native';
8
+ import { SafeAreaView } from 'react-native-safe-area-context';
9
+ import { readStudioAuthSettings } from '../authSettings';
10
+ import { getProjectAuthHealth, getProjectAuthSettings, ProjectAuthApiError, saveProjectAuthSettings, } from '../projectAuthApi';
11
+ import { configureProjectOAuthProvider } from '../projectSecretApi';
12
+ const SIGN_IN_IDENTIFIERS = ['email', 'phone', 'username'];
13
+ const PROFILE_FIELDS = [
14
+ 'email',
15
+ 'phone',
16
+ 'username',
17
+ 'firstName',
18
+ 'lastName',
19
+ 'displayName',
20
+ 'avatarUrl',
21
+ ];
22
+ export function StudioAuthSettingsOverlay(props) {
23
+ const { projectId, manifest, onClose } = props;
24
+ const { theme } = useZoraTheme();
25
+ const router = useRouter();
26
+ const [draft, setDraft] = useState(() => readStudioAuthSettings(manifest ?? createFallbackManifest()) ?? createDefaultSettings());
27
+ const [health, setHealth] = useState(null);
28
+ const [loading, setLoading] = useState(true);
29
+ const [saving, setSaving] = useState(false);
30
+ const [message, setMessage] = useState(null);
31
+ const [partialFailure, setPartialFailure] = useState(null);
32
+ const reload = useCallback(async () => {
33
+ setLoading(true);
34
+ try {
35
+ const [loaded, loadedHealth] = await Promise.all([
36
+ getProjectAuthSettings(projectId),
37
+ getProjectAuthHealth({ projectId, environment: 'local' }),
38
+ ]);
39
+ setDraft(loaded ?? createDefaultSettings());
40
+ setHealth(loadedHealth);
41
+ setPartialFailure(null);
42
+ setMessage(null);
43
+ }
44
+ catch (error) {
45
+ const local = manifest ? readStudioAuthSettings(manifest) : null;
46
+ if (local)
47
+ setDraft(local);
48
+ setMessage(toMessage(error));
49
+ }
50
+ finally {
51
+ setLoading(false);
52
+ }
53
+ }, [manifest, projectId]);
54
+ useEffect(() => {
55
+ void reload();
56
+ }, [reload]);
57
+ const refreshHealth = useCallback(async () => {
58
+ try {
59
+ setHealth(await getProjectAuthHealth({ projectId, environment: 'local' }));
60
+ }
61
+ catch (error) {
62
+ setMessage(toMessage(error));
63
+ }
64
+ }, [projectId]);
65
+ const save = useCallback(async () => {
66
+ setSaving(true);
67
+ setMessage(null);
68
+ try {
69
+ const saved = await saveProjectAuthSettings({ projectId, config: draft });
70
+ setDraft(saved);
71
+ setHealth(await getProjectAuthHealth({ projectId, environment: 'local' }));
72
+ setPartialFailure(null);
73
+ setMessage('Authentication configuration saved to the Studio manifest draft.');
74
+ }
75
+ catch (error) {
76
+ setMessage(toMessage(error));
77
+ }
78
+ finally {
79
+ setSaving(false);
80
+ }
81
+ }, [draft, projectId]);
82
+ const retryPartialFailure = useCallback(async () => {
83
+ if (!partialFailure)
84
+ return;
85
+ setSaving(true);
86
+ setMessage(null);
87
+ try {
88
+ const persisted = (await getProjectAuthSettings(projectId)) ?? createDefaultSettings();
89
+ const persistedOAuth = persisted.oauth ?? createDefaultOAuth();
90
+ await saveProjectAuthSettings({
91
+ projectId,
92
+ config: {
93
+ ...persisted,
94
+ oauth: {
95
+ ...persistedOAuth,
96
+ providers: upsertProvider(persistedOAuth.providers, partialFailure.intendedProvider),
97
+ },
98
+ },
99
+ });
100
+ setDraft((current) => {
101
+ const currentOAuth = current.oauth ?? createDefaultOAuth();
102
+ return {
103
+ ...current,
104
+ oauth: {
105
+ ...currentOAuth,
106
+ providers: upsertProvider(currentOAuth.providers, partialFailure.intendedProvider),
107
+ },
108
+ };
109
+ });
110
+ await refreshHealth();
111
+ setPartialFailure(null);
112
+ setMessage(`Linked ${partialFailure.credentialsRef} to the provider configuration.`);
113
+ }
114
+ catch (error) {
115
+ setMessage(toMessage(error));
116
+ }
117
+ finally {
118
+ setSaving(false);
119
+ }
120
+ }, [partialFailure, projectId, refreshHealth]);
121
+ const authEnabled = draft.scope !== 'none';
122
+ const signUpEnabled = draft.signUp !== undefined;
123
+ const profileEnabled = draft.profile !== undefined;
124
+ const oauth = draft.oauth ?? createDefaultOAuth();
125
+ return (_jsxs(SafeAreaView, { style: [
126
+ styles.overlay,
127
+ { backgroundColor: theme.colors.background, borderColor: theme.colors.border },
128
+ ], children: [_jsxs(View, { style: [styles.header, { borderBottomColor: theme.colors.border }], children: [_jsxs(View, { style: styles.grow, children: [_jsx(Heading, { level: 2, text: "Authentication" }), _jsx(Text, { color: "neutral", emphasis: "muted", variant: "bodySmall", children: "Configure canonical auth methods, routes, profile settings, and provider activation." })] }), _jsx(IconButton, { icon: { name: 'close-outline' }, label: "Close authentication settings", color: "neutral", variant: "ghost", onPress: onClose })] }), _jsxs(ScrollView, { contentContainerStyle: styles.content, keyboardShouldPersistTaps: "handled", children: [loading ? _jsx(ActivityIndicator, {}) : null, _jsx(AuthHealthCard, { health: health }), _jsxs(Card, { title: "Overview", children: [_jsx(SwitchSetting, { title: "Authentication enabled", description: "Disabling auth keeps the configuration but sets the canonical scope to none.", value: authEnabled, onValueChange: (enabled) => setDraft((current) => ({ ...current, scope: enabled ? 'global' : 'none' })) }), _jsx(KeyValue, { label: "Provider", value: "Supabase" }), _jsx(KeyValue, { label: "Configuration source", value: "infra.auth" }), _jsx(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: "Roles, RBAC, ABAC, registered users, and user passwords are intentionally not managed here." })] }), _jsxs(Card, { title: "Email and password", children: [_jsx(Text, { weight: "semiBold", children: "Sign-in identifiers" }), _jsx(View, { style: styles.choiceRow, children: SIGN_IN_IDENTIFIERS.map((identifier) => {
129
+ const selected = draft.signIn.identifiers.includes(identifier);
130
+ return (_jsx(Choice, { label: identifier, selected: selected, onPress: () => setDraft((current) => {
131
+ const identifiers = selected
132
+ ? current.signIn.identifiers.filter((value) => value !== identifier)
133
+ : [...current.signIn.identifiers, identifier];
134
+ if (identifiers.length === 0)
135
+ return current;
136
+ return { ...current, signIn: { identifiers } };
137
+ }) }, identifier));
138
+ }) }), _jsx(SwitchSetting, { title: "Public sign-up enabled", description: "When disabled, sign-up configuration is removed from the desired state.", value: signUpEnabled, onValueChange: (enabled) => setDraft((current) => enabled
139
+ ? {
140
+ ...current,
141
+ signUp: current.signUp ?? {
142
+ requiredFields: ['email', 'password'],
143
+ optionalFields: [],
144
+ signUpPolicy: 'requireVerification',
145
+ },
146
+ }
147
+ : omitSignUp(current)) }), draft.signUp ? (_jsxs(_Fragment, { children: [_jsx(Field, { label: "Required sign-up fields (comma-separated)", children: _jsx(Input, { value: draft.signUp.requiredFields.join(', '), autoCapitalize: "none", onChangeText: (value) => setDraft((current) => current.signUp
148
+ ? {
149
+ ...current,
150
+ signUp: {
151
+ ...current.signUp,
152
+ requiredFields: splitList(value),
153
+ },
154
+ }
155
+ : current) }) }), _jsx(Field, { label: "Optional sign-up fields (comma-separated)", children: _jsx(Input, { value: (draft.signUp.optionalFields ?? []).join(', '), autoCapitalize: "none", onChangeText: (value) => setDraft((current) => current.signUp
156
+ ? {
157
+ ...current,
158
+ signUp: {
159
+ ...current.signUp,
160
+ optionalFields: splitList(value),
161
+ },
162
+ }
163
+ : current) }) }), _jsx(SwitchSetting, { title: "Email confirmation required", description: "Uses the canonical requireVerification sign-up policy.", value: draft.signUp.signUpPolicy === 'requireVerification', onValueChange: (required) => setDraft((current) => current.signUp
164
+ ? {
165
+ ...current,
166
+ signUp: {
167
+ ...current.signUp,
168
+ signUpPolicy: required ? 'requireVerification' : 'autoSignIn',
169
+ },
170
+ }
171
+ : current) })] })) : null, _jsx(Field, { label: "Forgot-password route", children: _jsx(Input, { value: draft.flow.forgotPasswordRoute ?? '', autoCapitalize: "none", onChangeText: (forgotPasswordRoute) => setDraft((current) => ({
172
+ ...current,
173
+ flow: {
174
+ ...current.flow,
175
+ ...(forgotPasswordRoute.trim() ? { forgotPasswordRoute } : {}),
176
+ },
177
+ })) }) })] }), _jsxs(Card, { title: "Routes", children: [_jsx(RouteField, { label: "Sign-in route", value: draft.flow.signInRoute, onChange: (signInRoute) => updateFlow(setDraft, { signInRoute }) }), _jsx(RouteField, { label: "Sign-up route", value: draft.flow.signUpRoute ?? '', onChange: (signUpRoute) => updateFlow(setDraft, { signUpRoute }) }), _jsx(RouteField, { label: "Sign-out route", value: draft.flow.signOutRoute ?? '', onChange: (signOutRoute) => updateFlow(setDraft, { signOutRoute }) }), _jsx(RouteField, { label: "Post-sign-in route", value: draft.flow.postSignInRoute, onChange: (postSignInRoute) => updateFlow(setDraft, { postSignInRoute }) }), _jsx(RouteField, { label: "Unauthorized route", value: draft.flow.unauthorizedRoute ?? '', onChange: (unauthorizedRoute) => updateFlow(setDraft, { unauthorizedRoute }) })] }), _jsxs(Card, { title: "OAuth providers", children: [_jsx(SwitchSetting, { title: "OAuth enabled", description: "Provider credentials remain in the server-side secret store.", value: oauth.enabled, onValueChange: (enabled) => setDraft((current) => ({
178
+ ...current,
179
+ oauth: { ...(current.oauth ?? createDefaultOAuth()), enabled },
180
+ })) }), _jsx(Field, { label: "Callback route", children: _jsx(Input, { value: oauth.callbackRoute, autoCapitalize: "none", onChangeText: (callbackRoute) => setDraft((current) => ({
181
+ ...current,
182
+ oauth: { ...(current.oauth ?? createDefaultOAuth()), callbackRoute },
183
+ })) }) }), SUPABASE_OAUTH_PROVIDER_IDS.map((providerId) => (_jsx(OAuthProviderSetting, { projectId: projectId, providerId: providerId, oauth: oauth, providerHealth: health?.providers.find((provider) => provider.providerId === providerId), onChange: (nextOAuth, nextMessage) => {
184
+ setDraft((current) => ({ ...current, oauth: nextOAuth }));
185
+ setMessage(nextMessage);
186
+ }, onSaved: (nextOAuth, nextMessage) => {
187
+ setDraft((current) => ({ ...current, oauth: nextOAuth }));
188
+ setMessage(nextMessage);
189
+ setPartialFailure(null);
190
+ void refreshHealth();
191
+ }, onPartialFailure: (failure) => {
192
+ setPartialFailure(failure);
193
+ setMessage(`${failure.credentialsRef} was saved, but the manifest link failed. Retry the link or open project secrets for cleanup.`);
194
+ } }, providerId))), partialFailure ? (_jsxs(View, { style: styles.partialFailure, children: [_jsx(Text, { color: "warning", weight: "semiBold", children: "Credential secret saved, manifest link failed." }), _jsxs(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: ["Ref: ", partialFailure.credentialsRef] }), _jsxs(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: ["Configured fields: ", partialFailure.configuredFields.join(', ') || 'none'] }), _jsxs(View, { style: styles.actions, children: [_jsx(SecondaryButton, { label: "Retry manifest link", onPress: () => void retryPartialFailure() }), _jsx(SecondaryButton, { label: "Open cleanup", onPress: () => {
195
+ router.push('/ankh/secrets');
196
+ } })] })] })) : null, _jsx(View, { style: styles.actions, children: _jsx(SecondaryButton, { label: "Open project secrets", onPress: () => {
197
+ router.push('/ankh/secrets');
198
+ } }) })] }), _jsxs(Card, { title: "Profile", children: [_jsx(SwitchSetting, { title: "Profile table enabled", description: "Profiles are separate from Supabase Auth users. The users table is not an option.", value: profileEnabled, onValueChange: (enabled) => setDraft((current) => enabled
199
+ ? {
200
+ ...current,
201
+ profile: current.profile ?? {
202
+ table: 'profiles',
203
+ fields: ['email', 'displayName', 'avatarUrl'],
204
+ primaryKey: 'authUserId',
205
+ createStrategy: 'trigger',
206
+ updateStrategy: 'api',
207
+ },
208
+ }
209
+ : omitProfile(current)) }), draft.profile ? (_jsxs(_Fragment, { children: [_jsx(Field, { label: "Profile table", children: _jsx(Input, { value: draft.profile.table ?? 'profiles', autoCapitalize: "none", onChangeText: (table) => setDraft((current) => current.profile
210
+ ? { ...current, profile: { ...current.profile, table } }
211
+ : current) }) }), _jsx(Text, { weight: "semiBold", children: "Profile fields" }), _jsx(View, { style: styles.choiceRow, children: PROFILE_FIELDS.map((field) => {
212
+ const selected = draft.profile?.fields.includes(field) ?? false;
213
+ return (_jsx(Choice, { label: field, selected: selected, onPress: () => setDraft((current) => {
214
+ if (!current.profile)
215
+ return current;
216
+ const fields = selected
217
+ ? current.profile.fields.filter((value) => value !== field)
218
+ : [...current.profile.fields, field];
219
+ return {
220
+ ...current,
221
+ profile: { ...current.profile, fields },
222
+ };
223
+ }) }, field));
224
+ }) }), _jsx(KeyValue, { label: "Primary key", value: "authUserId" }), _jsx(Field, { label: "Create strategy", children: _jsx(View, { style: styles.choiceRow, children: ['trigger', 'api', 'app'].map((strategy) => (_jsx(Choice, { label: strategy, selected: draft.profile?.createStrategy === strategy, onPress: () => setDraft((current) => current.profile
225
+ ? {
226
+ ...current,
227
+ profile: { ...current.profile, createStrategy: strategy },
228
+ }
229
+ : current) }, strategy))) }) }), _jsx(Field, { label: "Update strategy", children: _jsx(View, { style: styles.choiceRow, children: ['api', 'app'].map((strategy) => (_jsx(Choice, { label: strategy, selected: draft.profile?.updateStrategy === strategy, onPress: () => setDraft((current) => current.profile
230
+ ? {
231
+ ...current,
232
+ profile: { ...current.profile, updateStrategy: strategy },
233
+ }
234
+ : current) }, strategy))) }) })] })) : null] }), _jsxs(View, { style: styles.footerActions, children: [_jsx(SecondaryButton, { label: "Reload", onPress: () => void reload() }), _jsx(PrimaryButton, { label: "Save authentication", loading: saving, onPress: () => void save() })] }), message ? _jsx(Message, { text: message }) : null] })] }));
235
+ }
236
+ function OAuthProviderSetting(props) {
237
+ const definition = getSupabaseOAuthProviderDefinition(props.providerId);
238
+ const current = props.oauth.providers.find((provider) => provider.id === props.providerId);
239
+ const requiredFields = props.providerHealth?.requiredFields ??
240
+ definition?.secretFields.map((field) => field.name) ??
241
+ [];
242
+ const configuredFields = props.providerHealth?.configuredFields ?? [];
243
+ const credentialsComplete = definition !== null &&
244
+ Boolean(current?.credentialsRef) &&
245
+ requiredFields.length > 0 &&
246
+ requiredFields.every((field) => configuredFields.includes(field));
247
+ const enabled = current?.enabled === true;
248
+ const [credentialValues, setCredentialValues] = useState({});
249
+ const [savingCredentials, setSavingCredentials] = useState(false);
250
+ const [credentialMessage, setCredentialMessage] = useState(null);
251
+ const setEnabled = (nextEnabled) => {
252
+ if (nextEnabled && !credentialsComplete) {
253
+ props.onChange(props.oauth, `Complete ${definition?.label ?? props.providerId} credentials before enabling the provider.`);
254
+ return;
255
+ }
256
+ const nextProvider = {
257
+ ...(current ?? {
258
+ id: props.providerId,
259
+ label: definition?.label ?? props.providerId,
260
+ scopes: [...(definition?.defaultScopes ?? [])],
261
+ }),
262
+ enabled: nextEnabled,
263
+ };
264
+ props.onChange({
265
+ ...props.oauth,
266
+ providers: upsertProvider(props.oauth.providers, nextProvider),
267
+ }, null);
268
+ };
269
+ const saveCredentials = async () => {
270
+ if (!definition) {
271
+ setCredentialMessage('The selected OAuth provider is not supported.');
272
+ return;
273
+ }
274
+ const entries = definition.secretFields.map((field) => [field.name, credentialValues[field.name] ?? '']);
275
+ if (entries.some(([, value]) => !value)) {
276
+ setCredentialMessage('Enter a complete credential payload. Existing values cannot be merged.');
277
+ return;
278
+ }
279
+ const credentialsRef = current?.credentialsRef ?? `auth/oauth/${props.providerId}`;
280
+ const intendedProvider = {
281
+ ...(current ?? {
282
+ id: props.providerId,
283
+ label: definition.label,
284
+ scopes: [...definition.defaultScopes],
285
+ }),
286
+ enabled,
287
+ credentialsRef,
288
+ };
289
+ const intendedOAuth = {
290
+ ...props.oauth,
291
+ providers: upsertProvider(props.oauth.providers, intendedProvider),
292
+ };
293
+ setSavingCredentials(true);
294
+ setCredentialMessage(null);
295
+ try {
296
+ const result = await configureProjectOAuthProvider({
297
+ projectId: props.projectId,
298
+ providerId: props.providerId,
299
+ environment: 'local',
300
+ credentialsRef,
301
+ enabled,
302
+ label: current?.label ?? definition.label,
303
+ scopes: current?.scopes ?? definition.defaultScopes,
304
+ payload: Object.freeze(Object.fromEntries(entries)),
305
+ });
306
+ if (result.ok) {
307
+ props.onSaved(mergeOAuthProviderCredentialsRef(intendedOAuth, intendedProvider, result.credentialsRef), `${definition.label} credentials saved through ${result.credentialsRef}.`);
308
+ return;
309
+ }
310
+ if (result.state === 'secret_saved_manifest_failed' &&
311
+ result.credentialsRef &&
312
+ result.metadata) {
313
+ props.onPartialFailure({
314
+ state: 'secret_saved_manifest_failed',
315
+ providerId: props.providerId,
316
+ credentialsRef: result.credentialsRef,
317
+ configuredFields: result.metadata.configuredFields,
318
+ intendedProvider: {
319
+ ...intendedProvider,
320
+ credentialsRef: result.credentialsRef,
321
+ },
322
+ });
323
+ return;
324
+ }
325
+ setCredentialMessage(result.error.message);
326
+ }
327
+ catch (error) {
328
+ setCredentialMessage(toMessage(error));
329
+ }
330
+ finally {
331
+ setCredentialValues({});
332
+ setSavingCredentials(false);
333
+ }
334
+ };
335
+ return (_jsxs(View, { style: styles.providerPanel, children: [_jsxs(View, { style: styles.providerRow, children: [_jsxs(View, { style: styles.grow, children: [_jsx(Text, { weight: "semiBold", children: definition?.label ?? props.providerId }), _jsxs(Text, { color: credentialsComplete ? 'success' : 'warning', emphasis: "muted", variant: "caption", children: [formatProviderHealthStatus(props.providerHealth?.status ?? 'missing'), current?.credentialsRef ? `: ${current.credentialsRef}` : ''] }), _jsxs(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: ["Required fields: ", props.providerHealth?.requiredFields.join(', ') || 'loading'] })] }), _jsx(Switch, { value: enabled, onValueChange: setEnabled })] }), definition?.secretFields.map((field) => (_jsx(Field, { label: field.label, children: _jsx(Input, { value: credentialValues[field.name] ?? '', secureTextEntry: field.secret, autoCapitalize: "none", placeholder: credentialsComplete ? 'Enter complete replacement value' : field.label, onChangeText: (value) => setCredentialValues((currentValues) => ({
336
+ ...currentValues,
337
+ [field.name]: value,
338
+ })) }) }, field.name))), _jsx(View, { style: styles.actions, children: _jsx(PrimaryButton, { label: credentialsComplete ? 'Replace credentials' : 'Save credentials', loading: savingCredentials, onPress: () => void saveCredentials() }) }), credentialMessage ? _jsx(Message, { text: credentialMessage }) : null] }));
339
+ }
340
+ function mergeOAuthProviderCredentialsRef(oauth, provider, credentialsRef) {
341
+ return {
342
+ ...oauth,
343
+ providers: upsertProvider(oauth.providers, {
344
+ ...provider,
345
+ credentialsRef,
346
+ }),
347
+ };
348
+ }
349
+ function upsertProvider(providers, provider) {
350
+ const index = providers.findIndex((candidate) => candidate.id === provider.id);
351
+ if (index < 0)
352
+ return [...providers, provider];
353
+ return providers.map((candidate, candidateIndex) => candidateIndex === index ? provider : candidate);
354
+ }
355
+ function updateFlow(setDraft, patch) {
356
+ setDraft((current) => ({ ...current, flow: { ...current.flow, ...patch } }));
357
+ }
358
+ function omitSignUp(settings) {
359
+ const { signUp: _signUp, ...rest } = settings;
360
+ return rest;
361
+ }
362
+ function omitProfile(settings) {
363
+ const { profile: _profile, ...rest } = settings;
364
+ return rest;
365
+ }
366
+ function createDefaultSettings() {
367
+ return {
368
+ scope: 'none',
369
+ provider: 'supabase',
370
+ flow: { ...DEFAULT_AUTH_FLOW },
371
+ signIn: { identifiers: ['email'] },
372
+ oauth: createDefaultOAuth(),
373
+ };
374
+ }
375
+ function createDefaultOAuth() {
376
+ return {
377
+ enabled: false,
378
+ callbackRoute: '/auth/callback',
379
+ providers: [],
380
+ };
381
+ }
382
+ function createFallbackManifest() {
383
+ return {
384
+ metadata: { name: 'App', slug: 'app', version: '1.0.0', themeId: 'default' },
385
+ settings: { localization: { defaultLocale: 'en', locales: ['en'] } },
386
+ infra: { plugins: [] },
387
+ navigator: { type: 'stack', routes: [] },
388
+ screens: {},
389
+ themes: [],
390
+ activeThemeId: 'default',
391
+ };
392
+ }
393
+ function splitList(value) {
394
+ return [
395
+ ...new Set(value
396
+ .split(',')
397
+ .map((entry) => entry.trim())
398
+ .filter(Boolean)),
399
+ ];
400
+ }
401
+ function toMessage(error) {
402
+ if (error instanceof ProjectAuthApiError)
403
+ return error.message;
404
+ return error instanceof Error ? error.message : 'Authentication configuration request failed.';
405
+ }
406
+ function AuthHealthCard({ health }) {
407
+ if (!health) {
408
+ return (_jsx(Card, { title: "Health", children: _jsx(Text, { color: "neutral", emphasis: "muted", variant: "bodySmall", children: "Auth health is unavailable." }) }));
409
+ }
410
+ return (_jsxs(Card, { title: "Health", children: [_jsx(Text, { color: healthStatusColor(health.status), weight: "semiBold", children: formatHealthStatus(health.status) }), _jsx(KeyValue, { label: "Callback route", value: health.callbackUrls.appCallbackRoute }), health.callbackUrls.providerRedirectUrl ? (_jsx(KeyValue, { label: "Provider redirect URL", value: health.callbackUrls.providerRedirectUrl })) : null, health.providers.map((provider) => (_jsxs(View, { style: styles.healthProvider, children: [_jsxs(Text, { weight: "semiBold", children: [provider.label, ": ", formatProviderHealthStatus(provider.status)] }), _jsxs(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: ["Ref: ", provider.credentialsRef ?? 'none'] }), _jsxs(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: ["Required fields: ", provider.requiredFields.join(', ') || 'none'] }), _jsxs(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: ["Configured fields: ", provider.configuredFields.join(', ') || 'none'] }), provider.missingFields.length > 0 ? (_jsxs(Text, { color: "warning", variant: "caption", children: ["Missing fields: ", provider.missingFields.join(', ')] })) : null] }, provider.providerId))), health.diagnostics.map((diagnostic) => (_jsxs(View, { style: styles.diagnostic, children: [_jsx(Text, { color: diagnosticSeverityColor(diagnostic.severity), variant: "bodySmall", children: diagnostic.code }), _jsx(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: diagnostic.message })] }, `${diagnostic.code}:${diagnostic.path ?? ''}`))), health.diagnostics.length === 0 ? (_jsx(Text, { color: "success", variant: "bodySmall", children: "No auth diagnostics." })) : null] }));
411
+ }
412
+ function formatHealthStatus(status) {
413
+ if (status === 'healthy')
414
+ return 'Healthy';
415
+ if (status === 'warning')
416
+ return 'Warning';
417
+ if (status === 'error')
418
+ return 'Error';
419
+ return 'Not configured';
420
+ }
421
+ function formatProviderHealthStatus(status) {
422
+ if (status === 'configured')
423
+ return 'Configured';
424
+ if (status === 'incomplete')
425
+ return 'Incomplete';
426
+ if (status === 'missing')
427
+ return 'Missing secret';
428
+ if (status === 'invalid')
429
+ return 'Invalid';
430
+ return 'Disabled';
431
+ }
432
+ function healthStatusColor(status) {
433
+ if (status === 'healthy')
434
+ return 'success';
435
+ if (status === 'warning')
436
+ return 'warning';
437
+ if (status === 'error')
438
+ return 'danger';
439
+ return 'neutral';
440
+ }
441
+ function diagnosticSeverityColor(severity) {
442
+ if (severity === 'error')
443
+ return 'danger';
444
+ if (severity === 'warning')
445
+ return 'warning';
446
+ return 'neutral';
447
+ }
448
+ function Card(props) {
449
+ const { theme } = useZoraTheme();
450
+ return (_jsxs(View, { style: [
451
+ styles.card,
452
+ { backgroundColor: theme.colors.surface, borderColor: theme.colors.border },
453
+ ], children: [_jsx(Heading, { level: 3, text: props.title }), props.children] }));
454
+ }
455
+ function Field(props) {
456
+ return (_jsxs(View, { style: styles.field, children: [_jsx(Text, { variant: "bodySmall", weight: "semiBold", children: props.label }), props.children] }));
457
+ }
458
+ function RouteField(props) {
459
+ return (_jsx(Field, { label: props.label, children: _jsx(Input, { value: props.value, autoCapitalize: "none", onChangeText: props.onChange }) }));
460
+ }
461
+ function Input(props) {
462
+ const { theme } = useZoraTheme();
463
+ return (_jsx(TextInput, { ...props, placeholderTextColor: theme.colors.textMuted, style: [
464
+ styles.input,
465
+ {
466
+ color: theme.colors.text,
467
+ backgroundColor: theme.colors.background,
468
+ borderColor: theme.colors.border,
469
+ },
470
+ props.style,
471
+ ] }));
472
+ }
473
+ function SwitchSetting(props) {
474
+ return (_jsxs(View, { style: styles.switchRow, children: [_jsxs(View, { style: styles.grow, children: [_jsx(Text, { weight: "semiBold", children: props.title }), _jsx(Text, { color: "neutral", emphasis: "muted", variant: "caption", children: props.description })] }), _jsx(Switch, { value: props.value, onValueChange: props.onValueChange })] }));
475
+ }
476
+ function Choice(props) {
477
+ const { theme } = useZoraTheme();
478
+ return (_jsx(Pressable, { onPress: props.onPress, style: [
479
+ styles.choice,
480
+ {
481
+ borderColor: props.selected ? theme.colors.primary : theme.colors.border,
482
+ backgroundColor: props.selected ? theme.colors.surface : theme.colors.background,
483
+ },
484
+ ], children: _jsx(Text, { color: props.selected ? 'primary' : 'neutral', variant: "bodySmall", weight: "semiBold", children: props.label }) }));
485
+ }
486
+ function KeyValue(props) {
487
+ return (_jsxs(View, { style: styles.keyValue, children: [_jsx(Text, { color: "neutral", emphasis: "muted", variant: "bodySmall", children: props.label }), _jsx(Text, { weight: "semiBold", children: props.value })] }));
488
+ }
489
+ function PrimaryButton(props) {
490
+ const { theme } = useZoraTheme();
491
+ return (_jsx(Pressable, { disabled: props.loading, onPress: props.onPress, style: [styles.primaryButton, { backgroundColor: theme.colors.primary }], children: props.loading ? (_jsx(ActivityIndicator, { color: "#ffffff" })) : (_jsx(Text, { emphasis: "inverse", weight: "semiBold", children: props.label })) }));
492
+ }
493
+ function SecondaryButton(props) {
494
+ const { theme } = useZoraTheme();
495
+ return (_jsx(Pressable, { onPress: props.onPress, style: [styles.secondaryButton, { borderColor: theme.colors.border }], children: _jsx(Text, { weight: "semiBold", children: props.label }) }));
496
+ }
497
+ function Message({ text }) {
498
+ return (_jsx(Text, { color: "info", variant: "bodySmall", children: text }));
499
+ }
500
+ const styles = StyleSheet.create({
501
+ overlay: {
502
+ ...StyleSheet.absoluteFillObject,
503
+ zIndex: 1000,
504
+ borderWidth: 1,
505
+ },
506
+ header: {
507
+ minHeight: 72,
508
+ paddingHorizontal: 20,
509
+ paddingVertical: 12,
510
+ borderBottomWidth: 1,
511
+ flexDirection: 'row',
512
+ alignItems: 'center',
513
+ gap: 16,
514
+ },
515
+ content: {
516
+ width: '100%',
517
+ maxWidth: 920,
518
+ alignSelf: 'center',
519
+ padding: 20,
520
+ gap: 16,
521
+ },
522
+ card: {
523
+ borderWidth: 1,
524
+ borderRadius: 14,
525
+ padding: 16,
526
+ gap: 14,
527
+ },
528
+ grow: { flex: 1 },
529
+ field: { gap: 6 },
530
+ input: {
531
+ minHeight: 42,
532
+ borderWidth: 1,
533
+ borderRadius: 8,
534
+ paddingHorizontal: 12,
535
+ paddingVertical: 8,
536
+ },
537
+ switchRow: {
538
+ flexDirection: 'row',
539
+ alignItems: 'center',
540
+ gap: 16,
541
+ },
542
+ choiceRow: {
543
+ flexDirection: 'row',
544
+ flexWrap: 'wrap',
545
+ gap: 8,
546
+ },
547
+ choice: {
548
+ borderWidth: 1,
549
+ borderRadius: 999,
550
+ paddingHorizontal: 12,
551
+ paddingVertical: 8,
552
+ },
553
+ providerRow: {
554
+ flexDirection: 'row',
555
+ alignItems: 'center',
556
+ gap: 16,
557
+ paddingVertical: 8,
558
+ },
559
+ providerPanel: {
560
+ gap: 10,
561
+ paddingVertical: 8,
562
+ },
563
+ partialFailure: {
564
+ gap: 8,
565
+ paddingVertical: 10,
566
+ },
567
+ healthProvider: {
568
+ gap: 3,
569
+ paddingVertical: 8,
570
+ },
571
+ diagnostic: {
572
+ gap: 3,
573
+ paddingVertical: 6,
574
+ },
575
+ keyValue: {
576
+ flexDirection: 'row',
577
+ alignItems: 'center',
578
+ justifyContent: 'space-between',
579
+ gap: 16,
580
+ },
581
+ actions: {
582
+ flexDirection: 'row',
583
+ justifyContent: 'flex-end',
584
+ gap: 10,
585
+ },
586
+ footerActions: {
587
+ flexDirection: 'row',
588
+ justifyContent: 'flex-end',
589
+ gap: 10,
590
+ },
591
+ primaryButton: {
592
+ minHeight: 42,
593
+ borderRadius: 8,
594
+ paddingHorizontal: 16,
595
+ alignItems: 'center',
596
+ justifyContent: 'center',
597
+ },
598
+ secondaryButton: {
599
+ minHeight: 42,
600
+ borderWidth: 1,
601
+ borderRadius: 8,
602
+ paddingHorizontal: 16,
603
+ alignItems: 'center',
604
+ justifyContent: 'center',
605
+ },
606
+ });
607
+ //# sourceMappingURL=StudioAuthSettingsOverlay.js.map