@basictech/react 0.12.0-beta.0 → 0.12.0-beta.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/CHANGELOG.md +603 -0
- package/README.md +125 -29
- package/dist/index.d.mts +120 -11
- package/dist/index.d.ts +120 -11
- package/dist/index.js +1168 -111
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1165 -110
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +557 -0
- package/dist/styles.css.map +1 -0
- package/dist/styles.d.mts +2 -0
- package/dist/styles.d.ts +2 -0
- package/package.json +12 -6
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// src/create-basic.tsx
|
|
4
|
+
import { useContext as useContext2 } from "react";
|
|
5
|
+
|
|
6
|
+
// src/context.ts
|
|
7
|
+
import { createContext } from "react";
|
|
8
|
+
var BasicClientContext = createContext(null);
|
|
9
|
+
|
|
1
10
|
// src/create-client.ts
|
|
2
11
|
import {
|
|
3
12
|
MemoryKeyValueStorage,
|
|
@@ -6,6 +15,10 @@ import {
|
|
|
6
15
|
} from "@basictech/core";
|
|
7
16
|
|
|
8
17
|
// src/adapters/broadcast-channel.ts
|
|
18
|
+
function supportsAccessTokenAdoption(store) {
|
|
19
|
+
const candidate = store;
|
|
20
|
+
return typeof candidate?.accessToken === "function" && typeof candidate?.adoptAccessToken === "function";
|
|
21
|
+
}
|
|
9
22
|
function createBrowserMessageChannel(name) {
|
|
10
23
|
const channel = new BroadcastChannel(name);
|
|
11
24
|
const adapter = {
|
|
@@ -16,12 +29,11 @@ function createBrowserMessageChannel(name) {
|
|
|
16
29
|
channel.onmessage = (event) => adapter.onmessage?.({ data: event.data });
|
|
17
30
|
return adapter;
|
|
18
31
|
}
|
|
19
|
-
function createBrowserAuthChannelFactory(
|
|
20
|
-
|
|
21
|
-
return (name) => {
|
|
32
|
+
function createBrowserAuthChannelFactory(tokenStore) {
|
|
33
|
+
return (name, context) => {
|
|
22
34
|
const channel = createBrowserMessageChannel(name);
|
|
23
|
-
|
|
24
|
-
const tokenKey =
|
|
35
|
+
if (!context) return channel;
|
|
36
|
+
const { tokenKey } = context;
|
|
25
37
|
const adapter = {
|
|
26
38
|
postMessage: (message) => {
|
|
27
39
|
const outgoing = message;
|
|
@@ -568,32 +580,27 @@ function browserReconcileTrigger(listener) {
|
|
|
568
580
|
function createBasicClient(config) {
|
|
569
581
|
const local = browserStorage("localStorage") ?? new MemoryKeyValueStorage();
|
|
570
582
|
const session = browserStorage("sessionStorage") ?? new MemoryKeyValueStorage();
|
|
571
|
-
const
|
|
583
|
+
const tokenStore = config.tokenStore ?? new BrowserTokenStore(local);
|
|
572
584
|
const canBroadcast = typeof BroadcastChannel !== "undefined";
|
|
573
585
|
const canPersistReplica = typeof indexedDB !== "undefined";
|
|
586
|
+
const messageChannel = !canBroadcast ? void 0 : supportsAccessTokenAdoption(tokenStore) ? createBrowserAuthChannelFactory(tokenStore) : createBrowserMessageChannel;
|
|
574
587
|
return createCoreClient({
|
|
575
588
|
...config,
|
|
576
589
|
kv: config.kv ?? local,
|
|
577
|
-
tokenStore
|
|
590
|
+
tokenStore,
|
|
578
591
|
sessionStorage: config.sessionStorage ?? session,
|
|
579
592
|
replicaStore: config.replicaStore ?? (canPersistReplica ? new PersistenceStore(config.clientId) : new MemoryReplicaStoreFactory()),
|
|
580
593
|
navigate: config.navigate ?? browserNavigate,
|
|
581
594
|
currentUrl: config.currentUrl ?? browserCurrentUrl,
|
|
582
595
|
replaceUrl: config.replaceUrl ?? browserReplaceUrl,
|
|
583
596
|
reconcileTrigger: config.reconcileTrigger ?? browserReconcileTrigger,
|
|
584
|
-
createMessageChannel: config.createMessageChannel ??
|
|
597
|
+
createMessageChannel: config.createMessageChannel ?? messageChannel,
|
|
585
598
|
uploadTransport: config.uploadTransport ?? (typeof XMLHttpRequest === "undefined" ? void 0 : browserUploadTransport)
|
|
586
599
|
});
|
|
587
600
|
}
|
|
588
601
|
|
|
589
602
|
// src/provider.tsx
|
|
590
603
|
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
591
|
-
|
|
592
|
-
// src/context.ts
|
|
593
|
-
import { createContext } from "react";
|
|
594
|
-
var BasicClientContext = createContext(null);
|
|
595
|
-
|
|
596
|
-
// src/provider.tsx
|
|
597
604
|
import { jsx } from "react/jsx-runtime";
|
|
598
605
|
var clientLifecycles = /* @__PURE__ */ new WeakMap();
|
|
599
606
|
function retainClient(client) {
|
|
@@ -662,10 +669,10 @@ function BasicProvider(props) {
|
|
|
662
669
|
}
|
|
663
670
|
|
|
664
671
|
// src/hooks/accounts.ts
|
|
665
|
-
import { useCallback, useMemo } from "react";
|
|
672
|
+
import { useCallback, useMemo as useMemo2 } from "react";
|
|
666
673
|
|
|
667
674
|
// src/hooks/client.ts
|
|
668
|
-
import { useContext, useEffect as useEffect2, useRef as useRef2, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
675
|
+
import { useContext, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
669
676
|
import {
|
|
670
677
|
BasicError as BasicError4
|
|
671
678
|
} from "@basictech/core";
|
|
@@ -677,6 +684,10 @@ function useRequiredClient() {
|
|
|
677
684
|
function useClientSnapshot(client) {
|
|
678
685
|
return useSyncExternalStore2(client.subscribe, client.getSnapshot, client.getSnapshot);
|
|
679
686
|
}
|
|
687
|
+
function useSignInGate(snapshot) {
|
|
688
|
+
const code = snapshot.isReady && !snapshot.isSignedIn ? snapshot.readOnlyReason ?? "AUTHORIZATION_REQUIRED" : null;
|
|
689
|
+
return useMemo(() => code === null ? null : new BasicError4(code, code === "AUTH_EXPIRED" ? "Session expired. Sign in again to load this list." : "Sign in to load this list."), [code]);
|
|
690
|
+
}
|
|
680
691
|
function toBasicError(error, fallback = "UNKNOWN_ERROR") {
|
|
681
692
|
if (error instanceof BasicError4) return error;
|
|
682
693
|
return new BasicError4(fallback, error instanceof Error ? error.message : String(error));
|
|
@@ -725,38 +736,35 @@ function useAsyncResult(load, initialData, enabled, settled, dependencies) {
|
|
|
725
736
|
function useAccountsFor(client) {
|
|
726
737
|
const snapshot = useClientSnapshot(client);
|
|
727
738
|
const switchAccount = useCallback((id) => client.switchAccount(id), [client]);
|
|
728
|
-
const addAccount = useCallback(() => client.addAccount(), [client]);
|
|
739
|
+
const addAccount = useCallback((options) => client.addAccount(options), [client]);
|
|
729
740
|
const removeAccount = useCallback((id) => client.removeAccount(id), [client]);
|
|
730
|
-
return
|
|
741
|
+
return useMemo2(() => ({
|
|
731
742
|
accounts: snapshot.accounts,
|
|
732
|
-
|
|
743
|
+
activeAccount: snapshot.activeAccount,
|
|
733
744
|
switchAccount,
|
|
734
745
|
addAccount,
|
|
735
746
|
removeAccount
|
|
736
|
-
}), [snapshot.accounts, snapshot.
|
|
747
|
+
}), [snapshot.accounts, snapshot.activeAccount, switchAccount, addAccount, removeAccount]);
|
|
737
748
|
}
|
|
738
749
|
function useAccounts() {
|
|
739
750
|
return useAccountsFor(useRequiredClient());
|
|
740
751
|
}
|
|
741
752
|
|
|
742
753
|
// src/hooks/auth.ts
|
|
743
|
-
import { useCallback as useCallback2, useMemo as
|
|
744
|
-
import {
|
|
745
|
-
BasicError as BasicError5
|
|
746
|
-
} from "@basictech/core";
|
|
754
|
+
import { useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
747
755
|
function useAuthFor(client) {
|
|
748
756
|
const snapshot = useClientSnapshot(client);
|
|
749
757
|
const signIn = useCallback2((input) => client.signIn(input), [client]);
|
|
750
758
|
const signOut = useCallback2(() => client.signOut(), [client]);
|
|
751
759
|
const getToken = useCallback2(() => client.getToken(), [client]);
|
|
752
|
-
return
|
|
760
|
+
return useMemo3(() => ({
|
|
753
761
|
isReady: snapshot.isReady,
|
|
754
762
|
isSignedIn: snapshot.isSignedIn,
|
|
755
763
|
isAnonymous: snapshot.isAnonymous,
|
|
756
764
|
canWrite: snapshot.canWrite,
|
|
757
765
|
readOnlyReason: snapshot.readOnlyReason,
|
|
758
766
|
status: snapshot.authStatus,
|
|
759
|
-
error: snapshot.
|
|
767
|
+
error: snapshot.authError,
|
|
760
768
|
user: snapshot.user,
|
|
761
769
|
did: snapshot.did,
|
|
762
770
|
handle: snapshot.handle,
|
|
@@ -769,6 +777,9 @@ function useAuth() {
|
|
|
769
777
|
return useAuthFor(useRequiredClient());
|
|
770
778
|
}
|
|
771
779
|
|
|
780
|
+
// src/hooks/basic.ts
|
|
781
|
+
import { useMemo as useMemo6 } from "react";
|
|
782
|
+
|
|
772
783
|
// src/hooks/db.ts
|
|
773
784
|
function useDbFor(client, source = "default") {
|
|
774
785
|
return client.db(source);
|
|
@@ -784,13 +795,13 @@ function useCollection(name, options = {}) {
|
|
|
784
795
|
}
|
|
785
796
|
|
|
786
797
|
// src/hooks/repos.ts
|
|
787
|
-
import { useCallback as useCallback3, useMemo as
|
|
798
|
+
import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
|
|
788
799
|
function useReposFor(client) {
|
|
789
800
|
const snapshot = useClientSnapshot(client);
|
|
790
801
|
const refresh = useCallback3(() => client.refreshRepos(), [client]);
|
|
791
802
|
const create = useCallback3((input) => client.createRepo(input), [client]);
|
|
792
803
|
const archive = useCallback3((repoId) => client.archiveRepo(repoId), [client]);
|
|
793
|
-
return
|
|
804
|
+
return useMemo4(() => ({
|
|
794
805
|
repos: snapshot.repos,
|
|
795
806
|
defaultRepoId: snapshot.defaultRepoId,
|
|
796
807
|
refresh,
|
|
@@ -803,7 +814,7 @@ function useRepos() {
|
|
|
803
814
|
}
|
|
804
815
|
|
|
805
816
|
// src/hooks/status.ts
|
|
806
|
-
import { useCallback as useCallback4, useMemo as
|
|
817
|
+
import { useCallback as useCallback4, useMemo as useMemo5 } from "react";
|
|
807
818
|
function useSyncStatusFor(client, source) {
|
|
808
819
|
const snapshot = useClientSnapshot(client);
|
|
809
820
|
const sourceKey = JSON.stringify(source);
|
|
@@ -818,7 +829,7 @@ function useSyncStatusFor(client, source) {
|
|
|
818
829
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
819
830
|
[client, sourceKey]
|
|
820
831
|
);
|
|
821
|
-
return
|
|
832
|
+
return useMemo5(() => ({
|
|
822
833
|
status: source === void 0 ? snapshot.syncStatus : client.status(source),
|
|
823
834
|
pendingCount: client.pending(source).length,
|
|
824
835
|
rejected: client.rejected(source),
|
|
@@ -845,51 +856,41 @@ function useBasicFor(client) {
|
|
|
845
856
|
const auth = useAuthFor(client);
|
|
846
857
|
const accounts = useAccountsFor(client);
|
|
847
858
|
const sync = useSyncStatusFor(client);
|
|
848
|
-
const repos = useReposFor(client);
|
|
849
|
-
|
|
859
|
+
const repos = useReposFor(client).repos;
|
|
860
|
+
const db = useDbFor(client);
|
|
861
|
+
return useMemo6(() => ({
|
|
850
862
|
...auth,
|
|
851
863
|
client,
|
|
852
|
-
db
|
|
864
|
+
db,
|
|
853
865
|
accounts,
|
|
854
866
|
sync,
|
|
855
|
-
repos
|
|
856
|
-
};
|
|
867
|
+
repos
|
|
868
|
+
}), [auth, client, db, accounts, sync, repos]);
|
|
857
869
|
}
|
|
858
870
|
function useBasic() {
|
|
859
871
|
return useBasicFor(useRequiredClient());
|
|
860
872
|
}
|
|
861
873
|
|
|
862
874
|
// src/hooks/files.ts
|
|
863
|
-
import { useMemo as
|
|
875
|
+
import { useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
864
876
|
var zero = () => 0;
|
|
865
877
|
function useFilesFor(client, query = {}, options = {}) {
|
|
866
878
|
const source = options.source ?? "default";
|
|
867
879
|
const sourceKey = JSON.stringify(source);
|
|
868
880
|
const queryKey = JSON.stringify(query);
|
|
869
881
|
const snapshot = useClientSnapshot(client);
|
|
870
|
-
const subscription =
|
|
882
|
+
const subscription = useMemo7(
|
|
871
883
|
() => client.queryStore.collection(source, "_files"),
|
|
872
884
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
873
885
|
[client, sourceKey]
|
|
874
886
|
);
|
|
875
887
|
const version = useSyncExternalStore3(subscription.subscribe, subscription.getSnapshot, zero);
|
|
876
888
|
return useAsyncResult(
|
|
877
|
-
async () =>
|
|
878
|
-
const files = client.db(source).files;
|
|
879
|
-
if (source !== "default" && "mountId" in source) {
|
|
880
|
-
const page = await files.list({
|
|
881
|
-
...query.prefix !== void 0 ? { prefix: query.prefix } : {},
|
|
882
|
-
...query.limit !== void 0 ? { limit: query.limit } : {},
|
|
883
|
-
...query.cursor !== void 0 ? { cursor: query.cursor } : {}
|
|
884
|
-
});
|
|
885
|
-
return page.data;
|
|
886
|
-
}
|
|
887
|
-
return (await files.list(query)).data;
|
|
888
|
-
},
|
|
889
|
+
async () => (await client.db(source).files.list(query)).data,
|
|
889
890
|
[],
|
|
890
891
|
snapshot.isReady,
|
|
891
892
|
snapshot.isReady,
|
|
892
|
-
[sourceKey, queryKey, version, snapshot.
|
|
893
|
+
[sourceKey, queryKey, version, snapshot.activeAccount?.id]
|
|
893
894
|
);
|
|
894
895
|
}
|
|
895
896
|
function useFiles(query = {}, options = {}) {
|
|
@@ -897,13 +898,15 @@ function useFiles(query = {}, options = {}) {
|
|
|
897
898
|
}
|
|
898
899
|
function useStorageInfoFor(client) {
|
|
899
900
|
const snapshot = useClientSnapshot(client);
|
|
900
|
-
|
|
901
|
+
const gate = useSignInGate(snapshot);
|
|
902
|
+
const result = useAsyncResult(
|
|
901
903
|
() => client.storageInfo(client.resolveRepoId()),
|
|
902
904
|
null,
|
|
903
905
|
snapshot.isReady && snapshot.isSignedIn,
|
|
904
906
|
snapshot.isReady,
|
|
905
|
-
[snapshot.
|
|
907
|
+
[snapshot.activeAccount?.id]
|
|
906
908
|
);
|
|
909
|
+
return { ...result, error: result.error ?? gate };
|
|
907
910
|
}
|
|
908
911
|
function useStorageInfo() {
|
|
909
912
|
return useStorageInfoFor(useRequiredClient());
|
|
@@ -913,33 +916,34 @@ function useStorageInfo() {
|
|
|
913
916
|
import { useCallback as useCallback5 } from "react";
|
|
914
917
|
function useMountsFor(client, query = {}) {
|
|
915
918
|
const snapshot = useClientSnapshot(client);
|
|
919
|
+
const gate = useSignInGate(snapshot);
|
|
916
920
|
const queryKey = JSON.stringify(query);
|
|
917
921
|
const result = useAsyncResult(
|
|
918
922
|
() => client.shares.mounts(query),
|
|
919
923
|
[],
|
|
920
924
|
snapshot.isReady && snapshot.isSignedIn,
|
|
921
925
|
snapshot.isReady,
|
|
922
|
-
[queryKey, snapshot.
|
|
926
|
+
[queryKey, snapshot.activeAccount?.id]
|
|
923
927
|
);
|
|
924
928
|
const open = useCallback5(
|
|
925
929
|
(mountId) => client.shares.mount(mountId),
|
|
926
930
|
[client]
|
|
927
931
|
);
|
|
928
932
|
const manageUrl = useCallback5(() => client.shares.manageUrl(), [client]);
|
|
929
|
-
|
|
930
|
-
return { data: mounts, mounts, ...state, open, manageUrl };
|
|
933
|
+
return { ...result, error: result.error ?? gate, open, manageUrl };
|
|
931
934
|
}
|
|
932
935
|
function useMounts(query = {}) {
|
|
933
936
|
return useMountsFor(useRequiredClient(), query);
|
|
934
937
|
}
|
|
935
938
|
function useOutgoingSharesFor(client) {
|
|
936
939
|
const snapshot = useClientSnapshot(client);
|
|
940
|
+
const gate = useSignInGate(snapshot);
|
|
937
941
|
const result = useAsyncResult(
|
|
938
942
|
() => client.shares.listOutgoing(),
|
|
939
943
|
[],
|
|
940
944
|
snapshot.isReady && snapshot.isSignedIn,
|
|
941
945
|
snapshot.isReady,
|
|
942
|
-
[snapshot.
|
|
946
|
+
[snapshot.activeAccount?.id]
|
|
943
947
|
);
|
|
944
948
|
const create = useCallback5((input) => client.shares.createOutgoing(input), [client]);
|
|
945
949
|
const get = useCallback5((id) => client.shares.getOutgoing(id), [client]);
|
|
@@ -947,11 +951,9 @@ function useOutgoingSharesFor(client) {
|
|
|
947
951
|
const revoke = useCallback5((id) => client.shares.revoke(id), [client]);
|
|
948
952
|
const getContactHandle = useCallback5((did) => client.shares.getContactHandle(did), [client]);
|
|
949
953
|
const manageUrl = useCallback5(() => client.shares.manageUrl(), [client]);
|
|
950
|
-
const { data: outgoingShares, ...state } = result;
|
|
951
954
|
return {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
...state,
|
|
955
|
+
...result,
|
|
956
|
+
error: result.error ?? gate,
|
|
955
957
|
create,
|
|
956
958
|
get,
|
|
957
959
|
cancel,
|
|
@@ -965,58 +967,35 @@ function useOutgoingShares() {
|
|
|
965
967
|
}
|
|
966
968
|
|
|
967
969
|
// src/hooks/query.ts
|
|
968
|
-
import { useEffect as useEffect3,
|
|
969
|
-
var subscribeNever = () => () => {
|
|
970
|
-
};
|
|
971
|
-
var zero2 = () => 0;
|
|
970
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
972
971
|
function useQueryValueFor(client, collection, query = {}, options = {}) {
|
|
973
972
|
const source = options.source ?? "default";
|
|
974
973
|
const sourceKey = JSON.stringify(source);
|
|
975
974
|
const queryKey = JSON.stringify(query);
|
|
976
975
|
const snapshot = useClientSnapshot(client);
|
|
977
|
-
const subscription = useMemo6(
|
|
978
|
-
() => client.queryStore.collection(source, collection),
|
|
979
|
-
// sourceKey captures semantic source identity rather than object identity.
|
|
980
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
981
|
-
[client, sourceKey, collection]
|
|
982
|
-
);
|
|
983
|
-
const version = useSyncExternalStore4(
|
|
984
|
-
subscription?.subscribe ?? subscribeNever,
|
|
985
|
-
subscription?.getSnapshot ?? zero2,
|
|
986
|
-
zero2
|
|
987
|
-
);
|
|
988
976
|
const [result, setResult] = useState3({
|
|
989
977
|
data: [],
|
|
990
978
|
isLoading: true,
|
|
991
979
|
error: null
|
|
992
980
|
});
|
|
993
981
|
useEffect3(() => {
|
|
994
|
-
let active = true;
|
|
995
|
-
if (!snapshot.isReady) {
|
|
996
|
-
setResult((current) => ({ ...current, isLoading: true, error: null }));
|
|
997
|
-
return () => {
|
|
998
|
-
active = false;
|
|
999
|
-
};
|
|
1000
|
-
}
|
|
1001
982
|
setResult((current) => ({ ...current, isLoading: true, error: null }));
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
(error) => {
|
|
1009
|
-
if (active) setResult((current) => ({
|
|
983
|
+
if (!snapshot.isReady) return;
|
|
984
|
+
try {
|
|
985
|
+
return client.db(source).collection(collection).watch(
|
|
986
|
+
query,
|
|
987
|
+
(page) => setResult({ data: page.data, isLoading: false, error: null }),
|
|
988
|
+
(error) => setResult((current) => ({
|
|
1010
989
|
...current,
|
|
1011
990
|
isLoading: false,
|
|
1012
991
|
error: toBasicError(error, "QUERY_FAILED")
|
|
1013
|
-
}))
|
|
1014
|
-
|
|
1015
|
-
)
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1019
|
-
}, [client, collection, sourceKey, queryKey,
|
|
992
|
+
}))
|
|
993
|
+
);
|
|
994
|
+
} catch (error) {
|
|
995
|
+
setResult((current) => ({ ...current, isLoading: false, error: toBasicError(error, "QUERY_FAILED") }));
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
}, [client, collection, sourceKey, queryKey, snapshot.isReady]);
|
|
1020
999
|
return result;
|
|
1021
1000
|
}
|
|
1022
1001
|
function useQueryFor(client, collection, query = {}, options = {}) {
|
|
@@ -1026,6 +1005,47 @@ function useQuery(collection, query = {}, options = {}) {
|
|
|
1026
1005
|
return useQueryValueFor(useRequiredClient(), collection, query, options);
|
|
1027
1006
|
}
|
|
1028
1007
|
|
|
1008
|
+
// src/hooks/profile.ts
|
|
1009
|
+
import {
|
|
1010
|
+
BasicError as BasicError5
|
|
1011
|
+
} from "@basictech/core";
|
|
1012
|
+
function useProjectProfileFor(client, enabled = true) {
|
|
1013
|
+
const snapshot = useClientSnapshot(client);
|
|
1014
|
+
const accountId = snapshot.activeAccount?.id;
|
|
1015
|
+
const result = useAsyncResult(
|
|
1016
|
+
async () => ({ accountId, profile: await client.getProjectProfile() }),
|
|
1017
|
+
null,
|
|
1018
|
+
enabled && snapshot.isReady && snapshot.isSignedIn,
|
|
1019
|
+
snapshot.isReady,
|
|
1020
|
+
[snapshot.activeAccount?.id]
|
|
1021
|
+
);
|
|
1022
|
+
const checkAccount = () => {
|
|
1023
|
+
if (client.getSnapshot().activeAccount?.id !== accountId)
|
|
1024
|
+
throw new BasicError5("ACCOUNT_CHANGED");
|
|
1025
|
+
};
|
|
1026
|
+
return {
|
|
1027
|
+
...result,
|
|
1028
|
+
data: enabled && snapshot.isSignedIn && result.data && result.data.accountId === accountId ? result.data.profile : null,
|
|
1029
|
+
async update(patch) {
|
|
1030
|
+
checkAccount();
|
|
1031
|
+
const profile = await client.updateProjectProfile(patch);
|
|
1032
|
+
checkAccount();
|
|
1033
|
+
result.refresh();
|
|
1034
|
+
return profile;
|
|
1035
|
+
},
|
|
1036
|
+
async reset() {
|
|
1037
|
+
checkAccount();
|
|
1038
|
+
const profile = await client.resetProjectProfile();
|
|
1039
|
+
checkAccount();
|
|
1040
|
+
result.refresh();
|
|
1041
|
+
return profile;
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
function useProjectProfile(enabled = true) {
|
|
1046
|
+
return useProjectProfileFor(useRequiredClient(), enabled);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1029
1049
|
// src/create-basic.tsx
|
|
1030
1050
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1031
1051
|
function createBasic(config) {
|
|
@@ -1033,32 +1053,1066 @@ function createBasic(config) {
|
|
|
1033
1053
|
function Provider({ children, renderWhileLoading }) {
|
|
1034
1054
|
return /* @__PURE__ */ jsx2(BasicProvider, { client, renderWhileLoading, children });
|
|
1035
1055
|
}
|
|
1056
|
+
function useBound() {
|
|
1057
|
+
if (useContext2(BasicClientContext) !== client) {
|
|
1058
|
+
throw new Error("Bound Basic hooks must be used within their own <basic.Provider>");
|
|
1059
|
+
}
|
|
1060
|
+
return client;
|
|
1061
|
+
}
|
|
1036
1062
|
function useBoundDb(source = "default") {
|
|
1037
|
-
return
|
|
1063
|
+
return useBound().db(source);
|
|
1038
1064
|
}
|
|
1039
1065
|
return {
|
|
1040
1066
|
client,
|
|
1041
1067
|
Provider,
|
|
1042
|
-
useBasic: () => useBasicFor(
|
|
1043
|
-
useAuth: () => useAuthFor(
|
|
1044
|
-
useAccounts: () => useAccountsFor(
|
|
1068
|
+
useBasic: () => useBasicFor(useBound()),
|
|
1069
|
+
useAuth: () => useAuthFor(useBound()),
|
|
1070
|
+
useAccounts: () => useAccountsFor(useBound()),
|
|
1045
1071
|
useDb: useBoundDb,
|
|
1046
|
-
useCollection: (name, options = {}) => useCollectionFor(
|
|
1047
|
-
useQuery: (collection, query = {}, options = {}) => useQueryFor(
|
|
1048
|
-
useSyncStatus: (source) => useSyncStatusFor(
|
|
1049
|
-
useSchemaStatus: (source = "default") => useSchemaStatusFor(
|
|
1050
|
-
useRepos: () => useReposFor(
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1072
|
+
useCollection: (name, options = {}) => useCollectionFor(useBound(), name, options),
|
|
1073
|
+
useQuery: (collection, query = {}, options = {}) => useQueryFor(useBound(), collection, query, options),
|
|
1074
|
+
useSyncStatus: (source) => useSyncStatusFor(useBound(), source),
|
|
1075
|
+
useSchemaStatus: (source = "default") => useSchemaStatusFor(useBound(), source),
|
|
1076
|
+
useRepos: () => useReposFor(useBound()),
|
|
1077
|
+
useProjectProfile: (enabled) => useProjectProfileFor(useBound(), enabled),
|
|
1078
|
+
useFiles: (query = {}, options = {}) => useFilesFor(useBound(), query, options),
|
|
1079
|
+
useStorageInfo: () => useStorageInfoFor(useBound()),
|
|
1080
|
+
useMounts: (query = {}) => useMountsFor(useBound(), query),
|
|
1081
|
+
useOutgoingShares: () => useOutgoingSharesFor(useBound())
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// src/components.tsx
|
|
1086
|
+
import {
|
|
1087
|
+
createContext as createContext2,
|
|
1088
|
+
Fragment,
|
|
1089
|
+
forwardRef,
|
|
1090
|
+
isValidElement,
|
|
1091
|
+
useContext as useContext3,
|
|
1092
|
+
useEffect as useEffect4,
|
|
1093
|
+
useRef as useRef3,
|
|
1094
|
+
useState as useState4
|
|
1095
|
+
} from "react";
|
|
1096
|
+
import { Avatar } from "@base-ui/react/avatar";
|
|
1097
|
+
import { Dialog } from "@base-ui/react/dialog";
|
|
1098
|
+
import { Menu } from "@base-ui/react/menu";
|
|
1099
|
+
import { Tabs } from "@base-ui/react/tabs";
|
|
1100
|
+
import { BasicError as BasicError6 } from "@basictech/core";
|
|
1101
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1102
|
+
var AppearanceContext = createContext2({});
|
|
1103
|
+
function appearanceStyle(appearance) {
|
|
1104
|
+
return {
|
|
1105
|
+
...appearance.accent ? { "--basic-accent": appearance.accent } : {},
|
|
1106
|
+
...appearance.radius ? { "--basic-radius": appearance.radius } : {}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function BasicUIProvider({
|
|
1110
|
+
appearance = {},
|
|
1111
|
+
children
|
|
1112
|
+
}) {
|
|
1113
|
+
const parent = useContext3(AppearanceContext);
|
|
1114
|
+
const value = { ...parent, ...appearance };
|
|
1115
|
+
return /* @__PURE__ */ jsx3(AppearanceContext.Provider, { value, children: /* @__PURE__ */ jsx3(
|
|
1116
|
+
"div",
|
|
1117
|
+
{
|
|
1118
|
+
className: "basic-ui",
|
|
1119
|
+
"data-theme": value.theme ?? "light",
|
|
1120
|
+
"data-density": value.density ?? "comfortable",
|
|
1121
|
+
style: appearanceStyle(value),
|
|
1122
|
+
children
|
|
1123
|
+
}
|
|
1124
|
+
) });
|
|
1125
|
+
}
|
|
1126
|
+
function useAppearanceProps() {
|
|
1127
|
+
const appearance = useContext3(AppearanceContext);
|
|
1128
|
+
return {
|
|
1129
|
+
"data-theme": appearance.theme ?? "light",
|
|
1130
|
+
"data-density": appearance.density ?? "comfortable",
|
|
1131
|
+
style: appearanceStyle(appearance)
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
var BasicButton = forwardRef(
|
|
1135
|
+
function BasicButton2({ variant = "outline", className = "", type = "button", ...props }, ref) {
|
|
1136
|
+
return /* @__PURE__ */ jsx3(
|
|
1137
|
+
"button",
|
|
1138
|
+
{
|
|
1139
|
+
...props,
|
|
1140
|
+
ref,
|
|
1141
|
+
type,
|
|
1142
|
+
className: `basic-button ${className}`,
|
|
1143
|
+
"data-variant": variant
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
);
|
|
1148
|
+
function useAction() {
|
|
1149
|
+
const [pending, setPending] = useState4(false);
|
|
1150
|
+
const [error, setError] = useState4(null);
|
|
1151
|
+
const busy = useRef3(false);
|
|
1152
|
+
const mounted = useRef3(true);
|
|
1153
|
+
useEffect4(() => {
|
|
1154
|
+
mounted.current = true;
|
|
1155
|
+
return () => {
|
|
1156
|
+
mounted.current = false;
|
|
1157
|
+
};
|
|
1158
|
+
}, []);
|
|
1159
|
+
async function run(action) {
|
|
1160
|
+
if (busy.current) return;
|
|
1161
|
+
busy.current = true;
|
|
1162
|
+
setPending(true);
|
|
1163
|
+
setError(null);
|
|
1164
|
+
try {
|
|
1165
|
+
await action();
|
|
1166
|
+
} catch (cause) {
|
|
1167
|
+
if (mounted.current)
|
|
1168
|
+
setError(
|
|
1169
|
+
cause instanceof BasicError6 && cause.code === "REDIRECT_URI_NOT_REGISTERED" ? "Sign-in is unavailable on this URL. Ask the app owner to register its callback URL in project settings." : cause instanceof Error ? cause.message : "The action failed. Please try again."
|
|
1170
|
+
);
|
|
1171
|
+
} finally {
|
|
1172
|
+
busy.current = false;
|
|
1173
|
+
if (mounted.current) setPending(false);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return { pending, error, run, clearError: () => setError(null) };
|
|
1177
|
+
}
|
|
1178
|
+
function AuthButton({
|
|
1179
|
+
auth: supplied,
|
|
1180
|
+
input,
|
|
1181
|
+
signOut = false,
|
|
1182
|
+
children,
|
|
1183
|
+
disabled,
|
|
1184
|
+
onClick,
|
|
1185
|
+
...props
|
|
1186
|
+
}) {
|
|
1187
|
+
const live = useAuth();
|
|
1188
|
+
const auth = supplied ?? live;
|
|
1189
|
+
const action = useAction();
|
|
1190
|
+
const expired = auth.status === "expired";
|
|
1191
|
+
return /* @__PURE__ */ jsxs("span", { className: "basic-action", children: [
|
|
1192
|
+
/* @__PURE__ */ jsx3(
|
|
1193
|
+
BasicButton,
|
|
1194
|
+
{
|
|
1195
|
+
...props,
|
|
1196
|
+
disabled: disabled || !auth.isReady || action.pending || (signOut ? !auth.isSignedIn && !expired : auth.isSignedIn && !expired),
|
|
1197
|
+
"aria-busy": action.pending,
|
|
1198
|
+
onClick: (event) => {
|
|
1199
|
+
onClick?.(event);
|
|
1200
|
+
if (!event.defaultPrevented)
|
|
1201
|
+
void action.run(
|
|
1202
|
+
() => signOut ? auth.signOut() : auth.signIn(input)
|
|
1203
|
+
);
|
|
1204
|
+
},
|
|
1205
|
+
children: action.pending ? "Please wait\u2026" : children ?? (signOut ? "Sign out" : expired ? "Sign in again" : "Sign in")
|
|
1206
|
+
}
|
|
1207
|
+
),
|
|
1208
|
+
/* @__PURE__ */ jsx3(ActionError, { error: action.error, onClose: action.clearError })
|
|
1209
|
+
] });
|
|
1210
|
+
}
|
|
1211
|
+
function SignInButton(props) {
|
|
1212
|
+
return /* @__PURE__ */ jsx3(AuthButton, { ...props });
|
|
1213
|
+
}
|
|
1214
|
+
function SignOutButton(props) {
|
|
1215
|
+
return /* @__PURE__ */ jsx3(AuthButton, { ...props, signOut: true });
|
|
1216
|
+
}
|
|
1217
|
+
function UserAvatar({
|
|
1218
|
+
accounts,
|
|
1219
|
+
auth,
|
|
1220
|
+
projectProfile,
|
|
1221
|
+
allowAddAccount,
|
|
1222
|
+
...avatarProps
|
|
1223
|
+
}) {
|
|
1224
|
+
return /* @__PURE__ */ jsx3(
|
|
1225
|
+
UserMenu,
|
|
1226
|
+
{
|
|
1227
|
+
accounts,
|
|
1228
|
+
auth,
|
|
1229
|
+
projectProfile,
|
|
1230
|
+
allowAddAccount,
|
|
1231
|
+
sync: avatarProps.sync,
|
|
1232
|
+
showSyncBadge: avatarProps.showSyncBadge,
|
|
1233
|
+
avatarProps
|
|
1234
|
+
}
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
function AccountAvatar({
|
|
1238
|
+
profile: supplied,
|
|
1239
|
+
size = 32,
|
|
1240
|
+
showSyncBadge,
|
|
1241
|
+
sync,
|
|
1242
|
+
className = "",
|
|
1243
|
+
style,
|
|
1244
|
+
...props
|
|
1245
|
+
}) {
|
|
1246
|
+
const accounts = useAccounts();
|
|
1247
|
+
const client = useRequiredClient();
|
|
1248
|
+
const profile = supplied === void 0 ? accounts.activeAccount : supplied;
|
|
1249
|
+
const name = profile?.name || (profile?.kind === "anon" ? "Local account" : profile?.handle || profile?.email || "Guest");
|
|
1250
|
+
const initials = name.trim().split(/\s+/).slice(0, 2).map((part) => Array.from(part)[0]).join("").toUpperCase();
|
|
1251
|
+
const avatar = /* @__PURE__ */ jsxs(
|
|
1252
|
+
Avatar.Root,
|
|
1253
|
+
{
|
|
1254
|
+
...props,
|
|
1255
|
+
className: `basic-avatar ${className}`,
|
|
1256
|
+
style: { width: size, height: size, ...style },
|
|
1257
|
+
role: "img",
|
|
1258
|
+
"aria-label": props["aria-label"] ?? name,
|
|
1259
|
+
children: [
|
|
1260
|
+
profile?.picture && /* @__PURE__ */ jsx3(
|
|
1261
|
+
Avatar.Image,
|
|
1262
|
+
{
|
|
1263
|
+
src: profile.picture,
|
|
1264
|
+
alt: "",
|
|
1265
|
+
className: "basic-avatar-image",
|
|
1266
|
+
referrerPolicy: "no-referrer"
|
|
1267
|
+
}
|
|
1268
|
+
),
|
|
1269
|
+
/* @__PURE__ */ jsx3(Avatar.Fallback, { className: "basic-avatar-fallback", children: initials || "?" })
|
|
1270
|
+
]
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
1273
|
+
return showSyncBadge ?? client.mode === "sync" ? /* @__PURE__ */ jsxs("span", { className: "basic-avatar-container", children: [
|
|
1274
|
+
avatar,
|
|
1275
|
+
/* @__PURE__ */ jsx3(SyncStatus, { sync, className: "basic-avatar-sync" })
|
|
1276
|
+
] }) : avatar;
|
|
1277
|
+
}
|
|
1278
|
+
function AuthStatus({
|
|
1279
|
+
auth: supplied,
|
|
1280
|
+
className = "",
|
|
1281
|
+
...props
|
|
1282
|
+
}) {
|
|
1283
|
+
const live = useAuth();
|
|
1284
|
+
const auth = supplied ?? live;
|
|
1285
|
+
const expired = auth.status === "expired";
|
|
1286
|
+
const label = !auth.isReady || auth.status === "bootstrapping" ? "Checking session" : expired ? "Session expired" : auth.status === "recovering" ? "Reconnecting session" : auth.isSignedIn ? "Signed in" : auth.isAnonymous ? "Guest \xB7 local only" : "Signed out";
|
|
1287
|
+
return /* @__PURE__ */ jsxs(
|
|
1288
|
+
"span",
|
|
1289
|
+
{
|
|
1290
|
+
...props,
|
|
1291
|
+
role: "status",
|
|
1292
|
+
className: `basic-status ${className}`,
|
|
1293
|
+
"data-tone": expired || auth.error ? "warning" : auth.isSignedIn ? "success" : "neutral",
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ jsx3("span", { className: "basic-dot" }),
|
|
1296
|
+
label
|
|
1297
|
+
]
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
function SyncStatus({
|
|
1302
|
+
sync: supplied,
|
|
1303
|
+
source,
|
|
1304
|
+
className = "",
|
|
1305
|
+
...props
|
|
1306
|
+
}) {
|
|
1307
|
+
const live = useSyncStatus(source);
|
|
1308
|
+
const client = useRequiredClient();
|
|
1309
|
+
const sync = supplied ?? live;
|
|
1310
|
+
if (client.mode === "rest") return null;
|
|
1311
|
+
const issues = sync.rejected.length + sync.conflicts.length;
|
|
1312
|
+
const labels = {
|
|
1313
|
+
idle: "Idle",
|
|
1314
|
+
bootstrapping: "Starting sync",
|
|
1315
|
+
connecting: "Connecting",
|
|
1316
|
+
online: "Synced",
|
|
1317
|
+
offline: "Offline",
|
|
1318
|
+
stopped: "Sync stopped",
|
|
1319
|
+
error: "Sync error",
|
|
1320
|
+
local: "Local only",
|
|
1321
|
+
stale: "Sync stale",
|
|
1322
|
+
ended: "Access ended"
|
|
1055
1323
|
};
|
|
1324
|
+
const label = issues ? `${issues} sync ${issues === 1 ? "issue" : "issues"}` : sync.status === "online" && sync.pendingCount ? "Syncing" : labels[sync.status];
|
|
1325
|
+
return /* @__PURE__ */ jsxs(
|
|
1326
|
+
"span",
|
|
1327
|
+
{
|
|
1328
|
+
...props,
|
|
1329
|
+
role: "status",
|
|
1330
|
+
title: props.title ?? `${label}${sync.pendingCount > 0 ? ` \xB7 ${sync.pendingCount} pending` : ""}`,
|
|
1331
|
+
className: `basic-status ${className}`,
|
|
1332
|
+
"data-tone": issues || ["error", "stale", "ended"].includes(sync.status) ? "warning" : sync.status === "online" && !sync.pendingCount ? "success" : ["bootstrapping", "connecting"].includes(sync.status) || sync.status === "online" && sync.pendingCount > 0 ? "progress" : "neutral",
|
|
1333
|
+
children: [
|
|
1334
|
+
/* @__PURE__ */ jsx3("span", { className: "basic-dot" }),
|
|
1335
|
+
label,
|
|
1336
|
+
sync.pendingCount > 0 && ` \xB7 ${sync.pendingCount} pending`
|
|
1337
|
+
]
|
|
1338
|
+
}
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
function displayHandle(handle) {
|
|
1342
|
+
return handle ? `@${handle.replace(/^@+/, "")}` : "";
|
|
1343
|
+
}
|
|
1344
|
+
function UserButton(props) {
|
|
1345
|
+
return /* @__PURE__ */ jsx3(UserMenu, { ...props, trigger: "button" });
|
|
1346
|
+
}
|
|
1347
|
+
function UserMenu({
|
|
1348
|
+
accounts: supplied,
|
|
1349
|
+
auth: suppliedAuth,
|
|
1350
|
+
sync,
|
|
1351
|
+
showSyncStatus = true,
|
|
1352
|
+
showSyncBadge,
|
|
1353
|
+
shape = "rounded",
|
|
1354
|
+
trigger = "avatar",
|
|
1355
|
+
avatarProps,
|
|
1356
|
+
projectProfile,
|
|
1357
|
+
allowAddAccount = true,
|
|
1358
|
+
className = "",
|
|
1359
|
+
accountSettingsUrl
|
|
1360
|
+
}) {
|
|
1361
|
+
const liveAccounts = useAccounts();
|
|
1362
|
+
const liveAuth = useAuth();
|
|
1363
|
+
const accounts = supplied ?? liveAccounts;
|
|
1364
|
+
const auth = suppliedAuth ?? liveAuth;
|
|
1365
|
+
const action = useAction();
|
|
1366
|
+
const [settingsOpen, setSettingsOpen] = useState4(false);
|
|
1367
|
+
const [clearAccountId, setClearAccountId] = useState4(null);
|
|
1368
|
+
const triggerRef = useRef3(null);
|
|
1369
|
+
const appearance = useAppearanceProps();
|
|
1370
|
+
const expired = auth.status === "expired";
|
|
1371
|
+
const active = accounts.activeAccount;
|
|
1372
|
+
const otherAccounts = accounts.accounts.filter(
|
|
1373
|
+
(account) => account.id !== active?.id
|
|
1374
|
+
);
|
|
1375
|
+
const name = active?.name || active?.kind !== "anon" && displayHandle(active?.handle) || "Local account";
|
|
1376
|
+
return /* @__PURE__ */ jsxs("span", { className: `basic-action ${className}`, children: [
|
|
1377
|
+
/* @__PURE__ */ jsxs(Menu.Root, { children: [
|
|
1378
|
+
/* @__PURE__ */ jsxs(
|
|
1379
|
+
Menu.Trigger,
|
|
1380
|
+
{
|
|
1381
|
+
ref: triggerRef,
|
|
1382
|
+
className: `basic-button basic-account-trigger basic-trigger-${trigger}`,
|
|
1383
|
+
"data-shape": shape,
|
|
1384
|
+
disabled: !auth.isReady || action.pending,
|
|
1385
|
+
"aria-label": "Open user menu",
|
|
1386
|
+
children: [
|
|
1387
|
+
/* @__PURE__ */ jsx3(
|
|
1388
|
+
AccountAvatar,
|
|
1389
|
+
{
|
|
1390
|
+
profile: accounts.activeAccount,
|
|
1391
|
+
sync,
|
|
1392
|
+
showSyncBadge,
|
|
1393
|
+
...avatarProps
|
|
1394
|
+
}
|
|
1395
|
+
),
|
|
1396
|
+
trigger === "button" && /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
1397
|
+
/* @__PURE__ */ jsxs("span", { className: "basic-identity", children: [
|
|
1398
|
+
/* @__PURE__ */ jsx3("strong", { children: name }),
|
|
1399
|
+
active?.kind !== "anon" && active?.handle && /* @__PURE__ */ jsx3("small", { children: displayHandle(active.handle) })
|
|
1400
|
+
] }),
|
|
1401
|
+
/* @__PURE__ */ jsx3(
|
|
1402
|
+
"svg",
|
|
1403
|
+
{
|
|
1404
|
+
className: "basic-chevron",
|
|
1405
|
+
width: "16",
|
|
1406
|
+
height: "16",
|
|
1407
|
+
viewBox: "0 0 24 24",
|
|
1408
|
+
fill: "none",
|
|
1409
|
+
stroke: "currentColor",
|
|
1410
|
+
strokeWidth: "2",
|
|
1411
|
+
strokeLinecap: "round",
|
|
1412
|
+
strokeLinejoin: "round",
|
|
1413
|
+
"aria-hidden": "true",
|
|
1414
|
+
children: /* @__PURE__ */ jsx3("path", { d: "m6 9 6 6 6-6" })
|
|
1415
|
+
}
|
|
1416
|
+
)
|
|
1417
|
+
] })
|
|
1418
|
+
]
|
|
1419
|
+
}
|
|
1420
|
+
),
|
|
1421
|
+
/* @__PURE__ */ jsx3(Menu.Portal, { children: /* @__PURE__ */ jsx3(Menu.Positioner, { sideOffset: 8, className: "basic-positioner", children: /* @__PURE__ */ jsxs(Menu.Popup, { ...appearance, className: "basic-ui basic-menu", children: [
|
|
1422
|
+
/* @__PURE__ */ jsxs("div", { className: "basic-menu-profile", children: [
|
|
1423
|
+
/* @__PURE__ */ jsx3(
|
|
1424
|
+
AccountAvatar,
|
|
1425
|
+
{
|
|
1426
|
+
profile: active,
|
|
1427
|
+
size: 48,
|
|
1428
|
+
sync,
|
|
1429
|
+
showSyncBadge: false
|
|
1430
|
+
}
|
|
1431
|
+
),
|
|
1432
|
+
/* @__PURE__ */ jsxs("span", { className: "basic-identity", children: [
|
|
1433
|
+
/* @__PURE__ */ jsx3("strong", { children: active ? name : "No active account" }),
|
|
1434
|
+
active?.kind !== "anon" && active?.handle && /* @__PURE__ */ jsx3("small", { children: displayHandle(active.handle) }),
|
|
1435
|
+
/* @__PURE__ */ jsxs("span", { className: "basic-account-statuses", children: [
|
|
1436
|
+
expired && /* @__PURE__ */ jsx3("span", { className: "basic-status", "data-tone": "warning", children: "Expired" }),
|
|
1437
|
+
showSyncStatus && /* @__PURE__ */ jsx3(SyncStatus, { sync })
|
|
1438
|
+
] })
|
|
1439
|
+
] })
|
|
1440
|
+
] }),
|
|
1441
|
+
expired && /* @__PURE__ */ jsx3(
|
|
1442
|
+
Menu.Item,
|
|
1443
|
+
{
|
|
1444
|
+
className: "basic-menu-item basic-menu-reauth",
|
|
1445
|
+
disabled: action.pending,
|
|
1446
|
+
onClick: () => void action.run(() => auth.signIn()),
|
|
1447
|
+
children: "Sign in again"
|
|
1448
|
+
}
|
|
1449
|
+
),
|
|
1450
|
+
/* @__PURE__ */ jsxs(
|
|
1451
|
+
Menu.Item,
|
|
1452
|
+
{
|
|
1453
|
+
className: "basic-menu-item basic-manage-account",
|
|
1454
|
+
onClick: () => setSettingsOpen(true),
|
|
1455
|
+
children: [
|
|
1456
|
+
/* @__PURE__ */ jsxs(
|
|
1457
|
+
"svg",
|
|
1458
|
+
{
|
|
1459
|
+
className: "basic-menu-icon",
|
|
1460
|
+
width: "16",
|
|
1461
|
+
height: "16",
|
|
1462
|
+
viewBox: "0 0 24 24",
|
|
1463
|
+
fill: "none",
|
|
1464
|
+
stroke: "currentColor",
|
|
1465
|
+
strokeWidth: "1.75",
|
|
1466
|
+
strokeLinecap: "round",
|
|
1467
|
+
strokeLinejoin: "round",
|
|
1468
|
+
"aria-hidden": "true",
|
|
1469
|
+
children: [
|
|
1470
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "7", r: "4" }),
|
|
1471
|
+
/* @__PURE__ */ jsx3("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" })
|
|
1472
|
+
]
|
|
1473
|
+
}
|
|
1474
|
+
),
|
|
1475
|
+
"Manage account"
|
|
1476
|
+
]
|
|
1477
|
+
}
|
|
1478
|
+
),
|
|
1479
|
+
(auth.isSignedIn || expired || active?.kind === "anon") && /* @__PURE__ */ jsxs(
|
|
1480
|
+
Menu.Item,
|
|
1481
|
+
{
|
|
1482
|
+
className: "basic-menu-item basic-menu-signout",
|
|
1483
|
+
disabled: action.pending,
|
|
1484
|
+
onClick: () => active?.kind === "anon" ? setClearAccountId(active.id) : void action.run(() => auth.signOut()),
|
|
1485
|
+
children: [
|
|
1486
|
+
/* @__PURE__ */ jsx3(
|
|
1487
|
+
"svg",
|
|
1488
|
+
{
|
|
1489
|
+
className: "basic-menu-icon",
|
|
1490
|
+
width: "16",
|
|
1491
|
+
height: "16",
|
|
1492
|
+
viewBox: "0 0 24 24",
|
|
1493
|
+
fill: "none",
|
|
1494
|
+
stroke: "currentColor",
|
|
1495
|
+
strokeWidth: "1.75",
|
|
1496
|
+
strokeLinecap: "round",
|
|
1497
|
+
strokeLinejoin: "round",
|
|
1498
|
+
"aria-hidden": "true",
|
|
1499
|
+
children: /* @__PURE__ */ jsx3("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9" })
|
|
1500
|
+
}
|
|
1501
|
+
),
|
|
1502
|
+
active?.kind === "anon" ? "Clear account" : "Sign out"
|
|
1503
|
+
]
|
|
1504
|
+
}
|
|
1505
|
+
),
|
|
1506
|
+
otherAccounts.length > 0 && /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
1507
|
+
/* @__PURE__ */ jsx3(Menu.Separator, { className: "basic-separator" }),
|
|
1508
|
+
/* @__PURE__ */ jsx3("div", { className: "basic-menu-heading", children: "Switch accounts" })
|
|
1509
|
+
] }),
|
|
1510
|
+
otherAccounts.map((account) => /* @__PURE__ */ jsxs(
|
|
1511
|
+
Menu.Item,
|
|
1512
|
+
{
|
|
1513
|
+
className: "basic-menu-item",
|
|
1514
|
+
disabled: action.pending,
|
|
1515
|
+
onClick: () => void action.run(() => accounts.switchAccount(account.id)),
|
|
1516
|
+
children: [
|
|
1517
|
+
/* @__PURE__ */ jsx3(
|
|
1518
|
+
AccountAvatar,
|
|
1519
|
+
{
|
|
1520
|
+
profile: account,
|
|
1521
|
+
size: 32,
|
|
1522
|
+
sync,
|
|
1523
|
+
showSyncBadge: false
|
|
1524
|
+
}
|
|
1525
|
+
),
|
|
1526
|
+
/* @__PURE__ */ jsxs("span", { className: "basic-identity", children: [
|
|
1527
|
+
/* @__PURE__ */ jsx3("strong", { children: account.name || account.kind !== "anon" && displayHandle(account.handle) || (account.kind === "anon" ? "Local account" : "Account") }),
|
|
1528
|
+
account.kind !== "anon" && account.handle && /* @__PURE__ */ jsx3("small", { children: displayHandle(account.handle) })
|
|
1529
|
+
] }),
|
|
1530
|
+
/* @__PURE__ */ jsx3("span", { className: "basic-account-statuses", children: account.auth.status === "expired" ? /* @__PURE__ */ jsx3("span", { className: "basic-status", "data-tone": "warning", children: "Expired" }) : null })
|
|
1531
|
+
]
|
|
1532
|
+
},
|
|
1533
|
+
account.id
|
|
1534
|
+
)),
|
|
1535
|
+
(allowAddAccount || !auth.isSignedIn && !expired) && /* @__PURE__ */ jsx3(
|
|
1536
|
+
Menu.Item,
|
|
1537
|
+
{
|
|
1538
|
+
className: "basic-menu-item",
|
|
1539
|
+
disabled: action.pending,
|
|
1540
|
+
onClick: () => void action.run(
|
|
1541
|
+
() => allowAddAccount ? accounts.addAccount({ signIn: true }) : auth.signIn()
|
|
1542
|
+
),
|
|
1543
|
+
children: "\uFF0B Add account"
|
|
1544
|
+
}
|
|
1545
|
+
)
|
|
1546
|
+
] }) }) })
|
|
1547
|
+
] }),
|
|
1548
|
+
/* @__PURE__ */ jsx3(
|
|
1549
|
+
ActionError,
|
|
1550
|
+
{
|
|
1551
|
+
error: action.error,
|
|
1552
|
+
onClose: action.clearError,
|
|
1553
|
+
finalFocus: triggerRef
|
|
1554
|
+
}
|
|
1555
|
+
),
|
|
1556
|
+
/* @__PURE__ */ jsx3(
|
|
1557
|
+
Modal,
|
|
1558
|
+
{
|
|
1559
|
+
open: clearAccountId !== null,
|
|
1560
|
+
onOpenChange: (open) => {
|
|
1561
|
+
if (!open) setClearAccountId(null);
|
|
1562
|
+
},
|
|
1563
|
+
trigger: null,
|
|
1564
|
+
finalFocus: triggerRef,
|
|
1565
|
+
title: "Clear local account?",
|
|
1566
|
+
description: "This deletes this account\u2019s local data, including unsynced changes. This cannot be undone.",
|
|
1567
|
+
children: /* @__PURE__ */ jsxs("div", { className: "basic-actions", children: [
|
|
1568
|
+
/* @__PURE__ */ jsx3(
|
|
1569
|
+
BasicButton,
|
|
1570
|
+
{
|
|
1571
|
+
disabled: action.pending,
|
|
1572
|
+
onClick: () => setClearAccountId(null),
|
|
1573
|
+
children: "Cancel"
|
|
1574
|
+
}
|
|
1575
|
+
),
|
|
1576
|
+
/* @__PURE__ */ jsx3(
|
|
1577
|
+
BasicButton,
|
|
1578
|
+
{
|
|
1579
|
+
disabled: action.pending,
|
|
1580
|
+
onClick: () => {
|
|
1581
|
+
const id = clearAccountId;
|
|
1582
|
+
if (!id) return;
|
|
1583
|
+
setClearAccountId(null);
|
|
1584
|
+
void action.run(() => accounts.removeAccount(id));
|
|
1585
|
+
},
|
|
1586
|
+
children: "Clear account"
|
|
1587
|
+
}
|
|
1588
|
+
)
|
|
1589
|
+
] })
|
|
1590
|
+
}
|
|
1591
|
+
),
|
|
1592
|
+
/* @__PURE__ */ jsx3(
|
|
1593
|
+
AccountSettingsModal,
|
|
1594
|
+
{
|
|
1595
|
+
auth,
|
|
1596
|
+
accounts,
|
|
1597
|
+
open: settingsOpen,
|
|
1598
|
+
onOpenChange: setSettingsOpen,
|
|
1599
|
+
trigger: null,
|
|
1600
|
+
finalFocus: triggerRef,
|
|
1601
|
+
accountSettingsUrl,
|
|
1602
|
+
projectProfile,
|
|
1603
|
+
sync
|
|
1604
|
+
}
|
|
1605
|
+
)
|
|
1606
|
+
] });
|
|
1607
|
+
}
|
|
1608
|
+
function ActionError({
|
|
1609
|
+
error,
|
|
1610
|
+
onClose,
|
|
1611
|
+
finalFocus
|
|
1612
|
+
}) {
|
|
1613
|
+
return /* @__PURE__ */ jsxs(
|
|
1614
|
+
Modal,
|
|
1615
|
+
{
|
|
1616
|
+
open: !!error,
|
|
1617
|
+
onOpenChange: (open) => {
|
|
1618
|
+
if (!open) onClose();
|
|
1619
|
+
},
|
|
1620
|
+
trigger: null,
|
|
1621
|
+
finalFocus,
|
|
1622
|
+
title: "Unable to complete action",
|
|
1623
|
+
description: "Your request could not be completed.",
|
|
1624
|
+
children: [
|
|
1625
|
+
/* @__PURE__ */ jsx3("p", { role: "alert", className: "basic-error", children: error }),
|
|
1626
|
+
/* @__PURE__ */ jsx3(BasicButton, { onClick: onClose, children: "Dismiss" })
|
|
1627
|
+
]
|
|
1628
|
+
}
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
function Modal({
|
|
1632
|
+
open,
|
|
1633
|
+
onOpenChange,
|
|
1634
|
+
trigger,
|
|
1635
|
+
finalFocus,
|
|
1636
|
+
title,
|
|
1637
|
+
description,
|
|
1638
|
+
header,
|
|
1639
|
+
sectionLabel,
|
|
1640
|
+
children
|
|
1641
|
+
}) {
|
|
1642
|
+
const appearance = useAppearanceProps();
|
|
1643
|
+
return /* @__PURE__ */ jsxs(Dialog.Root, { open, onOpenChange, children: [
|
|
1644
|
+
trigger !== null && (isValidElement(trigger) ? /* @__PURE__ */ jsx3(Dialog.Trigger, { render: trigger }) : /* @__PURE__ */ jsx3(Dialog.Trigger, { render: /* @__PURE__ */ jsx3(BasicButton, {}), children: trigger ?? title })),
|
|
1645
|
+
/* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
1646
|
+
/* @__PURE__ */ jsx3(Dialog.Backdrop, { className: "basic-backdrop" }),
|
|
1647
|
+
/* @__PURE__ */ jsxs(
|
|
1648
|
+
Dialog.Popup,
|
|
1649
|
+
{
|
|
1650
|
+
...appearance,
|
|
1651
|
+
finalFocus,
|
|
1652
|
+
className: "basic-ui basic-dialog",
|
|
1653
|
+
children: [
|
|
1654
|
+
/* @__PURE__ */ jsxs(
|
|
1655
|
+
"div",
|
|
1656
|
+
{
|
|
1657
|
+
className: `basic-dialog-heading${header ? " basic-dialog-heading-overlay" : ""}`,
|
|
1658
|
+
children: [
|
|
1659
|
+
/* @__PURE__ */ jsx3(Dialog.Title, { className: header ? "basic-sr-only" : "basic-title", children: title }),
|
|
1660
|
+
/* @__PURE__ */ jsx3(
|
|
1661
|
+
Dialog.Close,
|
|
1662
|
+
{
|
|
1663
|
+
className: "basic-button",
|
|
1664
|
+
"aria-label": `Close ${title.toLowerCase()}`,
|
|
1665
|
+
children: "\u2715"
|
|
1666
|
+
}
|
|
1667
|
+
)
|
|
1668
|
+
]
|
|
1669
|
+
}
|
|
1670
|
+
),
|
|
1671
|
+
header,
|
|
1672
|
+
/* @__PURE__ */ jsx3(
|
|
1673
|
+
Dialog.Description,
|
|
1674
|
+
{
|
|
1675
|
+
className: header ? "basic-sr-only" : "basic-muted",
|
|
1676
|
+
children: description
|
|
1677
|
+
}
|
|
1678
|
+
),
|
|
1679
|
+
sectionLabel && /* @__PURE__ */ jsx3("div", { className: "basic-modal-section", children: sectionLabel }),
|
|
1680
|
+
children
|
|
1681
|
+
]
|
|
1682
|
+
}
|
|
1683
|
+
)
|
|
1684
|
+
] })
|
|
1685
|
+
] });
|
|
1686
|
+
}
|
|
1687
|
+
function AccountSettingsModal({
|
|
1688
|
+
auth: suppliedAuth,
|
|
1689
|
+
accounts: suppliedAccounts,
|
|
1690
|
+
accountSettingsUrl,
|
|
1691
|
+
projectProfile,
|
|
1692
|
+
sync,
|
|
1693
|
+
children,
|
|
1694
|
+
...props
|
|
1695
|
+
}) {
|
|
1696
|
+
const liveAuth = useAuth();
|
|
1697
|
+
const liveAccounts = useAccounts();
|
|
1698
|
+
const auth = suppliedAuth ?? liveAuth;
|
|
1699
|
+
const accounts = suppliedAccounts ?? liveAccounts;
|
|
1700
|
+
const liveSync = useSyncStatus();
|
|
1701
|
+
const syncState = sync ?? liveSync;
|
|
1702
|
+
const client = useRequiredClient();
|
|
1703
|
+
const account = accounts.activeAccount;
|
|
1704
|
+
const copyAction = useAction();
|
|
1705
|
+
const [copied, setCopied] = useState4(null);
|
|
1706
|
+
const details = [
|
|
1707
|
+
[
|
|
1708
|
+
"Account DID",
|
|
1709
|
+
account?.kind === "anon" ? "Not assigned (local account)" : account?.did ?? auth.did ?? "Unavailable"
|
|
1710
|
+
],
|
|
1711
|
+
[
|
|
1712
|
+
"PDS URL",
|
|
1713
|
+
account?.kind === "anon" ? "Not connected (local account)" : auth.user?.pds_url ?? "Unavailable"
|
|
1714
|
+
],
|
|
1715
|
+
["Local account ID", account?.id ?? "None"],
|
|
1716
|
+
["Account kind", account?.kind ?? "None"],
|
|
1717
|
+
["Storage prefix", account?.storagePrefix ?? "None"],
|
|
1718
|
+
["Client mode", client.mode],
|
|
1719
|
+
["Auth state", auth.status],
|
|
1720
|
+
["Ready", auth.isReady ? "Yes" : "No"],
|
|
1721
|
+
["Can write", auth.canWrite ? "Yes" : "No"],
|
|
1722
|
+
["Read-only reason", auth.readOnlyReason ?? "None"],
|
|
1723
|
+
["Auth error code", auth.error?.code ?? "None"],
|
|
1724
|
+
["Sync state", syncState.status],
|
|
1725
|
+
["Pending writes", String(syncState.pendingCount)],
|
|
1726
|
+
["Rejected writes", String(syncState.rejected.length)],
|
|
1727
|
+
["Conflicts", String(syncState.conflicts.length)]
|
|
1728
|
+
];
|
|
1729
|
+
const diagnostics = JSON.stringify(Object.fromEntries(details), null, 2);
|
|
1730
|
+
return /* @__PURE__ */ jsx3(
|
|
1731
|
+
Modal,
|
|
1732
|
+
{
|
|
1733
|
+
...props,
|
|
1734
|
+
title: "Account settings",
|
|
1735
|
+
description: "Your identity and session on this device.",
|
|
1736
|
+
header: /* @__PURE__ */ jsx3(AccountHeader, { account: accounts.activeAccount, sync }),
|
|
1737
|
+
children: /* @__PURE__ */ jsxs(Tabs.Root, { defaultValue: "profile", children: [
|
|
1738
|
+
/* @__PURE__ */ jsxs(
|
|
1739
|
+
Tabs.List,
|
|
1740
|
+
{
|
|
1741
|
+
className: "basic-tabs",
|
|
1742
|
+
"aria-label": "Account settings sections",
|
|
1743
|
+
children: [
|
|
1744
|
+
/* @__PURE__ */ jsx3(Tabs.Tab, { value: "profile", children: "Profile" }),
|
|
1745
|
+
/* @__PURE__ */ jsx3(Tabs.Tab, { value: "advanced", children: "Advanced" })
|
|
1746
|
+
]
|
|
1747
|
+
}
|
|
1748
|
+
),
|
|
1749
|
+
/* @__PURE__ */ jsxs(Tabs.Panel, { value: "profile", keepMounted: true, children: [
|
|
1750
|
+
auth.isSignedIn && /* @__PURE__ */ jsx3(
|
|
1751
|
+
ProjectProfileEditor,
|
|
1752
|
+
{
|
|
1753
|
+
profile: projectProfile,
|
|
1754
|
+
canWrite: auth.canWrite
|
|
1755
|
+
},
|
|
1756
|
+
accounts.activeAccount?.id ?? auth.did
|
|
1757
|
+
),
|
|
1758
|
+
accountSettingsUrl && /* @__PURE__ */ jsx3("a", { className: "basic-link", href: accountSettingsUrl, children: "Manage profile and security \u2197" }),
|
|
1759
|
+
children,
|
|
1760
|
+
!auth.isSignedIn && /* @__PURE__ */ jsx3("div", { className: "basic-actions", children: /* @__PURE__ */ jsx3(SignInButton, { auth }) })
|
|
1761
|
+
] }),
|
|
1762
|
+
/* @__PURE__ */ jsxs(Tabs.Panel, { value: "advanced", children: [
|
|
1763
|
+
/* @__PURE__ */ jsx3("p", { className: "basic-muted", children: "Read-only diagnostics for this account." }),
|
|
1764
|
+
/* @__PURE__ */ jsx3("dl", { className: "basic-details", children: details.map(([label, value]) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1765
|
+
/* @__PURE__ */ jsx3("dt", { children: label }),
|
|
1766
|
+
/* @__PURE__ */ jsx3("dd", { children: value })
|
|
1767
|
+
] }, label)) }),
|
|
1768
|
+
/* @__PURE__ */ jsxs("div", { className: "basic-actions", children: [
|
|
1769
|
+
/* @__PURE__ */ jsx3(
|
|
1770
|
+
BasicButton,
|
|
1771
|
+
{
|
|
1772
|
+
disabled: copyAction.pending,
|
|
1773
|
+
onClick: () => {
|
|
1774
|
+
setCopied(null);
|
|
1775
|
+
void copyAction.run(async () => {
|
|
1776
|
+
if (!navigator.clipboard?.writeText)
|
|
1777
|
+
throw new Error("Clipboard is unavailable in this browser.");
|
|
1778
|
+
await navigator.clipboard.writeText(diagnostics);
|
|
1779
|
+
setCopied(diagnostics);
|
|
1780
|
+
});
|
|
1781
|
+
},
|
|
1782
|
+
children: "Copy to clipboard"
|
|
1783
|
+
}
|
|
1784
|
+
),
|
|
1785
|
+
copied === diagnostics && /* @__PURE__ */ jsx3("span", { role: "status", className: "basic-muted", children: "Copied" })
|
|
1786
|
+
] }),
|
|
1787
|
+
copyAction.error && /* @__PURE__ */ jsx3("p", { role: "alert", className: "basic-error", children: copyAction.error })
|
|
1788
|
+
] })
|
|
1789
|
+
] })
|
|
1790
|
+
}
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
function AccountHeader({
|
|
1794
|
+
account,
|
|
1795
|
+
sync
|
|
1796
|
+
}) {
|
|
1797
|
+
return /* @__PURE__ */ jsxs("div", { className: "basic-profile", children: [
|
|
1798
|
+
/* @__PURE__ */ jsx3(
|
|
1799
|
+
AccountAvatar,
|
|
1800
|
+
{
|
|
1801
|
+
profile: account,
|
|
1802
|
+
size: 72,
|
|
1803
|
+
sync,
|
|
1804
|
+
showSyncBadge: false
|
|
1805
|
+
}
|
|
1806
|
+
),
|
|
1807
|
+
/* @__PURE__ */ jsxs("span", { className: "basic-identity", children: [
|
|
1808
|
+
/* @__PURE__ */ jsx3("strong", { children: account?.name || account?.kind !== "anon" && displayHandle(account?.handle) || "Local account" }),
|
|
1809
|
+
account?.kind !== "anon" && account?.handle && /* @__PURE__ */ jsx3("small", { children: displayHandle(account.handle) }),
|
|
1810
|
+
/* @__PURE__ */ jsx3("span", { className: "basic-account-statuses", children: /* @__PURE__ */ jsx3(SyncStatus, { sync }) })
|
|
1811
|
+
] })
|
|
1812
|
+
] });
|
|
1813
|
+
}
|
|
1814
|
+
function ProjectProfileEditor({
|
|
1815
|
+
profile: supplied,
|
|
1816
|
+
canWrite
|
|
1817
|
+
}) {
|
|
1818
|
+
const live = useProjectProfile(supplied === void 0);
|
|
1819
|
+
const profile = supplied ?? live;
|
|
1820
|
+
const action = useAction();
|
|
1821
|
+
const [draft, setDraft] = useState4({});
|
|
1822
|
+
const [saved, setSaved] = useState4(false);
|
|
1823
|
+
return /* @__PURE__ */ jsxs(
|
|
1824
|
+
"form",
|
|
1825
|
+
{
|
|
1826
|
+
className: "basic-profile-editor",
|
|
1827
|
+
onSubmit: (event) => {
|
|
1828
|
+
event.preventDefault();
|
|
1829
|
+
if (!canWrite || !Object.keys(draft).length) return;
|
|
1830
|
+
void action.run(async () => {
|
|
1831
|
+
await profile.update(draft);
|
|
1832
|
+
setDraft({});
|
|
1833
|
+
setSaved(true);
|
|
1834
|
+
});
|
|
1835
|
+
},
|
|
1836
|
+
children: [
|
|
1837
|
+
/* @__PURE__ */ jsx3("h3", { className: "basic-section-label", children: "Personal information" }),
|
|
1838
|
+
/* @__PURE__ */ jsx3("p", { className: "basic-muted", children: "Only this app sees these overrides. Clear a field to use your universal profile value." }),
|
|
1839
|
+
profile.isLoading ? /* @__PURE__ */ jsx3("p", { role: "status", children: "Loading project profile\u2026" }) : profile.error ? /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
1840
|
+
/* @__PURE__ */ jsx3("p", { role: "alert", className: "basic-error", children: profile.error.message }),
|
|
1841
|
+
/* @__PURE__ */ jsx3(BasicButton, { onClick: profile.refresh, children: "Retry profile" })
|
|
1842
|
+
] }) : profile.data && /* @__PURE__ */ jsxs("fieldset", { disabled: !canWrite || action.pending, children: [
|
|
1843
|
+
/* @__PURE__ */ jsxs("label", { className: "basic-field", children: [
|
|
1844
|
+
"Display name",
|
|
1845
|
+
/* @__PURE__ */ jsx3(
|
|
1846
|
+
"input",
|
|
1847
|
+
{
|
|
1848
|
+
className: "basic-input",
|
|
1849
|
+
type: "text",
|
|
1850
|
+
maxLength: 64,
|
|
1851
|
+
value: ("name" in draft ? draft.name : profile.data?.profile.name) ?? "",
|
|
1852
|
+
onChange: (event) => {
|
|
1853
|
+
setSaved(false);
|
|
1854
|
+
setDraft((current) => ({
|
|
1855
|
+
...current,
|
|
1856
|
+
name: event.target.value || null
|
|
1857
|
+
}));
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
)
|
|
1861
|
+
] }),
|
|
1862
|
+
/* @__PURE__ */ jsxs("div", { className: "basic-actions basic-modal-footer", children: [
|
|
1863
|
+
/* @__PURE__ */ jsx3(
|
|
1864
|
+
BasicButton,
|
|
1865
|
+
{
|
|
1866
|
+
variant: "ghost",
|
|
1867
|
+
onClick: () => {
|
|
1868
|
+
setDraft({});
|
|
1869
|
+
setSaved(false);
|
|
1870
|
+
},
|
|
1871
|
+
children: "Cancel"
|
|
1872
|
+
}
|
|
1873
|
+
),
|
|
1874
|
+
/* @__PURE__ */ jsx3(
|
|
1875
|
+
BasicButton,
|
|
1876
|
+
{
|
|
1877
|
+
type: "submit",
|
|
1878
|
+
variant: "solid",
|
|
1879
|
+
disabled: !Object.keys(draft).length,
|
|
1880
|
+
children: "Save changes"
|
|
1881
|
+
}
|
|
1882
|
+
)
|
|
1883
|
+
] })
|
|
1884
|
+
] }),
|
|
1885
|
+
action.error && /* @__PURE__ */ jsx3("p", { role: "alert", className: "basic-error", children: action.error }),
|
|
1886
|
+
saved && /* @__PURE__ */ jsx3("p", { role: "status", children: "Project profile saved." })
|
|
1887
|
+
]
|
|
1888
|
+
}
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
function SharesModal({
|
|
1892
|
+
auth: supplied,
|
|
1893
|
+
accounts: suppliedAccounts,
|
|
1894
|
+
sync,
|
|
1895
|
+
shares,
|
|
1896
|
+
scope,
|
|
1897
|
+
repo = "default",
|
|
1898
|
+
...props
|
|
1899
|
+
}) {
|
|
1900
|
+
const live = useAuth();
|
|
1901
|
+
const liveAccounts = useAccounts();
|
|
1902
|
+
const auth = supplied ?? live;
|
|
1903
|
+
return /* @__PURE__ */ jsx3(
|
|
1904
|
+
Modal,
|
|
1905
|
+
{
|
|
1906
|
+
...props,
|
|
1907
|
+
title: "Shares",
|
|
1908
|
+
description: "Manage incoming invitations and share access to selected data.",
|
|
1909
|
+
header: /* @__PURE__ */ jsx3(
|
|
1910
|
+
AccountHeader,
|
|
1911
|
+
{
|
|
1912
|
+
account: (suppliedAccounts ?? liveAccounts).activeAccount,
|
|
1913
|
+
sync
|
|
1914
|
+
}
|
|
1915
|
+
),
|
|
1916
|
+
children: !auth.isReady ? /* @__PURE__ */ jsx3("p", { role: "status", children: "Checking session\u2026" }) : !auth.isSignedIn ? /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
1917
|
+
/* @__PURE__ */ jsx3("p", { className: "basic-muted", children: "Sign in to share data." }),
|
|
1918
|
+
/* @__PURE__ */ jsx3(SignInButton, { auth })
|
|
1919
|
+
] }) : /* @__PURE__ */ jsx3(
|
|
1920
|
+
SharesContent,
|
|
1921
|
+
{
|
|
1922
|
+
shares,
|
|
1923
|
+
scope,
|
|
1924
|
+
repo,
|
|
1925
|
+
canWrite: auth.canWrite
|
|
1926
|
+
},
|
|
1927
|
+
auth.did ?? "account"
|
|
1928
|
+
)
|
|
1929
|
+
}
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
function SharesContent({
|
|
1933
|
+
shares: supplied,
|
|
1934
|
+
scope,
|
|
1935
|
+
repo,
|
|
1936
|
+
canWrite
|
|
1937
|
+
}) {
|
|
1938
|
+
const live = useOutgoingShares();
|
|
1939
|
+
const shares = supplied ?? live;
|
|
1940
|
+
const action = useAction();
|
|
1941
|
+
const [recipient, setRecipient] = useState4("");
|
|
1942
|
+
const [role, setRole] = useState4("viewer");
|
|
1943
|
+
const [confirmation, setConfirmation] = useState4(null);
|
|
1944
|
+
const [message, setMessage] = useState4("");
|
|
1945
|
+
const scopeValid = scope.length > 0 && scope.every(
|
|
1946
|
+
(item) => item.table.trim() && (item.recordIds === void 0 || item.recordIds.length > 0)
|
|
1947
|
+
);
|
|
1948
|
+
return /* @__PURE__ */ jsxs(Tabs.Root, { defaultValue: "outgoing", className: "basic-shares", children: [
|
|
1949
|
+
/* @__PURE__ */ jsxs(Tabs.List, { className: "basic-tabs", "aria-label": "Share direction", children: [
|
|
1950
|
+
/* @__PURE__ */ jsx3(Tabs.Tab, { value: "outgoing", children: "Outgoing" }),
|
|
1951
|
+
/* @__PURE__ */ jsx3(Tabs.Tab, { value: "incoming", children: "Incoming invitations" })
|
|
1952
|
+
] }),
|
|
1953
|
+
/* @__PURE__ */ jsx3(Tabs.Panel, { value: "incoming", children: /* @__PURE__ */ jsxs("section", { "aria-label": "Incoming invitations", children: [
|
|
1954
|
+
/* @__PURE__ */ jsx3("h3", { className: "basic-title", children: "Incoming invitations" }),
|
|
1955
|
+
/* @__PURE__ */ jsx3("p", { className: "basic-muted", children: "View, accept, or decline incoming invitations in Basic ID. Your app session cannot access the account-wide inbox." }),
|
|
1956
|
+
/* @__PURE__ */ jsx3("a", { className: "basic-link", href: shares.manageUrl(), children: "Open incoming invitations in Basic ID \u2197" })
|
|
1957
|
+
] }) }),
|
|
1958
|
+
/* @__PURE__ */ jsxs(Tabs.Panel, { value: "outgoing", children: [
|
|
1959
|
+
/* @__PURE__ */ jsx3("p", { className: "basic-muted", children: "Revoking access stops future requests. It cannot retract data the recipient already downloaded." }),
|
|
1960
|
+
/* @__PURE__ */ jsxs("div", { className: "basic-scope", children: [
|
|
1961
|
+
/* @__PURE__ */ jsx3("strong", { children: "New invitation permissions" }),
|
|
1962
|
+
scope.map((item, index) => /* @__PURE__ */ jsxs("span", { children: [
|
|
1963
|
+
item.table,
|
|
1964
|
+
" \xB7",
|
|
1965
|
+
" ",
|
|
1966
|
+
item.recordIds ? `${item.recordIds.length} selected records` : "all records"
|
|
1967
|
+
] }, index))
|
|
1968
|
+
] }),
|
|
1969
|
+
!canWrite && /* @__PURE__ */ jsx3("p", { className: "basic-error", children: "This session is read only. Sharing changes are disabled." }),
|
|
1970
|
+
/* @__PURE__ */ jsxs(
|
|
1971
|
+
"form",
|
|
1972
|
+
{
|
|
1973
|
+
className: "basic-share-form",
|
|
1974
|
+
onSubmit: (event) => {
|
|
1975
|
+
event.preventDefault();
|
|
1976
|
+
if (!recipient.trim() || !scopeValid || !canWrite) return;
|
|
1977
|
+
void action.run(async () => {
|
|
1978
|
+
await shares.create({
|
|
1979
|
+
repo,
|
|
1980
|
+
scope,
|
|
1981
|
+
role,
|
|
1982
|
+
...recipient.trim().startsWith("did:") ? { recipientDid: recipient.trim() } : { recipientHandle: recipient.trim().replace(/^@+/, "") }
|
|
1983
|
+
});
|
|
1984
|
+
setRecipient("");
|
|
1985
|
+
setMessage("Invitation sent.");
|
|
1986
|
+
shares.refresh();
|
|
1987
|
+
});
|
|
1988
|
+
},
|
|
1989
|
+
children: [
|
|
1990
|
+
/* @__PURE__ */ jsxs("label", { className: "basic-field", children: [
|
|
1991
|
+
"Recipient handle or DID",
|
|
1992
|
+
/* @__PURE__ */ jsx3(
|
|
1993
|
+
"input",
|
|
1994
|
+
{
|
|
1995
|
+
className: "basic-input",
|
|
1996
|
+
value: recipient,
|
|
1997
|
+
onChange: (event) => setRecipient(event.target.value),
|
|
1998
|
+
placeholder: "@alex.basic.id",
|
|
1999
|
+
required: true,
|
|
2000
|
+
disabled: action.pending || !canWrite
|
|
2001
|
+
}
|
|
2002
|
+
)
|
|
2003
|
+
] }),
|
|
2004
|
+
/* @__PURE__ */ jsxs("label", { className: "basic-field", children: [
|
|
2005
|
+
"Permission",
|
|
2006
|
+
/* @__PURE__ */ jsxs(
|
|
2007
|
+
"select",
|
|
2008
|
+
{
|
|
2009
|
+
className: "basic-input",
|
|
2010
|
+
value: role,
|
|
2011
|
+
onChange: (event) => setRole(event.target.value),
|
|
2012
|
+
disabled: action.pending || !canWrite,
|
|
2013
|
+
children: [
|
|
2014
|
+
/* @__PURE__ */ jsx3("option", { value: "viewer", children: "Can view" }),
|
|
2015
|
+
/* @__PURE__ */ jsx3("option", { value: "editor", children: "Can edit" })
|
|
2016
|
+
]
|
|
2017
|
+
}
|
|
2018
|
+
)
|
|
2019
|
+
] }),
|
|
2020
|
+
/* @__PURE__ */ jsx3(
|
|
2021
|
+
BasicButton,
|
|
2022
|
+
{
|
|
2023
|
+
type: "submit",
|
|
2024
|
+
variant: "solid",
|
|
2025
|
+
disabled: action.pending || !canWrite || !scopeValid || !recipient.trim(),
|
|
2026
|
+
children: action.pending ? "Please wait\u2026" : "Send invitation"
|
|
2027
|
+
}
|
|
2028
|
+
)
|
|
2029
|
+
]
|
|
2030
|
+
}
|
|
2031
|
+
),
|
|
2032
|
+
!scopeValid && /* @__PURE__ */ jsx3("p", { role: "alert", className: "basic-error", children: "Select at least one table or record to share." }),
|
|
2033
|
+
action.error && /* @__PURE__ */ jsx3("p", { role: "alert", className: "basic-error", children: action.error }),
|
|
2034
|
+
message && /* @__PURE__ */ jsx3("p", { role: "status", className: "basic-muted", children: message }),
|
|
2035
|
+
/* @__PURE__ */ jsxs("div", { className: "basic-section-heading", children: [
|
|
2036
|
+
/* @__PURE__ */ jsx3("h3", { className: "basic-title", children: "Outgoing shares \xB7 all permissions" }),
|
|
2037
|
+
/* @__PURE__ */ jsx3(
|
|
2038
|
+
BasicButton,
|
|
2039
|
+
{
|
|
2040
|
+
disabled: action.pending || shares.isLoading,
|
|
2041
|
+
onClick: shares.refresh,
|
|
2042
|
+
children: "Refresh"
|
|
2043
|
+
}
|
|
2044
|
+
)
|
|
2045
|
+
] }),
|
|
2046
|
+
shares.isLoading ? /* @__PURE__ */ jsx3("p", { role: "status", children: "Loading shares\u2026" }) : shares.error ? /* @__PURE__ */ jsx3("p", { role: "alert", className: "basic-error", children: shares.error.message }) : !shares.data.length ? /* @__PURE__ */ jsx3("p", { className: "basic-empty", children: "No outgoing shares yet." }) : /* @__PURE__ */ jsx3("ul", { className: "basic-share-list", children: shares.data.map((share) => /* @__PURE__ */ jsxs("li", { children: [
|
|
2047
|
+
/* @__PURE__ */ jsxs("div", { className: "basic-identity", children: [
|
|
2048
|
+
/* @__PURE__ */ jsx3("strong", { children: share.display.shareName || share.recipientDid }),
|
|
2049
|
+
/* @__PURE__ */ jsxs("small", { children: [
|
|
2050
|
+
share.role === "viewer" ? "Can view" : "Can edit",
|
|
2051
|
+
" \xB7",
|
|
2052
|
+
" ",
|
|
2053
|
+
share.effectiveState,
|
|
2054
|
+
" \xB7",
|
|
2055
|
+
" ",
|
|
2056
|
+
share.scope.map((item) => item.table).join(", ")
|
|
2057
|
+
] })
|
|
2058
|
+
] }),
|
|
2059
|
+
share.state !== "ended" && (confirmation === share.id ? /* @__PURE__ */ jsxs("div", { className: "basic-actions", children: [
|
|
2060
|
+
/* @__PURE__ */ jsx3("span", { children: "Remove access?" }),
|
|
2061
|
+
/* @__PURE__ */ jsxs(
|
|
2062
|
+
BasicButton,
|
|
2063
|
+
{
|
|
2064
|
+
disabled: action.pending || !canWrite,
|
|
2065
|
+
onClick: () => void action.run(async () => {
|
|
2066
|
+
await (share.state === "pending" ? shares.cancel(share.id) : shares.revoke(share.id));
|
|
2067
|
+
setConfirmation(null);
|
|
2068
|
+
setMessage(
|
|
2069
|
+
share.state === "pending" ? "Invitation canceled." : "Access revoked."
|
|
2070
|
+
);
|
|
2071
|
+
shares.refresh();
|
|
2072
|
+
}),
|
|
2073
|
+
children: [
|
|
2074
|
+
"Confirm",
|
|
2075
|
+
" ",
|
|
2076
|
+
share.state === "pending" ? "cancel" : "revoke"
|
|
2077
|
+
]
|
|
2078
|
+
}
|
|
2079
|
+
),
|
|
2080
|
+
/* @__PURE__ */ jsx3(
|
|
2081
|
+
BasicButton,
|
|
2082
|
+
{
|
|
2083
|
+
disabled: action.pending,
|
|
2084
|
+
onClick: () => setConfirmation(null),
|
|
2085
|
+
children: "Keep access"
|
|
2086
|
+
}
|
|
2087
|
+
)
|
|
2088
|
+
] }) : /* @__PURE__ */ jsx3(
|
|
2089
|
+
BasicButton,
|
|
2090
|
+
{
|
|
2091
|
+
disabled: action.pending || !canWrite,
|
|
2092
|
+
onClick: () => setConfirmation(share.id),
|
|
2093
|
+
children: share.state === "pending" ? "Cancel invitation" : "Revoke access"
|
|
2094
|
+
}
|
|
2095
|
+
))
|
|
2096
|
+
] }, share.id)) })
|
|
2097
|
+
] })
|
|
2098
|
+
] });
|
|
1056
2099
|
}
|
|
1057
2100
|
export {
|
|
2101
|
+
AccountSettingsModal,
|
|
2102
|
+
AuthStatus,
|
|
2103
|
+
BasicButton,
|
|
1058
2104
|
BasicProvider,
|
|
2105
|
+
BasicUIProvider,
|
|
1059
2106
|
BrowserKeyValueStorage,
|
|
1060
2107
|
BrowserTokenStore,
|
|
1061
2108
|
PersistenceStore,
|
|
2109
|
+
SharesModal,
|
|
2110
|
+
SignInButton,
|
|
2111
|
+
SignOutButton,
|
|
2112
|
+
SyncStatus,
|
|
2113
|
+
UserAvatar,
|
|
2114
|
+
UserButton,
|
|
2115
|
+
UserMenu,
|
|
1062
2116
|
browserCurrentUrl,
|
|
1063
2117
|
browserNavigate,
|
|
1064
2118
|
browserReplaceUrl,
|
|
@@ -1076,6 +2130,7 @@ export {
|
|
|
1076
2130
|
useFiles,
|
|
1077
2131
|
useMounts,
|
|
1078
2132
|
useOutgoingShares,
|
|
2133
|
+
useProjectProfile,
|
|
1079
2134
|
useQuery,
|
|
1080
2135
|
useRepos,
|
|
1081
2136
|
useSchemaStatus,
|