@nebulr-group/bridge-svelte 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/auth/route-guard.d.ts +2 -0
- package/dist/auth/route-guard.js +28 -0
- package/dist/client/BridgeBootstrap.d.ts +8 -0
- package/dist/client/BridgeBootstrap.js +105 -0
- package/dist/client/BridgeBootstrap.svelte +43 -0
- package/dist/client/BridgeBootstrap.svelte.d.ts +6 -0
- package/dist/client/components/FeatureFlag.svelte +37 -0
- package/dist/client/components/FeatureFlag.svelte.d.ts +15 -0
- package/dist/client/components/ProfileName.svelte +18 -0
- package/dist/client/components/ProfileName.svelte.d.ts +6 -0
- package/dist/client/components/developer/ApiTokenManagement.svelte +365 -0
- package/dist/client/components/developer/ApiTokenManagement.svelte.d.ts +6 -0
- package/dist/client/components/sdk-auth/ForgotPassword.svelte +170 -0
- package/dist/client/components/sdk-auth/ForgotPassword.svelte.d.ts +10 -0
- package/dist/client/components/sdk-auth/LoginForm.svelte +414 -0
- package/dist/client/components/sdk-auth/LoginForm.svelte.d.ts +25 -0
- package/dist/client/components/sdk-auth/MagicLink.svelte +92 -0
- package/dist/client/components/sdk-auth/MagicLink.svelte.d.ts +9 -0
- package/dist/client/components/sdk-auth/MfaChallenge.svelte +150 -0
- package/dist/client/components/sdk-auth/MfaChallenge.svelte.d.ts +9 -0
- package/dist/client/components/sdk-auth/MfaSetup.svelte +182 -0
- package/dist/client/components/sdk-auth/MfaSetup.svelte.d.ts +8 -0
- package/dist/client/components/sdk-auth/PasskeyLogin.svelte +99 -0
- package/dist/client/components/sdk-auth/PasskeyLogin.svelte.d.ts +10 -0
- package/dist/client/components/sdk-auth/PasskeyRequestSetupLink.svelte +83 -0
- package/dist/client/components/sdk-auth/PasskeyRequestSetupLink.svelte.d.ts +7 -0
- package/dist/client/components/sdk-auth/PasskeySetup.svelte +151 -0
- package/dist/client/components/sdk-auth/PasskeySetup.svelte.d.ts +11 -0
- package/dist/client/components/sdk-auth/SignupForm.svelte +119 -0
- package/dist/client/components/sdk-auth/SignupForm.svelte.d.ts +14 -0
- package/dist/client/components/sdk-auth/SsoButton.svelte +70 -0
- package/dist/client/components/sdk-auth/SsoButton.svelte.d.ts +13 -0
- package/dist/client/components/sdk-auth/SsoProviderIcon.svelte +56 -0
- package/dist/client/components/sdk-auth/SsoProviderIcon.svelte.d.ts +7 -0
- package/dist/client/components/sdk-auth/TenantSelector.svelte +83 -0
- package/dist/client/components/sdk-auth/TenantSelector.svelte.d.ts +11 -0
- package/dist/client/components/sdk-auth/WorkspaceSelector.svelte +108 -0
- package/dist/client/components/sdk-auth/WorkspaceSelector.svelte.d.ts +16 -0
- package/dist/client/components/sdk-auth/shared/Alert.svelte +17 -0
- package/dist/client/components/sdk-auth/shared/Alert.svelte.d.ts +9 -0
- package/dist/client/components/sdk-auth/shared/AuthFormWrapper.svelte +23 -0
- package/dist/client/components/sdk-auth/shared/AuthFormWrapper.svelte.d.ts +10 -0
- package/dist/client/components/sdk-auth/shared/Spinner.svelte +16 -0
- package/dist/client/components/sdk-auth/shared/Spinner.svelte.d.ts +7 -0
- package/dist/client/components/subscription/PlanSelector.svelte +204 -0
- package/dist/client/components/subscription/PlanSelector.svelte.d.ts +22 -0
- package/dist/client/components/team/TeamAddUserDialog.svelte +125 -0
- package/dist/client/components/team/TeamAddUserDialog.svelte.d.ts +17 -0
- package/dist/client/components/team/TeamConfirmDialog.svelte +80 -0
- package/dist/client/components/team/TeamConfirmDialog.svelte.d.ts +21 -0
- package/dist/client/components/team/TeamEditUserDialog.svelte +131 -0
- package/dist/client/components/team/TeamEditUserDialog.svelte.d.ts +19 -0
- package/dist/client/components/team/TeamManagementPanel.svelte +72 -0
- package/dist/client/components/team/TeamManagementPanel.svelte.d.ts +20 -0
- package/dist/client/components/team/TeamProfileForm.svelte +129 -0
- package/dist/client/components/team/TeamProfileForm.svelte.d.ts +7 -0
- package/dist/client/components/team/TeamUserActionsMenu.svelte +63 -0
- package/dist/client/components/team/TeamUserActionsMenu.svelte.d.ts +9 -0
- package/dist/client/components/team/TeamUserList.svelte +226 -0
- package/dist/client/components/team/TeamUserList.svelte.d.ts +7 -0
- package/dist/client/components/team/TeamWorkspaceForm.svelte +134 -0
- package/dist/client/components/team/TeamWorkspaceForm.svelte.d.ts +7 -0
- package/dist/client/stores/config.store.d.ts +6 -0
- package/dist/client/stores/config.store.js +76 -0
- package/dist/client/utils/cn.d.ts +1 -0
- package/dist/client/utils/cn.js +3 -0
- package/dist/core/bridge-instance.d.ts +52 -0
- package/dist/core/bridge-instance.js +160 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +39 -0
- package/dist/shared/feature-flag.d.ts +6 -0
- package/dist/shared/feature-flag.js +13 -0
- package/dist/shared/logger.d.ts +18 -0
- package/dist/shared/logger.js +32 -0
- package/dist/shared/profile.d.ts +18 -0
- package/dist/shared/profile.js +25 -0
- package/dist/shared/types/config.d.ts +42 -0
- package/dist/shared/types/config.js +1 -0
- package/dist/styles.css +1089 -0
- package/package.json +86 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Tab = {
|
|
4
|
+
id: 'users' | 'profile' | 'workspace';
|
|
5
|
+
label: string;
|
|
6
|
+
};
|
|
7
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
defaultTab?: 'users' | 'profile' | 'workspace';
|
|
9
|
+
showProfileTab?: boolean;
|
|
10
|
+
showWorkspaceTab?: boolean;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
tabBar?: Snippet<[{
|
|
13
|
+
tabs: Tab[];
|
|
14
|
+
activeTab: string;
|
|
15
|
+
setTab: (id: string) => void;
|
|
16
|
+
}]>;
|
|
17
|
+
}
|
|
18
|
+
declare const TeamManagementPanel: import("svelte").Component<Props, {}, "">;
|
|
19
|
+
type TeamManagementPanel = ReturnType<typeof TeamManagementPanel>;
|
|
20
|
+
export default TeamManagementPanel;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { TeamProfile } from '@thebridge/auth-core';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { onMount } from 'svelte';
|
|
5
|
+
import { getBridgeAuth } from '../../../core/bridge-instance.js';
|
|
6
|
+
import Alert from '../sdk-auth/shared/Alert.svelte';
|
|
7
|
+
import Spinner from '../sdk-auth/shared/Spinner.svelte';
|
|
8
|
+
|
|
9
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
onError?: (error: Error) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
onError,
|
|
15
|
+
class: className,
|
|
16
|
+
style,
|
|
17
|
+
...rest
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
let profile = $state<TeamProfile | null>(null);
|
|
21
|
+
let firstName = $state('');
|
|
22
|
+
let lastName = $state('');
|
|
23
|
+
let loading = $state(true);
|
|
24
|
+
let saving = $state(false);
|
|
25
|
+
let error = $state<string | null>(null);
|
|
26
|
+
let success = $state<string | null>(null);
|
|
27
|
+
|
|
28
|
+
onMount(() => {
|
|
29
|
+
loadProfile();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
async function loadProfile() {
|
|
33
|
+
loading = true;
|
|
34
|
+
error = null;
|
|
35
|
+
try {
|
|
36
|
+
const bridge = getBridgeAuth();
|
|
37
|
+
profile = await bridge.team.getProfile();
|
|
38
|
+
firstName = profile.firstName ?? '';
|
|
39
|
+
lastName = profile.lastName ?? '';
|
|
40
|
+
} catch (err) {
|
|
41
|
+
const e = err instanceof Error ? err : new Error('Failed to load profile');
|
|
42
|
+
error = e.message;
|
|
43
|
+
onError?.(e);
|
|
44
|
+
} finally {
|
|
45
|
+
loading = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function handleSubmit() {
|
|
50
|
+
saving = true;
|
|
51
|
+
error = null;
|
|
52
|
+
success = null;
|
|
53
|
+
try {
|
|
54
|
+
const bridge = getBridgeAuth();
|
|
55
|
+
profile = await bridge.team.updateProfile({ firstName, lastName });
|
|
56
|
+
success = 'Profile updated successfully.';
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const e = err instanceof Error ? err : new Error('Failed to update profile');
|
|
59
|
+
error = e.message;
|
|
60
|
+
onError?.(e);
|
|
61
|
+
} finally {
|
|
62
|
+
saving = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<div class={className} {style} data-bridge-team-profile {...rest}>
|
|
68
|
+
<h3 class="bridge-team-section-title">My Profile</h3>
|
|
69
|
+
|
|
70
|
+
{#if loading}
|
|
71
|
+
<div class="bridge-team-loading">
|
|
72
|
+
<Spinner size={32} />
|
|
73
|
+
<span>Loading profile...</span>
|
|
74
|
+
</div>
|
|
75
|
+
{:else}
|
|
76
|
+
{#if error}
|
|
77
|
+
<div class="bridge-team-alert"><Alert variant="error">{error}</Alert></div>
|
|
78
|
+
{/if}
|
|
79
|
+
{#if success}
|
|
80
|
+
<div class="bridge-team-alert"><Alert variant="success">{success}</Alert></div>
|
|
81
|
+
{/if}
|
|
82
|
+
|
|
83
|
+
<form class="bridge-team-form" onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
|
84
|
+
<div class="bridge-team-form-group">
|
|
85
|
+
<label for="bridge-profile-email">Email</label>
|
|
86
|
+
<input id="bridge-profile-email" type="email" value={profile?.email ?? ''} disabled />
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div class="bridge-team-form-row">
|
|
90
|
+
<div class="bridge-team-form-group">
|
|
91
|
+
<label for="bridge-profile-first-name">First Name</label>
|
|
92
|
+
<input
|
|
93
|
+
id="bridge-profile-first-name"
|
|
94
|
+
type="text"
|
|
95
|
+
bind:value={firstName}
|
|
96
|
+
disabled={saving}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="bridge-team-form-group">
|
|
101
|
+
<label for="bridge-profile-last-name">Last Name</label>
|
|
102
|
+
<input
|
|
103
|
+
id="bridge-profile-last-name"
|
|
104
|
+
type="text"
|
|
105
|
+
bind:value={lastName}
|
|
106
|
+
disabled={saving}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{#if profile?.role}
|
|
112
|
+
<div class="bridge-team-form-group">
|
|
113
|
+
<label>Role</label>
|
|
114
|
+
<div class="bridge-team-readonly">{profile.role}</div>
|
|
115
|
+
</div>
|
|
116
|
+
{/if}
|
|
117
|
+
|
|
118
|
+
<div class="bridge-team-form-actions">
|
|
119
|
+
<button
|
|
120
|
+
type="submit"
|
|
121
|
+
class="bridge-btn bridge-btn-primary"
|
|
122
|
+
disabled={saving}
|
|
123
|
+
>
|
|
124
|
+
{saving ? 'Saving...' : 'Save Changes'}
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
</form>
|
|
128
|
+
{/if}
|
|
129
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
onError?: (error: Error) => void;
|
|
4
|
+
}
|
|
5
|
+
declare const TeamProfileForm: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type TeamProfileForm = ReturnType<typeof TeamProfileForm>;
|
|
7
|
+
export default TeamProfileForm;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
onedit?: () => void;
|
|
6
|
+
onresetpassword?: () => void;
|
|
7
|
+
ondelete?: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
onedit,
|
|
12
|
+
onresetpassword,
|
|
13
|
+
ondelete,
|
|
14
|
+
class: className,
|
|
15
|
+
style,
|
|
16
|
+
...rest
|
|
17
|
+
}: Props = $props();
|
|
18
|
+
|
|
19
|
+
let isOpen = $state(false);
|
|
20
|
+
let menuEl: HTMLDivElement | undefined = $state();
|
|
21
|
+
|
|
22
|
+
function toggle(e: MouseEvent) {
|
|
23
|
+
e.stopPropagation();
|
|
24
|
+
isOpen = !isOpen;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleAction(fn?: () => void) {
|
|
28
|
+
isOpen = false;
|
|
29
|
+
fn?.();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleClickOutside(e: MouseEvent) {
|
|
33
|
+
if (isOpen && menuEl && !menuEl.contains(e.target as Node)) {
|
|
34
|
+
isOpen = false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<svelte:document onclick={handleClickOutside} />
|
|
40
|
+
|
|
41
|
+
<div class={className} {style} data-bridge-team-actions bind:this={menuEl} {...rest}>
|
|
42
|
+
<button class="bridge-team-actions-trigger" onclick={toggle} aria-label="Actions">
|
|
43
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
44
|
+
<circle cx="8" cy="3" r="1.5" />
|
|
45
|
+
<circle cx="8" cy="8" r="1.5" />
|
|
46
|
+
<circle cx="8" cy="13" r="1.5" />
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
|
|
50
|
+
{#if isOpen}
|
|
51
|
+
<div class="bridge-team-actions-menu">
|
|
52
|
+
<button class="bridge-team-actions-item" onclick={() => handleAction(onedit)}>
|
|
53
|
+
Edit
|
|
54
|
+
</button>
|
|
55
|
+
<button class="bridge-team-actions-item" onclick={() => handleAction(onresetpassword)}>
|
|
56
|
+
Reset Password
|
|
57
|
+
</button>
|
|
58
|
+
<button class="bridge-team-actions-item bridge-team-actions-item--danger" onclick={() => handleAction(ondelete)}>
|
|
59
|
+
Delete
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
63
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
onedit?: () => void;
|
|
4
|
+
onresetpassword?: () => void;
|
|
5
|
+
ondelete?: () => void;
|
|
6
|
+
}
|
|
7
|
+
declare const TeamUserActionsMenu: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type TeamUserActionsMenu = ReturnType<typeof TeamUserActionsMenu>;
|
|
9
|
+
export default TeamUserActionsMenu;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { TeamUser } from '@thebridge/auth-core';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { onMount } from 'svelte';
|
|
5
|
+
import { getBridgeAuth } from '../../../core/bridge-instance.js';
|
|
6
|
+
import Alert from '../sdk-auth/shared/Alert.svelte';
|
|
7
|
+
import Spinner from '../sdk-auth/shared/Spinner.svelte';
|
|
8
|
+
import TeamAddUserDialog from './TeamAddUserDialog.svelte';
|
|
9
|
+
import TeamConfirmDialog from './TeamConfirmDialog.svelte';
|
|
10
|
+
import TeamEditUserDialog from './TeamEditUserDialog.svelte';
|
|
11
|
+
import TeamUserActionsMenu from './TeamUserActionsMenu.svelte';
|
|
12
|
+
|
|
13
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
onError,
|
|
19
|
+
class: className,
|
|
20
|
+
style,
|
|
21
|
+
...rest
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
let users = $state<TeamUser[]>([]);
|
|
25
|
+
let roles = $state<string[]>([]);
|
|
26
|
+
let mfaEnabled = $state(false);
|
|
27
|
+
let loading = $state(true);
|
|
28
|
+
let error = $state<string | null>(null);
|
|
29
|
+
|
|
30
|
+
// Dialog states
|
|
31
|
+
let showAddDialog = $state(false);
|
|
32
|
+
let showEditDialog = $state(false);
|
|
33
|
+
let editingUser = $state<TeamUser | null>(null);
|
|
34
|
+
let showDeleteConfirm = $state(false);
|
|
35
|
+
let showResetConfirm = $state(false);
|
|
36
|
+
let deletingUser = $state<TeamUser | null>(null);
|
|
37
|
+
let resettingUser = $state<TeamUser | null>(null);
|
|
38
|
+
let actionLoading = $state(false);
|
|
39
|
+
|
|
40
|
+
onMount(() => {
|
|
41
|
+
loadData();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
async function loadData() {
|
|
45
|
+
loading = true;
|
|
46
|
+
error = null;
|
|
47
|
+
try {
|
|
48
|
+
const bridge = getBridgeAuth();
|
|
49
|
+
const [userResult, rolesResult] = await Promise.all([
|
|
50
|
+
bridge.team.listUsers(),
|
|
51
|
+
bridge.team.listUserRoles(),
|
|
52
|
+
]);
|
|
53
|
+
users = userResult.users;
|
|
54
|
+
mfaEnabled = userResult.mfaEnabled;
|
|
55
|
+
roles = rolesResult;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const e = err instanceof Error ? err : new Error('Failed to load users');
|
|
58
|
+
error = e.message;
|
|
59
|
+
onError?.(e);
|
|
60
|
+
} finally {
|
|
61
|
+
loading = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function openEdit(user: TeamUser) {
|
|
66
|
+
editingUser = user;
|
|
67
|
+
showEditDialog = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function openDelete(user: TeamUser) {
|
|
71
|
+
deletingUser = user;
|
|
72
|
+
showDeleteConfirm = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function openResetPassword(user: TeamUser) {
|
|
76
|
+
resettingUser = user;
|
|
77
|
+
showResetConfirm = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleUsersAdded(added: TeamUser[]) {
|
|
81
|
+
users = [...users, ...added];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handleUserUpdated(updated: TeamUser) {
|
|
85
|
+
users = users.map((u) => (u.id === updated.id ? updated : u));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function handleDeleteConfirm() {
|
|
89
|
+
if (!deletingUser) return;
|
|
90
|
+
actionLoading = true;
|
|
91
|
+
try {
|
|
92
|
+
const bridge = getBridgeAuth();
|
|
93
|
+
await bridge.team.deleteUser(deletingUser.id);
|
|
94
|
+
users = users.filter((u) => u.id !== deletingUser!.id);
|
|
95
|
+
showDeleteConfirm = false;
|
|
96
|
+
deletingUser = null;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const e = err instanceof Error ? err : new Error('Failed to delete user');
|
|
99
|
+
onError?.(e);
|
|
100
|
+
} finally {
|
|
101
|
+
actionLoading = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function handleResetConfirm() {
|
|
106
|
+
if (!resettingUser) return;
|
|
107
|
+
actionLoading = true;
|
|
108
|
+
try {
|
|
109
|
+
const bridge = getBridgeAuth();
|
|
110
|
+
await bridge.team.sendPasswordResetLink(resettingUser.id);
|
|
111
|
+
showResetConfirm = false;
|
|
112
|
+
resettingUser = null;
|
|
113
|
+
} catch (err) {
|
|
114
|
+
const e = err instanceof Error ? err : new Error('Failed to send reset link');
|
|
115
|
+
onError?.(e);
|
|
116
|
+
} finally {
|
|
117
|
+
actionLoading = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<div class={className} {style} data-bridge-team-users {...rest}>
|
|
123
|
+
<div class="bridge-team-users-header">
|
|
124
|
+
<h3 class="bridge-team-users-title">Team Members</h3>
|
|
125
|
+
<button class="bridge-btn bridge-btn-primary" onclick={() => (showAddDialog = true)}>
|
|
126
|
+
Add Member
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{#if loading}
|
|
131
|
+
<div class="bridge-team-loading">
|
|
132
|
+
<Spinner size={32} />
|
|
133
|
+
<span>Loading team members...</span>
|
|
134
|
+
</div>
|
|
135
|
+
{:else if error}
|
|
136
|
+
<Alert variant="error">{error}</Alert>
|
|
137
|
+
{:else if users.length === 0}
|
|
138
|
+
<div class="bridge-team-empty">
|
|
139
|
+
<p>No team members yet.</p>
|
|
140
|
+
<button class="bridge-btn bridge-btn-primary" onclick={() => (showAddDialog = true)}>
|
|
141
|
+
Add your first team member
|
|
142
|
+
</button>
|
|
143
|
+
</div>
|
|
144
|
+
{:else}
|
|
145
|
+
<div class="bridge-team-table-wrapper">
|
|
146
|
+
<table class="bridge-team-table">
|
|
147
|
+
<thead>
|
|
148
|
+
<tr>
|
|
149
|
+
<th>User</th>
|
|
150
|
+
<th>Role</th>
|
|
151
|
+
<th>Status</th>
|
|
152
|
+
<th></th>
|
|
153
|
+
</tr>
|
|
154
|
+
</thead>
|
|
155
|
+
<tbody>
|
|
156
|
+
{#each users as user (user.id)}
|
|
157
|
+
<tr>
|
|
158
|
+
<td>
|
|
159
|
+
<div class="bridge-team-user-cell">
|
|
160
|
+
<div class="bridge-team-user-name">
|
|
161
|
+
{user.fullName || user.username || user.email}
|
|
162
|
+
</div>
|
|
163
|
+
<div class="bridge-team-user-email">{user.email}</div>
|
|
164
|
+
</div>
|
|
165
|
+
</td>
|
|
166
|
+
<td>
|
|
167
|
+
<span class="bridge-team-badge">{user.role ?? '—'}</span>
|
|
168
|
+
</td>
|
|
169
|
+
<td>
|
|
170
|
+
<span
|
|
171
|
+
class="bridge-team-status"
|
|
172
|
+
data-state={user.enabled ? 'active' : 'disabled'}
|
|
173
|
+
>
|
|
174
|
+
{user.enabled ? 'Active' : 'Disabled'}
|
|
175
|
+
</span>
|
|
176
|
+
</td>
|
|
177
|
+
<td class="bridge-team-actions-cell">
|
|
178
|
+
<TeamUserActionsMenu
|
|
179
|
+
onedit={() => openEdit(user)}
|
|
180
|
+
onresetpassword={() => openResetPassword(user)}
|
|
181
|
+
ondelete={() => openDelete(user)}
|
|
182
|
+
/>
|
|
183
|
+
</td>
|
|
184
|
+
</tr>
|
|
185
|
+
{/each}
|
|
186
|
+
</tbody>
|
|
187
|
+
</table>
|
|
188
|
+
</div>
|
|
189
|
+
{/if}
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<TeamAddUserDialog
|
|
193
|
+
open={showAddDialog}
|
|
194
|
+
onclose={() => (showAddDialog = false)}
|
|
195
|
+
onadded={handleUsersAdded}
|
|
196
|
+
/>
|
|
197
|
+
|
|
198
|
+
<TeamEditUserDialog
|
|
199
|
+
open={showEditDialog}
|
|
200
|
+
user={editingUser}
|
|
201
|
+
{roles}
|
|
202
|
+
onclose={() => { showEditDialog = false; editingUser = null; }}
|
|
203
|
+
onupdated={handleUserUpdated}
|
|
204
|
+
/>
|
|
205
|
+
|
|
206
|
+
<TeamConfirmDialog
|
|
207
|
+
open={showDeleteConfirm}
|
|
208
|
+
title="Delete User"
|
|
209
|
+
message="Are you sure you want to delete {deletingUser?.email ?? 'this user'}? This action cannot be undone."
|
|
210
|
+
confirmLabel="Delete"
|
|
211
|
+
variant="danger"
|
|
212
|
+
loading={actionLoading}
|
|
213
|
+
onconfirm={handleDeleteConfirm}
|
|
214
|
+
oncancel={() => { showDeleteConfirm = false; deletingUser = null; }}
|
|
215
|
+
/>
|
|
216
|
+
|
|
217
|
+
<TeamConfirmDialog
|
|
218
|
+
open={showResetConfirm}
|
|
219
|
+
title="Reset Password"
|
|
220
|
+
message="Send a password reset link to {resettingUser?.email ?? 'this user'}?"
|
|
221
|
+
confirmLabel="Send Reset Link"
|
|
222
|
+
variant="default"
|
|
223
|
+
loading={actionLoading}
|
|
224
|
+
onconfirm={handleResetConfirm}
|
|
225
|
+
oncancel={() => { showResetConfirm = false; resettingUser = null; }}
|
|
226
|
+
/>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
onError?: (error: Error) => void;
|
|
4
|
+
}
|
|
5
|
+
declare const TeamUserList: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type TeamUserList = ReturnType<typeof TeamUserList>;
|
|
7
|
+
export default TeamUserList;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { TeamWorkspace } from '@thebridge/auth-core';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { onMount } from 'svelte';
|
|
5
|
+
import { getBridgeAuth } from '../../../core/bridge-instance.js';
|
|
6
|
+
import Alert from '../sdk-auth/shared/Alert.svelte';
|
|
7
|
+
import Spinner from '../sdk-auth/shared/Spinner.svelte';
|
|
8
|
+
|
|
9
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
onError?: (error: Error) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
onError,
|
|
15
|
+
class: className,
|
|
16
|
+
style,
|
|
17
|
+
...rest
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
let workspace = $state<TeamWorkspace | null>(null);
|
|
21
|
+
let name = $state('');
|
|
22
|
+
let locale = $state('');
|
|
23
|
+
let loading = $state(true);
|
|
24
|
+
let saving = $state(false);
|
|
25
|
+
let error = $state<string | null>(null);
|
|
26
|
+
let success = $state<string | null>(null);
|
|
27
|
+
|
|
28
|
+
onMount(() => {
|
|
29
|
+
loadWorkspace();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
async function loadWorkspace() {
|
|
33
|
+
loading = true;
|
|
34
|
+
error = null;
|
|
35
|
+
try {
|
|
36
|
+
const bridge = getBridgeAuth();
|
|
37
|
+
workspace = await bridge.team.getWorkspace();
|
|
38
|
+
name = workspace.name ?? '';
|
|
39
|
+
locale = workspace.locale ?? '';
|
|
40
|
+
} catch (err) {
|
|
41
|
+
const e = err instanceof Error ? err : new Error('Failed to load workspace');
|
|
42
|
+
error = e.message;
|
|
43
|
+
onError?.(e);
|
|
44
|
+
} finally {
|
|
45
|
+
loading = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function handleSubmit() {
|
|
50
|
+
saving = true;
|
|
51
|
+
error = null;
|
|
52
|
+
success = null;
|
|
53
|
+
try {
|
|
54
|
+
const bridge = getBridgeAuth();
|
|
55
|
+
await bridge.team.updateWorkspace({ name, locale });
|
|
56
|
+
success = 'Workspace updated successfully.';
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const e = err instanceof Error ? err : new Error('Failed to update workspace');
|
|
59
|
+
error = e.message;
|
|
60
|
+
onError?.(e);
|
|
61
|
+
} finally {
|
|
62
|
+
saving = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<div class={className} {style} data-bridge-team-workspace {...rest}>
|
|
68
|
+
<h3 class="bridge-team-section-title">Workspace Settings</h3>
|
|
69
|
+
|
|
70
|
+
{#if loading}
|
|
71
|
+
<div class="bridge-team-loading">
|
|
72
|
+
<Spinner size={32} />
|
|
73
|
+
<span>Loading workspace...</span>
|
|
74
|
+
</div>
|
|
75
|
+
{:else}
|
|
76
|
+
{#if error}
|
|
77
|
+
<div class="bridge-team-alert"><Alert variant="error">{error}</Alert></div>
|
|
78
|
+
{/if}
|
|
79
|
+
{#if success}
|
|
80
|
+
<div class="bridge-team-alert"><Alert variant="success">{success}</Alert></div>
|
|
81
|
+
{/if}
|
|
82
|
+
|
|
83
|
+
{#if workspace?.logo}
|
|
84
|
+
<div class="bridge-team-logo">
|
|
85
|
+
<img src={workspace.logo} alt="{workspace.name} logo" />
|
|
86
|
+
</div>
|
|
87
|
+
{/if}
|
|
88
|
+
|
|
89
|
+
<form class="bridge-team-form" onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
|
90
|
+
<div class="bridge-team-form-group">
|
|
91
|
+
<label for="bridge-workspace-name">Workspace Name</label>
|
|
92
|
+
<input
|
|
93
|
+
id="bridge-workspace-name"
|
|
94
|
+
type="text"
|
|
95
|
+
bind:value={name}
|
|
96
|
+
disabled={saving}
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="bridge-team-form-group">
|
|
101
|
+
<label for="bridge-workspace-locale">Locale</label>
|
|
102
|
+
<input
|
|
103
|
+
id="bridge-workspace-locale"
|
|
104
|
+
type="text"
|
|
105
|
+
bind:value={locale}
|
|
106
|
+
placeholder="en"
|
|
107
|
+
disabled={saving}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{#if workspace?.plan}
|
|
112
|
+
<div class="bridge-team-form-group">
|
|
113
|
+
<label>Current Plan</label>
|
|
114
|
+
<div class="bridge-team-readonly">{workspace.plan}</div>
|
|
115
|
+
</div>
|
|
116
|
+
{/if}
|
|
117
|
+
|
|
118
|
+
<div class="bridge-team-form-group">
|
|
119
|
+
<label>MFA</label>
|
|
120
|
+
<div class="bridge-team-readonly">{workspace?.mfa ? 'Enabled' : 'Disabled'}</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div class="bridge-team-form-actions">
|
|
124
|
+
<button
|
|
125
|
+
type="submit"
|
|
126
|
+
class="bridge-btn bridge-btn-primary"
|
|
127
|
+
disabled={saving}
|
|
128
|
+
>
|
|
129
|
+
{saving ? 'Saving...' : 'Save Changes'}
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</form>
|
|
133
|
+
{/if}
|
|
134
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
onError?: (error: Error) => void;
|
|
4
|
+
}
|
|
5
|
+
declare const TeamWorkspaceForm: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type TeamWorkspaceForm = ReturnType<typeof TeamWorkspaceForm>;
|
|
7
|
+
export default TeamWorkspaceForm;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BridgeConfig } from '../../shared/types/config';
|
|
2
|
+
export declare const bridgeConfig: import("svelte/store").Writable<BridgeConfig | null>;
|
|
3
|
+
export declare const readonlyConfig: import("svelte/store").Readable<BridgeConfig | null>;
|
|
4
|
+
export declare const configReady: import("svelte/store").Readable<boolean>;
|
|
5
|
+
export declare function initConfig(config: BridgeConfig): void;
|
|
6
|
+
export declare function getConfig(): BridgeConfig;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/lib/config/bridgeConfig.ts
|
|
2
|
+
import { derived, writable } from 'svelte/store';
|
|
3
|
+
import { initBridge } from '../../core/bridge-instance.js';
|
|
4
|
+
import { logger, setLoggerDebug } from '../../shared/logger.js';
|
|
5
|
+
const DEFAULT_CONFIG = {
|
|
6
|
+
apiBaseUrl: 'https://api.thebridge.dev',
|
|
7
|
+
defaultRedirectRoute: '/',
|
|
8
|
+
signupRoute: '/auth/signup',
|
|
9
|
+
debug: false
|
|
10
|
+
};
|
|
11
|
+
const { subscribe, set, update } = writable({
|
|
12
|
+
config: null,
|
|
13
|
+
routeConfig: null,
|
|
14
|
+
loaded: false
|
|
15
|
+
});
|
|
16
|
+
export const bridgeConfig = {
|
|
17
|
+
subscribe,
|
|
18
|
+
initConfig: (config, routeConfig) => {
|
|
19
|
+
if (!config?.appId) {
|
|
20
|
+
throw new Error('Bridge appId is required but was not provided in bridge configuration.');
|
|
21
|
+
}
|
|
22
|
+
// Default to /auth/oauth-callback if not provided
|
|
23
|
+
const DEFAULT_CALLBACK_PATH = '/auth/oauth-callback';
|
|
24
|
+
const defaultCallback = typeof window !== 'undefined'
|
|
25
|
+
? `${window.location.origin}${DEFAULT_CALLBACK_PATH}`
|
|
26
|
+
: undefined;
|
|
27
|
+
const merged = {
|
|
28
|
+
...DEFAULT_CONFIG,
|
|
29
|
+
callbackUrl: defaultCallback,
|
|
30
|
+
...config
|
|
31
|
+
};
|
|
32
|
+
// Use user provided callbackUrl if present, otherwise use default
|
|
33
|
+
if (config.callbackUrl) {
|
|
34
|
+
merged.callbackUrl = config.callbackUrl;
|
|
35
|
+
}
|
|
36
|
+
else if (!merged.callbackUrl && defaultCallback) {
|
|
37
|
+
merged.callbackUrl = defaultCallback;
|
|
38
|
+
}
|
|
39
|
+
// Initialize logger debug mode before anything else
|
|
40
|
+
setLoggerDebug(!!merged.debug);
|
|
41
|
+
// Initialize the singleton BridgeAuth instance
|
|
42
|
+
initBridge(merged);
|
|
43
|
+
// Set bridge full config and mark it as loaded
|
|
44
|
+
set({
|
|
45
|
+
config: merged,
|
|
46
|
+
routeConfig: routeConfig || null,
|
|
47
|
+
loaded: true
|
|
48
|
+
});
|
|
49
|
+
// This will only print when debug is true
|
|
50
|
+
logger.debug('[config] initialized', merged);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
// Convenience derived stores
|
|
54
|
+
export const configReady = derived(bridgeConfig, ($state) => $state.loaded);
|
|
55
|
+
export const readonlyConfig = derived(bridgeConfig, ($state) => $state.config);
|
|
56
|
+
// Synchronous access to latest config value
|
|
57
|
+
let _currentConfig = null;
|
|
58
|
+
let _currentRouteConfig = null;
|
|
59
|
+
bridgeConfig.subscribe(($state) => {
|
|
60
|
+
if ($state.loaded) {
|
|
61
|
+
_currentConfig = $state.config;
|
|
62
|
+
_currentRouteConfig = $state.routeConfig;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
export function getConfig() {
|
|
66
|
+
if (!_currentConfig) {
|
|
67
|
+
throw new Error('Config has not been initialized. Call initConfig(...) early in app startup.');
|
|
68
|
+
}
|
|
69
|
+
return _currentConfig;
|
|
70
|
+
}
|
|
71
|
+
export function getRouteGuardConfig() {
|
|
72
|
+
if (!_currentRouteConfig) {
|
|
73
|
+
throw new Error('RouteGuardConfig has not been initialized. Call initConfig(...) early in app startup.');
|
|
74
|
+
}
|
|
75
|
+
return _currentRouteConfig;
|
|
76
|
+
}
|