@svadmin/core 0.19.1 → 0.19.2
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/package.json +1 -1
- package/src/auth-hooks.svelte.ts +52 -123
- package/src/context.svelte.ts +1 -2
- package/src/form-hooks.svelte.ts +6 -7
- package/src/i18n.svelte.ts +1 -0
- package/src/theme.svelte.ts +4 -4
package/package.json
CHANGED
package/src/auth-hooks.svelte.ts
CHANGED
|
@@ -4,32 +4,42 @@
|
|
|
4
4
|
|
|
5
5
|
import { getAuthProvider } from './context.svelte';
|
|
6
6
|
import { navigate } from './router';
|
|
7
|
-
import {
|
|
7
|
+
import { notify } from './notification.svelte';
|
|
8
8
|
import { t } from './i18n.svelte';
|
|
9
|
-
import type { AuthActionResult, CheckResult, Identity } from './types';
|
|
9
|
+
import type { AuthActionResult, CheckResult, Identity, AuthProvider } from './types';
|
|
10
10
|
|
|
11
|
-
// ───
|
|
11
|
+
// ─── Mutate Factory ─────────────────────────────────────────────
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
interface CreateAuthMutationOptions {
|
|
14
|
+
method: keyof AuthProvider;
|
|
15
|
+
successMessage?: string | null;
|
|
16
|
+
errorMessage?: string;
|
|
17
|
+
onSuccess?: (result: AuthActionResult) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createAuthMutation(options: CreateAuthMutationOptions) {
|
|
14
21
|
const provider = getAuthProvider();
|
|
15
22
|
let isLoading = $state(false);
|
|
16
23
|
|
|
17
|
-
async function mutate(params
|
|
24
|
+
async function mutate(params?: Record<string, unknown>): Promise<AuthActionResult> {
|
|
18
25
|
if (!provider) throw new Error('AuthProvider not configured');
|
|
26
|
+
const fn = provider[options.method] as ((params?: Record<string, unknown>) => Promise<AuthActionResult>) | undefined;
|
|
27
|
+
if (!fn) throw new Error(`AuthProvider.${options.method} not implemented`);
|
|
28
|
+
|
|
19
29
|
isLoading = true;
|
|
20
30
|
try {
|
|
21
|
-
const result = await
|
|
31
|
+
const result = await fn.call(provider, params);
|
|
22
32
|
if (result.success) {
|
|
23
|
-
|
|
24
|
-
if (
|
|
33
|
+
if (options.successMessage) notify({ type: 'success', message: options.successMessage });
|
|
34
|
+
if (options.onSuccess) options.onSuccess(result);
|
|
25
35
|
} else {
|
|
26
|
-
const msg = result.error?.message ?? t('common.
|
|
27
|
-
|
|
36
|
+
const msg = result.error?.message ?? options.errorMessage ?? t('common.operationFailed');
|
|
37
|
+
notify({ type: 'error', message: msg });
|
|
28
38
|
}
|
|
29
39
|
return result;
|
|
30
40
|
} catch (err) {
|
|
31
|
-
const msg = err instanceof Error ? err.message : t('common.
|
|
32
|
-
|
|
41
|
+
const msg = err instanceof Error ? err.message : (options.errorMessage ?? t('common.operationFailed'));
|
|
42
|
+
notify({ type: 'error', message: msg });
|
|
33
43
|
return { success: false, error: { message: msg } };
|
|
34
44
|
} finally {
|
|
35
45
|
isLoading = false;
|
|
@@ -42,135 +52,54 @@ export function useLogin() {
|
|
|
42
52
|
};
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
// ───
|
|
55
|
+
// ─── useLogin ─────────────────────────────────────────────────
|
|
46
56
|
|
|
47
|
-
export function
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
export function useLogin() {
|
|
58
|
+
return createAuthMutation({
|
|
59
|
+
method: 'login',
|
|
60
|
+
successMessage: t('common.operationSuccess'),
|
|
61
|
+
errorMessage: t('common.loginFailed'),
|
|
62
|
+
onSuccess: (result) => { if (result.redirectTo) navigate(result.redirectTo); }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
50
65
|
|
|
51
|
-
|
|
52
|
-
if (!provider) throw new Error('AuthProvider not configured');
|
|
53
|
-
isLoading = true;
|
|
54
|
-
try {
|
|
55
|
-
const result = await provider.logout(params);
|
|
56
|
-
if (result.success) {
|
|
57
|
-
navigate(result.redirectTo ?? '/login');
|
|
58
|
-
}
|
|
59
|
-
return result;
|
|
60
|
-
} catch (err) {
|
|
61
|
-
const msg = err instanceof Error ? err.message : t('common.operationFailed');
|
|
62
|
-
toast.error(msg);
|
|
63
|
-
return { success: false, error: { message: msg } };
|
|
64
|
-
} finally {
|
|
65
|
-
isLoading = false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
66
|
+
// ─── useLogout ────────────────────────────────────────────────
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
export function useLogout() {
|
|
69
|
+
return createAuthMutation({
|
|
70
|
+
method: 'logout',
|
|
71
|
+
successMessage: null,
|
|
72
|
+
onSuccess: (result) => { navigate(result.redirectTo ?? '/login'); }
|
|
73
|
+
});
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
// ─── useRegister ──────────────────────────────────────────────
|
|
76
77
|
|
|
77
78
|
export function useRegister() {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
isLoading = true;
|
|
84
|
-
try {
|
|
85
|
-
const result = await provider.register(params);
|
|
86
|
-
if (result.success) {
|
|
87
|
-
toast.success(t('auth.registerSuccess'));
|
|
88
|
-
if (result.redirectTo) navigate(result.redirectTo);
|
|
89
|
-
} else {
|
|
90
|
-
const msg = result.error?.message ?? t('common.operationFailed');
|
|
91
|
-
toast.error(msg);
|
|
92
|
-
}
|
|
93
|
-
return result;
|
|
94
|
-
} catch (err) {
|
|
95
|
-
const msg = err instanceof Error ? err.message : t('common.operationFailed');
|
|
96
|
-
toast.error(msg);
|
|
97
|
-
return { success: false, error: { message: msg } };
|
|
98
|
-
} finally {
|
|
99
|
-
isLoading = false;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
mutate,
|
|
105
|
-
get isLoading() { return isLoading; },
|
|
106
|
-
};
|
|
79
|
+
return createAuthMutation({
|
|
80
|
+
method: 'register',
|
|
81
|
+
successMessage: t('auth.registerSuccess'),
|
|
82
|
+
onSuccess: (result) => { if (result.redirectTo) navigate(result.redirectTo); }
|
|
83
|
+
});
|
|
107
84
|
}
|
|
108
85
|
|
|
109
86
|
// ─── useForgotPassword ───────────────────────────────────────
|
|
110
87
|
|
|
111
88
|
export function useForgotPassword() {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (!provider?.forgotPassword) throw new Error('AuthProvider.forgotPassword not implemented');
|
|
117
|
-
isLoading = true;
|
|
118
|
-
try {
|
|
119
|
-
const result = await provider.forgotPassword(params);
|
|
120
|
-
if (result.success) {
|
|
121
|
-
toast.success(t('auth.resetLinkSent'));
|
|
122
|
-
} else {
|
|
123
|
-
const msg = result.error?.message ?? t('common.operationFailed');
|
|
124
|
-
toast.error(msg);
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
} catch (err) {
|
|
128
|
-
const msg = err instanceof Error ? err.message : t('common.operationFailed');
|
|
129
|
-
toast.error(msg);
|
|
130
|
-
return { success: false, error: { message: msg } };
|
|
131
|
-
} finally {
|
|
132
|
-
isLoading = false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
mutate,
|
|
138
|
-
get isLoading() { return isLoading; },
|
|
139
|
-
};
|
|
89
|
+
return createAuthMutation({
|
|
90
|
+
method: 'forgotPassword',
|
|
91
|
+
successMessage: t('auth.resetLinkSent')
|
|
92
|
+
});
|
|
140
93
|
}
|
|
141
94
|
|
|
142
95
|
// ─── useUpdatePassword ───────────────────────────────────────
|
|
143
96
|
|
|
144
97
|
export function useUpdatePassword() {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
isLoading = true;
|
|
151
|
-
try {
|
|
152
|
-
const result = await provider.updatePassword(params);
|
|
153
|
-
if (result.success) {
|
|
154
|
-
toast.success(t('common.operationSuccess'));
|
|
155
|
-
if (result.redirectTo) navigate(result.redirectTo);
|
|
156
|
-
} else {
|
|
157
|
-
const msg = result.error?.message ?? t('common.operationFailed');
|
|
158
|
-
toast.error(msg);
|
|
159
|
-
}
|
|
160
|
-
return result;
|
|
161
|
-
} catch (err) {
|
|
162
|
-
const msg = err instanceof Error ? err.message : t('common.operationFailed');
|
|
163
|
-
toast.error(msg);
|
|
164
|
-
return { success: false, error: { message: msg } };
|
|
165
|
-
} finally {
|
|
166
|
-
isLoading = false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
mutate,
|
|
172
|
-
get isLoading() { return isLoading; },
|
|
173
|
-
};
|
|
98
|
+
return createAuthMutation({
|
|
99
|
+
method: 'updatePassword',
|
|
100
|
+
successMessage: t('common.operationSuccess'),
|
|
101
|
+
onSuccess: (result) => { if (result.redirectTo) navigate(result.redirectTo); }
|
|
102
|
+
});
|
|
174
103
|
}
|
|
175
104
|
|
|
176
105
|
// ─── useGetIdentity ──────────────────────────────────────────
|
package/src/context.svelte.ts
CHANGED
|
@@ -86,8 +86,7 @@ export function setResources(newResources: ResourceDefinition[]): void {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export function getResources(): ResourceDefinition[] {
|
|
89
|
-
|
|
90
|
-
return resources;
|
|
89
|
+
return resources ?? [];
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
export function getResource(nameOrIdentifier: string): ResourceDefinition {
|
package/src/form-hooks.svelte.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { useParsed } from './useParsed.svelte';
|
|
|
3
3
|
import { getAdminOptions } from './options.svelte';
|
|
4
4
|
import { getDataProviderForResource } from './context.svelte';
|
|
5
5
|
import { createQuery, createMutation } from '@tanstack/svelte-query';
|
|
6
|
-
import {
|
|
6
|
+
import { notify } from './notification.svelte';
|
|
7
7
|
import { t } from './i18n.svelte';
|
|
8
8
|
import { audit } from './audit';
|
|
9
9
|
import { navigate } from './router';
|
|
@@ -245,9 +245,9 @@ export function useForm<
|
|
|
245
245
|
const msg = Array.isArray(messages) ? messages[0] : messages;
|
|
246
246
|
setFieldError(field, msg);
|
|
247
247
|
}
|
|
248
|
-
if (errorNotification !== false)
|
|
248
|
+
if (errorNotification !== false) notify({ type: 'error', message: typeof errorNotification === 'string' ? errorNotification : (error.message || t('common.operationFailed')) });
|
|
249
249
|
} else {
|
|
250
|
-
if (errorNotification !== false)
|
|
250
|
+
if (errorNotification !== false) notify({ type: 'error', message: typeof errorNotification === 'string' ? errorNotification : (t('common.operationFailed') + ': ' + error.message) });
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -288,7 +288,7 @@ export function useForm<
|
|
|
288
288
|
mutationFn: (variables: TVariables) => provider.create<TData, TVariables>({ resource, variables, meta: mutationMeta }),
|
|
289
289
|
onSuccess: (data: { data: TData }) => {
|
|
290
290
|
if (invalidateScopes !== false) queryClient.invalidateQueries({ queryKey: [resource] });
|
|
291
|
-
if (successNotification !== false)
|
|
291
|
+
if (successNotification !== false) notify({ type: 'success', message: typeof successNotification === 'string' ? successNotification : t('common.createSuccess') });
|
|
292
292
|
audit({ action: 'create', resource, recordId: String((data.data as Record<string, unknown>).id) });
|
|
293
293
|
onMutationSuccess?.(data);
|
|
294
294
|
if (redirectOverride !== false) doRedirect(redirectOverride ?? redirectDefault);
|
|
@@ -301,7 +301,7 @@ export function useForm<
|
|
|
301
301
|
mutationFn: (variables: TVariables) => provider.update<TData, TVariables>({ resource, id: currentId!, variables, meta: mutationMeta }),
|
|
302
302
|
onSuccess: (data: { data: TData }) => {
|
|
303
303
|
if (invalidateScopes !== false) queryClient.invalidateQueries({ queryKey: [resource] });
|
|
304
|
-
if (successNotification !== false)
|
|
304
|
+
if (successNotification !== false) notify({ type: 'success', message: typeof successNotification === 'string' ? successNotification : t('common.updateSuccess') });
|
|
305
305
|
audit({ action: 'update', resource, recordId: String(currentId) });
|
|
306
306
|
onMutationSuccess?.(data);
|
|
307
307
|
if (redirectOverride !== false) doRedirect(redirectOverride ?? redirectDefault);
|
|
@@ -324,7 +324,7 @@ export function useForm<
|
|
|
324
324
|
if (result === false || cancelled) return;
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
if (!runValidation()) {
|
|
327
|
+
if (!runValidation()) { notify({ type: 'warning', message: t('validation.required') }); return; }
|
|
328
328
|
redirectOverride = overrides?.redirect;
|
|
329
329
|
try {
|
|
330
330
|
if (action === 'create' || action === 'clone') await createMut.mutateAsync(values);
|
|
@@ -334,7 +334,6 @@ export function useForm<
|
|
|
334
334
|
} catch (error) {
|
|
335
335
|
// Errors are already handled by the mutation's onError callback.
|
|
336
336
|
// We catch here to prevent unhandled promise rejections.
|
|
337
|
-
handleHttpError(error instanceof Error ? error : new Error(String(error)));
|
|
338
337
|
}
|
|
339
338
|
}
|
|
340
339
|
|
package/src/i18n.svelte.ts
CHANGED
|
@@ -477,6 +477,7 @@ const locales: Record<string, Locale> = {
|
|
|
477
477
|
|
|
478
478
|
/** Detect best locale from browser language */
|
|
479
479
|
function detectLocale(): string {
|
|
480
|
+
if (typeof navigator === 'undefined') return 'en'; // Safe default for SSR
|
|
480
481
|
const browserLang = navigator.language || navigator.languages?.[0] || 'zh-CN';
|
|
481
482
|
// Exact match
|
|
482
483
|
if (locales[browserLang]) return browserLang;
|
package/src/theme.svelte.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface ColorPreset {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** Built-in color presets — consumers can extend via registerColorPreset(). */
|
|
30
|
-
export const builtinPresets: Record<string, ColorPreset> = {
|
|
30
|
+
export const builtinPresets: Record<string, ColorPreset> = $state({
|
|
31
31
|
neutral: {
|
|
32
32
|
name: 'neutral',
|
|
33
33
|
label: 'Neutral',
|
|
@@ -189,7 +189,7 @@ export const builtinPresets: Record<string, ColorPreset> = {
|
|
|
189
189
|
'--chart-1': 'oklch(0.541 0.281 293.009)',
|
|
190
190
|
},
|
|
191
191
|
},
|
|
192
|
-
};
|
|
192
|
+
});
|
|
193
193
|
|
|
194
194
|
/** Register a custom color preset. Overwrites any built-in preset with the same name. */
|
|
195
195
|
export function registerColorPreset(preset: ColorPreset): void {
|
|
@@ -292,11 +292,11 @@ export function clearCssOverrides(keys?: string[]): void {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// ── Color themes (display metadata, derived from presets) ──
|
|
295
|
-
export const colorThemes: { id: ColorTheme | string; label: string; color: string }[] = Object.values(builtinPresets).map(p => ({
|
|
295
|
+
export const colorThemes: { id: ColorTheme | string; label: string; color: string }[] = $state(Object.values(builtinPresets).map(p => ({
|
|
296
296
|
id: p.name as ColorTheme,
|
|
297
297
|
label: p.label,
|
|
298
298
|
color: p.color,
|
|
299
|
-
}));
|
|
299
|
+
})));
|
|
300
300
|
|
|
301
301
|
// ── Dark/Light mode ──────────────────────────────────────
|
|
302
302
|
|