@oxyhq/services 22.1.0 → 22.3.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/lib/commonjs/index.js +6 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +24 -19
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/OxyAuthChooser.js +107 -106
- package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js +0 -19
- package/lib/commonjs/ui/components/oxyAuthChooserStyles.js.map +1 -1
- package/lib/commonjs/ui/components/passkeyHubPopup.js +33 -0
- package/lib/commonjs/ui/components/passkeyHubPopup.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +7 -1
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/useOxyAccountGraph.js +2 -0
- package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFollow.js +53 -10
- package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +2 -0
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/index.js +6 -0
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/stores/followStore.js +276 -92
- package/lib/commonjs/ui/stores/followStore.js.map +1 -1
- package/lib/commonjs/ui/stores/resetSessionScopedStores.js +14 -0
- package/lib/commonjs/ui/stores/resetSessionScopedStores.js.map +1 -0
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +24 -19
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/OxyAuthChooser.js +108 -107
- package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
- package/lib/module/ui/components/oxyAuthChooserStyles.js +0 -19
- package/lib/module/ui/components/oxyAuthChooserStyles.js.map +1 -1
- package/lib/module/ui/components/passkeyHubPopup.js +29 -0
- package/lib/module/ui/components/passkeyHubPopup.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +7 -1
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/useOxyAccountGraph.js +2 -0
- package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
- package/lib/module/ui/hooks/useFollow.js +51 -9
- package/lib/module/ui/hooks/useFollow.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +2 -0
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/index.js +1 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/stores/followStore.js +274 -91
- package/lib/module/ui/stores/followStore.js.map +1 -1
- package/lib/module/ui/stores/resetSessionScopedStores.js +11 -0
- package/lib/module/ui/stores/resetSessionScopedStores.js.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts +0 -19
- package/lib/typescript/commonjs/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/passkeyHubPopup.d.ts +23 -0
- package/lib/typescript/commonjs/ui/components/passkeyHubPopup.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useFollow.d.ts +13 -2
- package/lib/typescript/commonjs/ui/hooks/useFollow.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/stores/followStore.d.ts +4 -1
- package/lib/typescript/commonjs/ui/stores/followStore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts +3 -0
- package/lib/typescript/commonjs/ui/stores/resetSessionScopedStores.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts +0 -19
- package/lib/typescript/module/ui/components/oxyAuthChooserStyles.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/passkeyHubPopup.d.ts +23 -0
- package/lib/typescript/module/ui/components/passkeyHubPopup.d.ts.map +1 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useFollow.d.ts +13 -2
- package/lib/typescript/module/ui/hooks/useFollow.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts +1 -1
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/stores/followStore.d.ts +4 -1
- package/lib/typescript/module/ui/stores/followStore.d.ts.map +1 -1
- package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts +3 -0
- package/lib/typescript/module/ui/stores/resetSessionScopedStores.d.ts.map +1 -0
- package/package.json +12 -12
- package/src/index.ts +1 -1
- package/src/ui/components/FollowButton.tsx +22 -20
- package/src/ui/components/OxyAuthChooser.tsx +125 -89
- package/src/ui/components/oxyAuthChooserStyles.ts +0 -19
- package/src/ui/components/passkeyHubPopup.ts +28 -0
- package/src/ui/context/OxyContext.tsx +6 -0
- package/src/ui/context/useOxyAccountGraph.ts +2 -0
- package/src/ui/hooks/useFollow.ts +51 -9
- package/src/ui/hooks/useSessionManagement.ts +2 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/stores/__tests__/followStore.test.ts +178 -0
- package/src/ui/stores/followStore.ts +277 -106
- package/src/ui/stores/resetSessionScopedStores.ts +8 -0
|
@@ -28,7 +28,7 @@ const isMultiMode = props => 'userIds' in props && Array.isArray(props.userIds);
|
|
|
28
28
|
const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
29
29
|
userId,
|
|
30
30
|
oxyServices,
|
|
31
|
-
initiallyFollowing
|
|
31
|
+
initiallyFollowing,
|
|
32
32
|
size = 'medium',
|
|
33
33
|
onFollowChange,
|
|
34
34
|
style,
|
|
@@ -42,17 +42,18 @@ const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
|
42
42
|
} = useTheme();
|
|
43
43
|
const {
|
|
44
44
|
isFollowing,
|
|
45
|
+
isKnown,
|
|
45
46
|
isLoading,
|
|
46
47
|
toggleFollow,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} = useFollowForButton(userId, oxyServices);
|
|
48
|
+
resolveStatus
|
|
49
|
+
} = useFollowForButton(userId, oxyServices, initiallyFollowing);
|
|
50
50
|
const handlePress = useCallback(async event => {
|
|
51
51
|
if (preventParentActions && event?.preventDefault) {
|
|
52
52
|
event.preventDefault();
|
|
53
53
|
event.stopPropagation?.();
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
// Ignore presses while a mutation is in flight or the status is still unknown.
|
|
56
|
+
if (disabled || isLoading || !isKnown) return;
|
|
56
57
|
try {
|
|
57
58
|
await toggleFollow();
|
|
58
59
|
onFollowChange?.(!isFollowing);
|
|
@@ -60,32 +61,36 @@ const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
|
60
61
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
61
62
|
toast.error(error.message || 'Failed to update follow status');
|
|
62
63
|
}
|
|
63
|
-
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}, [userId, initiallyFollowing]);
|
|
64
|
+
}, [disabled, isLoading, isKnown, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
65
|
+
|
|
66
|
+
// Enqueue a batched status fetch ONLY when the status is genuinely unknown
|
|
67
|
+
// (not seeded from `initiallyFollowing`, not already resolved). All buttons
|
|
68
|
+
// that enqueue in the same commit coalesce into a single bulk request; known/
|
|
69
|
+
// seeded ids never fetch. Once resolved, `isKnown` flips true and this no-ops.
|
|
70
70
|
useEffect(() => {
|
|
71
|
-
if (
|
|
72
|
-
}, [
|
|
73
|
-
|
|
71
|
+
if (!isKnown) resolveStatus();
|
|
72
|
+
}, [isKnown, resolveStatus]);
|
|
73
|
+
|
|
74
|
+
// While the status is genuinely unknown (being resolved), present a NEUTRAL,
|
|
75
|
+
// non-interactive state rather than a definitive "Follow".
|
|
76
|
+
const isBusy = isLoading || !isKnown;
|
|
77
|
+
const showFollowing = isKnown && isFollowing;
|
|
78
|
+
const showSpinner = showLoadingState && isBusy;
|
|
74
79
|
return /*#__PURE__*/_jsx(Button, {
|
|
75
|
-
variant:
|
|
80
|
+
variant: showFollowing ? 'secondary' : 'primary',
|
|
76
81
|
size: size,
|
|
77
82
|
onPress: () => {
|
|
78
83
|
void handlePress();
|
|
79
84
|
},
|
|
80
|
-
disabled: disabled ||
|
|
85
|
+
disabled: disabled || isBusy,
|
|
81
86
|
style: style,
|
|
82
87
|
textStyle: textStyle,
|
|
83
88
|
icon: showSpinner ? /*#__PURE__*/_jsx(Loading, {
|
|
84
89
|
variant: "inline",
|
|
85
90
|
size: "small",
|
|
86
|
-
color:
|
|
91
|
+
color: showFollowing ? colors.text : colors.primaryForeground
|
|
87
92
|
}) : undefined,
|
|
88
|
-
children: showSpinner ? undefined : isFollowing ? 'Following' : 'Follow'
|
|
93
|
+
children: showSpinner ? undefined : isKnown ? isFollowing ? 'Following' : 'Follow' : undefined
|
|
89
94
|
});
|
|
90
95
|
});
|
|
91
96
|
const FollowButtonMultiInner = /*#__PURE__*/memo(function FollowButtonMultiInner({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useEffect","useCallback","useMemo","useState","memo","useOxy","toast","Button","Loading","useFollow","useFollowForButton","useFollowStore","useTheme","useShallow","jsx","_jsx","DEFAULT_FOLLOW_ALL_LABEL","DEFAULT_FOLLOWED_ALL_LABEL","isMultiMode","props","Array","isArray","userIds","FollowButtonInner","userId","oxyServices","initiallyFollowing","size","onFollowChange","style","textStyle","disabled","showLoadingState","preventParentActions","colors","isFollowing","
|
|
1
|
+
{"version":3,"names":["useEffect","useCallback","useMemo","useState","memo","useOxy","toast","Button","Loading","useFollow","useFollowForButton","useFollowStore","useTheme","useShallow","jsx","_jsx","DEFAULT_FOLLOW_ALL_LABEL","DEFAULT_FOLLOWED_ALL_LABEL","isMultiMode","props","Array","isArray","userIds","FollowButtonInner","userId","oxyServices","initiallyFollowing","size","onFollowChange","style","textStyle","disabled","showLoadingState","preventParentActions","colors","isFollowing","isKnown","isLoading","toggleFollow","resolveStatus","handlePress","event","preventDefault","stopPropagation","err","error","Error","String","message","isBusy","showFollowing","showSpinner","variant","onPress","icon","color","text","primaryForeground","undefined","children","FollowButtonMultiInner","initiallyAllFollowing","followAllLabel","followedAllLabel","onBulkFollow","onBulkUnfollow","follow","followAllUsers","unfollowAllUsers","isAnyLoading","initialFollowStatuses","state","statuses","uid","Object","prototype","hasOwnProperty","call","followingUsers","hasKnownNotFollowing","some","storeAllFollowing","allFollowing","fetchAllStatuses","isSubmitting","setIsSubmitting","userIdsKey","join","result","allAlreadyFollowing","followedCount","results","length","every","entry","alreadyFollowing","anyFollowed","success","FollowButton","canUsePrivateApi","user","currentUser","currentUserId","id","trim","rawUserIds","multiUserIds","seen","Set","cleaned","raw","has","add","push","targetUserId"],"sourceRoot":"../../../../src","sources":["ui/components/FollowButton.tsx"],"mappings":";;AACA,SAASA,SAAS,EAAEC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,IAAI,QAAQ,OAAO;AAMvE,SAASC,MAAM,QAAQ,0BAAuB;AAC9C,SAASC,KAAK,QAAQ,cAAc;AACpC,SAASC,MAAM,QAAQ,qBAAqB;AAC5C,SAASC,OAAO,QAAQ,sBAAsB;AAC9C,SAASC,SAAS,EAAEC,kBAAkB,QAAQ,uBAAoB;AAClE,SAASC,cAAc,QAAQ,0BAAuB;AACtD,SAASC,QAAQ,QAAQ,oBAAoB;AAE7C,SAASC,UAAU,QAAQ,uBAAuB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAEnD,MAAMC,wBAAwB,GAAG,YAAY;AAC7C,MAAMC,0BAA0B,GAAG,WAAW;;AAE9C;;AAYA;;AAOA;;AAWA;AACA;AACA;AACA;;AAGA,MAAMC,WAAW,GAAIC,KAAwB,IAC3C,SAAS,IAAIA,KAAK,IAAIC,KAAK,CAACC,OAAO,CAACF,KAAK,CAACG,OAAO,CAAC;AAEpD,MAAMC,iBAAiB,gBAAGnB,IAAI,CAAC,SAASmB,iBAAiBA,CAAC;EACxDC,MAAM;EACNC,WAAW;EACXC,kBAAkB;EAClBC,IAAI,GAAG,QAAQ;EACfC,cAAc;EACdC,KAAK;EACLC,SAAS;EACTC,QAAQ,GAAG,KAAK;EAChBC,gBAAgB,GAAG,IAAI;EACvBC,oBAAoB,GAAG;AAC+B,CAAC,EAAE;EACzD,MAAM;IAAEC;EAAO,CAAC,GAAGtB,QAAQ,CAAC,CAAC;EAE7B,MAAM;IACJuB,WAAW;IACXC,OAAO;IACPC,SAAS;IACTC,YAAY;IACZC;EACF,CAAC,GAAG7B,kBAAkB,CAACc,MAAM,EAAEC,WAAW,EAAEC,kBAAkB,CAAC;EAE/D,MAAMc,WAAW,GAAGvC,WAAW,CAAC,MAAOwC,KAAqE,IAAK;IAC/G,IAAIR,oBAAoB,IAAIQ,KAAK,EAAEC,cAAc,EAAE;MACjDD,KAAK,CAACC,cAAc,CAAC,CAAC;MACtBD,KAAK,CAACE,eAAe,GAAG,CAAC;IAC3B;IACA;IACA,IAAIZ,QAAQ,IAAIM,SAAS,IAAI,CAACD,OAAO,EAAE;IAEvC,IAAI;MACF,MAAME,YAAY,CAAC,CAAC;MACpBV,cAAc,GAAG,CAACO,WAAW,CAAC;IAChC,CAAC,CAAC,OAAOS,GAAY,EAAE;MACrB,MAAMC,KAAK,GAAGD,GAAG,YAAYE,KAAK,GAAGF,GAAG,GAAG,IAAIE,KAAK,CAACC,MAAM,CAACH,GAAG,CAAC,CAAC;MACjEtC,KAAK,CAACuC,KAAK,CAACA,KAAK,CAACG,OAAO,IAAI,gCAAgC,CAAC;IAChE;EACF,CAAC,EAAE,CAACjB,QAAQ,EAAEM,SAAS,EAAED,OAAO,EAAEE,YAAY,EAAEV,cAAc,EAAEO,WAAW,EAAEF,oBAAoB,CAAC,CAAC;;EAEnG;EACA;EACA;EACA;EACAjC,SAAS,CAAC,MAAM;IACd,IAAI,CAACoC,OAAO,EAAEG,aAAa,CAAC,CAAC;EAC/B,CAAC,EAAE,CAACH,OAAO,EAAEG,aAAa,CAAC,CAAC;;EAE5B;EACA;EACA,MAAMU,MAAM,GAAGZ,SAAS,IAAI,CAACD,OAAO;EACpC,MAAMc,aAAa,GAAGd,OAAO,IAAID,WAAW;EAC5C,MAAMgB,WAAW,GAAGnB,gBAAgB,IAAIiB,MAAM;EAE9C,oBACElC,IAAA,CAACR,MAAM;IACL6C,OAAO,EAAEF,aAAa,GAAG,WAAW,GAAG,SAAU;IACjDvB,IAAI,EAAEA,IAAK;IACX0B,OAAO,EAAEA,CAAA,KAAM;MAAE,KAAKb,WAAW,CAAC,CAAC;IAAE,CAAE;IACvCT,QAAQ,EAAEA,QAAQ,IAAIkB,MAAO;IAC7BpB,KAAK,EAAEA,KAAM;IACbC,SAAS,EAAEA,SAAU;IACrBwB,IAAI,EAAEH,WAAW,gBACfpC,IAAA,CAACP,OAAO;MACN4C,OAAO,EAAC,QAAQ;MAChBzB,IAAI,EAAC,OAAO;MACZ4B,KAAK,EAAEL,aAAa,GAAGhB,MAAM,CAACsB,IAAI,GAAGtB,MAAM,CAACuB;IAAkB,CAC/D,CAAC,GACAC,SAAU;IAAAC,QAAA,EAEbR,WAAW,GAAGO,SAAS,GAAItB,OAAO,GAAID,WAAW,GAAG,WAAW,GAAG,QAAQ,GAAIuB;EAAU,CACnF,CAAC;AAEb,CAAC,CAAC;AAEF,MAAME,sBAAsB,gBAAGxD,IAAI,CAAC,SAASwD,sBAAsBA,CAAC;EAClEtC,OAAO;EACPuC,qBAAqB,GAAG,KAAK;EAC7BlC,IAAI,GAAG,QAAQ;EACfmC,cAAc,GAAG9C,wBAAwB;EACzC+C,gBAAgB,GAAG9C,0BAA0B;EAC7CW,cAAc;EACdoC,YAAY;EACZC,cAAc;EACdpC,KAAK;EACLC,SAAS;EACTC,QAAQ,GAAG,KAAK;EAChBC,gBAAgB,GAAG,IAAI;EACvBC,oBAAoB,GAAG;AACD,CAAC,EAAE;EACzB,MAAM;IAAEC;EAAO,CAAC,GAAGtB,QAAQ,CAAC,CAAC;EAC7B,MAAMsD,MAAM,GAAGzD,SAAS,CAACa,OAAO,CAAC;EACjC,MAAM6C,cAAc,GAAG,gBAAgB,IAAID,MAAM,GAAGA,MAAM,CAACC,cAAc,GAAGT,SAAS;EACrF,MAAMU,gBAAgB,GAAG,kBAAkB,IAAIF,MAAM,GAAGA,MAAM,CAACE,gBAAgB,GAAGV,SAAS;EAC3F,MAAMW,YAAY,GAAG,cAAc,IAAIH,MAAM,GAAGA,MAAM,CAACG,YAAY,GAAG,KAAK;EAE3E,MAAMC,qBAAqB,GAAG3D,cAAc,CAC1CE,UAAU,CAAE0D,KAAK,IAAK;IACpB,MAAMC,QAAwC,GAAG,CAAC,CAAC;IACnD,KAAK,MAAMC,GAAG,IAAInD,OAAO,EAAE;MACzBkD,QAAQ,CAACC,GAAG,CAAC,GAAGC,MAAM,CAACC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACN,KAAK,CAACO,cAAc,EAAEL,GAAG,CAAC,GAC3EF,KAAK,CAACO,cAAc,CAACL,GAAG,CAAC,GACzB,IAAI;IACV;IACA,OAAOD,QAAQ;EACjB,CAAC,CACH,CAAC;EAED,MAAMO,oBAAoB,GAAGzD,OAAO,CAAC0D,IAAI,CAAEP,GAAG,IAAKH,qBAAqB,CAACG,GAAG,CAAC,KAAK,KAAK,CAAC;;EAExF;EACA;EACA;EACA;EACA;EACA,MAAMQ,iBAAiB,GAAG,cAAc,IAAIf,MAAM,GAAGA,MAAM,CAACgB,YAAY,GAAG,KAAK;EAChF,MAAMA,YAAY,GAAGD,iBAAiB,IAAKpB,qBAAqB,IAAI,CAACkB,oBAAqB;EAC1F,MAAMI,gBAAgB,GAAG,kBAAkB,IAAIjB,MAAM,GAAGA,MAAM,CAACiB,gBAAgB,GAAGzB,SAAS;EAC3F,MAAM,CAAC0B,YAAY,EAAEC,eAAe,CAAC,GAAGlF,QAAQ,CAAC,KAAK,CAAC;EAEvD,MAAMkC,SAAS,GAAG+C,YAAY,IAAIf,YAAY;;EAE9C;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMiB,UAAU,GAAGpF,OAAO,CAAC,MAAMoB,OAAO,CAACiE,IAAI,CAAC,GAAG,CAAC,EAAE,CAACjE,OAAO,CAAC,CAAC;EAC9DtB,SAAS,CAAC,MAAM;IACdmF,gBAAgB,GAAG,CAAC;EACtB,CAAC,EAAE,CAACG,UAAU,EAAEH,gBAAgB,CAAC,CAAC;EAElC,MAAM3C,WAAW,GAAGvC,WAAW,CAAC,MAAOwC,KAAqE,IAAK;IAC/G,IAAIR,oBAAoB,IAAIQ,KAAK,EAAEC,cAAc,EAAE;MACjDD,KAAK,CAACC,cAAc,CAAC,CAAC;MACtBD,KAAK,CAACE,eAAe,GAAG,CAAC;IAC3B;IACA,IAAIZ,QAAQ,IAAIM,SAAS,EAAE;IAE3B,IAAI6C,YAAY,EAAE;MAChB,IAAI,CAACd,gBAAgB,EAAE;MACvBiB,eAAe,CAAC,IAAI,CAAC;MACrB,IAAI;QACF,MAAMG,MAAM,GAAG,MAAMpB,gBAAgB,CAAC,CAAC;QACvC;QACA;QACAxC,cAAc,GAAG,KAAK,CAAC;QACvBqC,cAAc,GAAGuB,MAAM,CAAC;MAC1B,CAAC,CAAC,OAAO5C,GAAY,EAAE;QACrB,MAAMC,KAAK,GAAGD,GAAG,YAAYE,KAAK,GAAGF,GAAG,GAAG,IAAIE,KAAK,CAACC,MAAM,CAACH,GAAG,CAAC,CAAC;QACjEtC,KAAK,CAACuC,KAAK,CAACA,KAAK,CAACG,OAAO,IAAI,gCAAgC,CAAC;MAChE,CAAC,SAAS;QACRqC,eAAe,CAAC,KAAK,CAAC;MACxB;MACA;IACF;IAEA,IAAI,CAAClB,cAAc,EAAE;IACrBkB,eAAe,CAAC,IAAI,CAAC;IACrB,IAAI;MACF,MAAMG,MAAM,GAAG,MAAMrB,cAAc,CAAC,CAAC;MACrC,MAAMsB,mBAAmB,GAAGD,MAAM,CAACE,aAAa,KAAK,CAAC,IACjDF,MAAM,CAACG,OAAO,CAACC,MAAM,GAAG,CAAC,IACzBJ,MAAM,CAACG,OAAO,CAACE,KAAK,CAAEC,KAAK,IAAKA,KAAK,CAACC,gBAAgB,CAAC;MAC5D,MAAMC,WAAW,GAAGR,MAAM,CAACE,aAAa,GAAG,CAAC,IACvCF,MAAM,CAACG,OAAO,CAACX,IAAI,CAAEc,KAAK,IAAKA,KAAK,CAACG,OAAO,IAAIH,KAAK,CAACC,gBAAgB,CAAC;;MAE5E;MACA;MACA,IAAIN,mBAAmB,IAAIO,WAAW,EAAE;QACtCpE,cAAc,GAAG,IAAI,CAAC;MACxB;MACAoC,YAAY,GAAGwB,MAAM,CAAC;IACxB,CAAC,CAAC,OAAO5C,GAAY,EAAE;MACrB,MAAMC,KAAK,GAAGD,GAAG,YAAYE,KAAK,GAAGF,GAAG,GAAG,IAAIE,KAAK,CAACC,MAAM,CAACH,GAAG,CAAC,CAAC;MACjEtC,KAAK,CAACuC,KAAK,CAACA,KAAK,CAACG,OAAO,IAAI,gCAAgC,CAAC;IAChE,CAAC,SAAS;MACRqC,eAAe,CAAC,KAAK,CAAC;IACxB;EACF,CAAC,EAAE,CAACtD,QAAQ,EAAEM,SAAS,EAAE6C,YAAY,EAAEf,cAAc,EAAEC,gBAAgB,EAAExC,cAAc,EAAEoC,YAAY,EAAEC,cAAc,EAAEhC,oBAAoB,CAAC,CAAC;EAE7I,MAAMkB,WAAW,GAAGnB,gBAAgB,IAAIK,SAAS;EAEjD,oBACEtB,IAAA,CAACR,MAAM;IACL6C,OAAO,EAAE8B,YAAY,GAAG,WAAW,GAAG,SAAU;IAChDvD,IAAI,EAAEA,IAAK;IACX0B,OAAO,EAAEA,CAAA,KAAM;MAAE,KAAKb,WAAW,CAAC,CAAC;IAAE,CAAE;IACvCT,QAAQ,EAAEA,QAAQ,IAAIM,SAAU;IAChCR,KAAK,EAAEA,KAAM;IACbC,SAAS,EAAEA,SAAU;IACrBwB,IAAI,EAAEH,WAAW,gBACfpC,IAAA,CAACP,OAAO;MACN4C,OAAO,EAAC,QAAQ;MAChBzB,IAAI,EAAC,OAAO;MACZ4B,KAAK,EAAE2B,YAAY,GAAGhD,MAAM,CAACsB,IAAI,GAAGtB,MAAM,CAACuB;IAAkB,CAC9D,CAAC,GACAC,SAAU;IAAAC,QAAA,EAEbR,WAAW,GAAGO,SAAS,GAAIwB,YAAY,GAAGnB,gBAAgB,GAAGD;EAAe,CACvE,CAAC;AAEb,CAAC,CAAC;AAEF,MAAMoC,YAAyC,GAAI/E,KAAK,IAAK;EAC3D,MAAM;IAAEM,WAAW;IAAE0E,gBAAgB;IAAEC,IAAI,EAAEC;EAAY,CAAC,GAAGhG,MAAM,CAAC,CAAC;EAErE,MAAMiG,aAAa,GAAGD,WAAW,EAAEE,EAAE,GAAGxD,MAAM,CAACsD,WAAW,CAACE,EAAE,CAAC,CAACC,IAAI,CAAC,CAAC,GAAG,EAAE;EAE1E,MAAMC,UAAU,GAAGvF,WAAW,CAACC,KAAK,CAAC,GAAGA,KAAK,CAACG,OAAO,GAAG,IAAI;;EAE5D;EACA,MAAMoF,YAAY,GAAGxG,OAAO,CAAC,MAAM;IACjC,IAAI,CAACuG,UAAU,EAAE,OAAO,EAAE;IAC1B,MAAME,IAAI,GAAG,IAAIC,GAAG,CAAS,CAAC;IAC9B,MAAMC,OAAiB,GAAG,EAAE;IAC5B,KAAK,MAAMC,GAAG,IAAIL,UAAU,EAAE;MAC5B,MAAMF,EAAE,GAAGO,GAAG,GAAG/D,MAAM,CAAC+D,GAAG,CAAC,CAACN,IAAI,CAAC,CAAC,GAAG,EAAE;MACxC,IAAI,CAACD,EAAE,IAAIA,EAAE,KAAKD,aAAa,IAAIK,IAAI,CAACI,GAAG,CAACR,EAAE,CAAC,EAAE;MACjDI,IAAI,CAACK,GAAG,CAACT,EAAE,CAAC;MACZM,OAAO,CAACI,IAAI,CAACV,EAAE,CAAC;IAClB;IACA,OAAOM,OAAO;EAChB,CAAC,EAAE,CAACJ,UAAU,EAAEH,aAAa,CAAC,CAAC;EAE/B,IAAI,CAACH,gBAAgB,EAAE;IACrB,OAAO,IAAI;EACb;EAEA,IAAIjF,WAAW,CAACC,KAAK,CAAC,EAAE;IACtB,IAAIuF,YAAY,CAACd,MAAM,KAAK,CAAC,EAAE;MAC7B,OAAO,IAAI;IACb;IACA,oBACE7E,IAAA,CAAC6C,sBAAsB;MAAA,GACjBzC,KAAK;MACTG,OAAO,EAAEoF;IAAa,CACvB,CAAC;EAEN;EAEA,MAAMQ,YAAY,GAAG/F,KAAK,CAACK,MAAM,GAAGuB,MAAM,CAAC5B,KAAK,CAACK,MAAM,CAAC,CAACgF,IAAI,CAAC,CAAC,GAAG,EAAE;EACpE,IAAI,CAACU,YAAY,IAAKZ,aAAa,IAAIA,aAAa,KAAKY,YAAa,EAAE;IACtE,OAAO,IAAI;EACb;EAEA,oBACEnG,IAAA,CAACQ,iBAAiB;IAAA,GACZJ,KAAK;IACTK,MAAM,EAAE0F,YAAa;IACrBzF,WAAW,EAAEA;EAAY,CAC1B,CAAC;AAEN,CAAC;AAED,SAASyE,YAAY;AACrB,eAAeA,YAAY","ignoreList":[]}
|
|
@@ -54,6 +54,7 @@ import { MaterialCommunityIcons } from '@expo/vector-icons';
|
|
|
54
54
|
import QRCode from 'react-native-qrcode-svg';
|
|
55
55
|
import { Avatar } from '@oxyhq/bloom/avatar';
|
|
56
56
|
import { Button } from '@oxyhq/bloom/button';
|
|
57
|
+
import { toast } from '@oxyhq/bloom';
|
|
57
58
|
import { Text } from '@oxyhq/bloom/typography';
|
|
58
59
|
import { useTheme, BloomColorScope, APP_COLOR_NAMES, APP_COLOR_PRESETS } from '@oxyhq/bloom/theme';
|
|
59
60
|
import { isOxyRpOrigin } from '@oxyhq/core';
|
|
@@ -65,7 +66,7 @@ import { getCommonsAcquisitionUrl } from "../utils/commonsStoreLinks.js";
|
|
|
65
66
|
import { authChooserStyles as styles } from "./oxyAuthChooserStyles.js";
|
|
66
67
|
|
|
67
68
|
/** Diameter of a row avatar. */
|
|
68
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
69
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
69
70
|
const ROW_AVATAR_SIZE = 40;
|
|
70
71
|
/** High-contrast QR colors — intentionally fixed (NOT themed) for scan reliability. */
|
|
71
72
|
const QR_PLATE_BG = '#FFFFFF';
|
|
@@ -111,46 +112,51 @@ const OxyAuthChooser = ({
|
|
|
111
112
|
} = useI18n();
|
|
112
113
|
const queryClient = useQueryClient();
|
|
113
114
|
|
|
114
|
-
// A credential minted for `oxy.so` can only be
|
|
115
|
-
// dev host) —
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
|
|
115
|
+
// A credential minted for `oxy.so` can only be ASSERTED there (or a loopback
|
|
116
|
+
// dev host) — a hard WebAuthn RP-ID boundary the browser enforces, not
|
|
117
|
+
// feature detection. On a first-party Oxy web origin the ceremony runs
|
|
118
|
+
// directly ('direct'). On any OTHER web origin (b2) it can't run locally, so
|
|
119
|
+
// the passkey action instead opens a popup at the auth.oxy.so passkey hub —
|
|
120
|
+
// where the SAME ceremony IS first-party — and relays the resulting session
|
|
121
|
+
// back via `AccountDialogController.startPasskeyHubSignIn` ('hub'). Native
|
|
122
|
+
// has neither: Commons owns identity there ('none'). `isOxyRpOrigin()` reads
|
|
123
|
+
// `location` once and is stable for the component's lifetime.
|
|
124
|
+
const passkeyMode = useMemo(() => {
|
|
125
|
+
if (!isWebBrowser()) return 'none';
|
|
126
|
+
return isOxyRpOrigin() ? 'direct' : 'hub';
|
|
127
|
+
}, []);
|
|
128
|
+
const passkeyAvailable = passkeyMode !== 'none';
|
|
120
129
|
const [signInPasskeyPending, setSignInPasskeyPending] = useState(false);
|
|
121
|
-
const [signInPasskeyError, setSignInPasskeyError] = useState(null);
|
|
122
130
|
const handleSignInWithPasskey = useCallback(async () => {
|
|
123
131
|
if (signInPasskeyPending) return;
|
|
124
132
|
setSignInPasskeyPending(true);
|
|
125
|
-
setSignInPasskeyError(null);
|
|
126
133
|
try {
|
|
127
134
|
await signInWithPasskey();
|
|
128
135
|
onComplete?.();
|
|
129
136
|
} catch (error) {
|
|
130
137
|
// A cancelled/failed ceremony keeps the view open so the user can retry
|
|
131
|
-
// or fall back to the QR — surface a
|
|
132
|
-
|
|
138
|
+
// or fall back to the QR — surface the reason as a toast (owner mandate:
|
|
139
|
+
// errors never render inline inside the dialog), never swallow.
|
|
140
|
+
toast.error(error instanceof Error && error.message ? error.message : t('accountSwitcher.toasts.passkeySignInFailed') || 'Passkey sign-in failed.');
|
|
133
141
|
} finally {
|
|
134
142
|
setSignInPasskeyPending(false);
|
|
135
143
|
}
|
|
136
|
-
}, [signInPasskeyPending, signInWithPasskey, onComplete]);
|
|
144
|
+
}, [signInPasskeyPending, signInWithPasskey, onComplete, t]);
|
|
137
145
|
const [createPasskeyPending, setCreatePasskeyPending] = useState(false);
|
|
138
|
-
const [createPasskeyError, setCreatePasskeyError] = useState(null);
|
|
139
146
|
const handleCreateWithPasskey = useCallback(async username => {
|
|
140
147
|
if (createPasskeyPending) return;
|
|
141
148
|
setCreatePasskeyPending(true);
|
|
142
|
-
setCreatePasskeyError(null);
|
|
143
149
|
try {
|
|
144
150
|
await registerWithPasskey({
|
|
145
151
|
username
|
|
146
152
|
});
|
|
147
153
|
onComplete?.();
|
|
148
154
|
} catch (error) {
|
|
149
|
-
|
|
155
|
+
toast.error(error instanceof Error && error.message ? error.message : t('signup.toasts.passkeyCreateFailed') || 'Account creation failed.');
|
|
150
156
|
} finally {
|
|
151
157
|
setCreatePasskeyPending(false);
|
|
152
158
|
}
|
|
153
|
-
}, [createPasskeyPending, registerWithPasskey, onComplete]);
|
|
159
|
+
}, [createPasskeyPending, registerWithPasskey, onComplete, t]);
|
|
154
160
|
|
|
155
161
|
// Bind the headless controller. `getSnapshot` returns a stable reference
|
|
156
162
|
// between changes, so it is `useSyncExternalStore`-safe. Guard the no-provider
|
|
@@ -162,6 +168,28 @@ const OxyAuthChooser = ({
|
|
|
162
168
|
view
|
|
163
169
|
} = snapshot;
|
|
164
170
|
|
|
171
|
+
// Sign-in device-flow failures land on `snapshot.signIn` asynchronously (poll /
|
|
172
|
+
// socket / popup cancel). Toast at the controller notification site — never an
|
|
173
|
+
// inline banner in the QR view (owner mandate, same contract as account-switch).
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (!controller) return;
|
|
176
|
+
let lastToastedSignInError = null;
|
|
177
|
+
const maybeToastSignInError = () => {
|
|
178
|
+
const {
|
|
179
|
+
signIn
|
|
180
|
+
} = controller.getSnapshot();
|
|
181
|
+
if (signIn.phase === 'error' && signIn.error && signIn.error !== lastToastedSignInError) {
|
|
182
|
+
lastToastedSignInError = signIn.error;
|
|
183
|
+
toast.error(signIn.error || t('accountSwitcher.toasts.signInFailed') || 'Sign-in failed. Please try again.');
|
|
184
|
+
}
|
|
185
|
+
if (signIn.phase !== 'error') {
|
|
186
|
+
lastToastedSignInError = null;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
maybeToastSignInError();
|
|
190
|
+
return controller.subscribe(maybeToastSignInError);
|
|
191
|
+
}, [controller, t]);
|
|
192
|
+
|
|
165
193
|
// Web: "Sign in with Oxy" auto-starts the instant the sign-in entry is
|
|
166
194
|
// reached — no button tap needed. `signInWithOxy()` tries the shared
|
|
167
195
|
// keychain first (silently, native-only) then falls to the QR handoff,
|
|
@@ -185,19 +213,24 @@ const OxyAuthChooser = ({
|
|
|
185
213
|
setSwitching(true);
|
|
186
214
|
try {
|
|
187
215
|
await controller.switchTo(accountId);
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
216
|
+
// `switchTo` never throws — it records a failure on the controller's
|
|
217
|
+
// snapshot. Read it back at the point the switch settles and surface it
|
|
218
|
+
// as a toast (event-driven, at the failure site — NOT an inline banner
|
|
219
|
+
// and NOT a snapshot-reaction). On success reload the app's account
|
|
220
|
+
// graph and drop cached account-scoped data so the new active identity
|
|
221
|
+
// re-fetches — the same side effects the context's own
|
|
222
|
+
// `switchToAccount` performs.
|
|
223
|
+
if (controller.getSnapshot().error) {
|
|
224
|
+
toast.error(t('accountSwitcher.toasts.switchFailed') || 'There was a problem switching accounts. Please try again.');
|
|
225
|
+
return;
|
|
196
226
|
}
|
|
227
|
+
void refreshAccounts();
|
|
228
|
+
queryClient.invalidateQueries();
|
|
229
|
+
onComplete?.();
|
|
197
230
|
} finally {
|
|
198
231
|
setSwitching(false);
|
|
199
232
|
}
|
|
200
|
-
}, [controller, switching, snapshot.activeAccountId, onComplete, refreshAccounts, queryClient]);
|
|
233
|
+
}, [controller, switching, snapshot.activeAccountId, onComplete, refreshAccounts, queryClient, t]);
|
|
201
234
|
const handleManage = useCallback(() => {
|
|
202
235
|
onComplete?.();
|
|
203
236
|
showBottomSheet?.('ManageAccount');
|
|
@@ -227,9 +260,13 @@ const OxyAuthChooser = ({
|
|
|
227
260
|
t: t,
|
|
228
261
|
onRetry: () => void controller.showQr(),
|
|
229
262
|
passkeyAvailable: passkeyAvailable,
|
|
230
|
-
onSignInWithPasskey: () => void handleSignInWithPasskey()
|
|
231
|
-
|
|
232
|
-
|
|
263
|
+
onSignInWithPasskey: passkeyMode === 'direct' ? () => void handleSignInWithPasskey() : () => void controller.startPasskeyHubSignIn()
|
|
264
|
+
// The hub-popup flow's pending state is `snapshot.signIn` (already
|
|
265
|
+
// rendered elsewhere in this view) — only the DIRECT ceremony uses this
|
|
266
|
+
// component-local pending flag. A ceremony FAILURE is surfaced as a
|
|
267
|
+
// toast by `handleSignInWithPasskey`, never inline.
|
|
268
|
+
,
|
|
269
|
+
passkeyPending: passkeyMode === 'direct' ? signInPasskeyPending : false,
|
|
233
270
|
onCreateAccount: () => controller.startSignup()
|
|
234
271
|
});
|
|
235
272
|
}
|
|
@@ -239,10 +276,10 @@ const OxyAuthChooser = ({
|
|
|
239
276
|
theme: theme,
|
|
240
277
|
t: t,
|
|
241
278
|
oxyServices: oxyServices,
|
|
242
|
-
|
|
279
|
+
passkeyMode: passkeyMode,
|
|
243
280
|
onCreateWithPasskey: handleCreateWithPasskey,
|
|
244
281
|
createPending: createPasskeyPending,
|
|
245
|
-
|
|
282
|
+
onOpenHub: () => void controller.startPasskeyHubSignIn(),
|
|
246
283
|
onBackToSignIn: () => controller.setView('signin')
|
|
247
284
|
});
|
|
248
285
|
}
|
|
@@ -261,33 +298,6 @@ const OxyAuthChooser = ({
|
|
|
261
298
|
// Accounts view
|
|
262
299
|
// ---------------------------------------------------------------------------
|
|
263
300
|
|
|
264
|
-
/**
|
|
265
|
-
* Inline error banner for `snapshot.error` (the account-list / switch-failure
|
|
266
|
-
* error — distinct from `snapshot.signIn.error`, which `QrView` already
|
|
267
|
-
* renders). `AccountsView` and `SignInView` previously swallowed it: a
|
|
268
|
-
* blocked/failed switch left the user with no feedback outside the QR view.
|
|
269
|
-
*/
|
|
270
|
-
const ErrorBanner = ({
|
|
271
|
-
error,
|
|
272
|
-
theme
|
|
273
|
-
}) => {
|
|
274
|
-
if (!error) return null;
|
|
275
|
-
return /*#__PURE__*/_jsxs(View, {
|
|
276
|
-
style: [styles.errorBanner, {
|
|
277
|
-
backgroundColor: theme.colors.negativeSubtle
|
|
278
|
-
}],
|
|
279
|
-
children: [/*#__PURE__*/_jsx(MaterialCommunityIcons, {
|
|
280
|
-
name: "alert-circle-outline",
|
|
281
|
-
size: 18,
|
|
282
|
-
color: theme.colors.error
|
|
283
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
284
|
-
style: [styles.errorBannerText, {
|
|
285
|
-
color: theme.colors.error
|
|
286
|
-
}],
|
|
287
|
-
children: error
|
|
288
|
-
})]
|
|
289
|
-
});
|
|
290
|
-
};
|
|
291
301
|
const AccountsView = ({
|
|
292
302
|
snapshot,
|
|
293
303
|
theme,
|
|
@@ -311,10 +321,7 @@ const AccountsView = ({
|
|
|
311
321
|
}
|
|
312
322
|
return /*#__PURE__*/_jsxs(View, {
|
|
313
323
|
style: styles.rows,
|
|
314
|
-
children: [/*#__PURE__*/_jsx(
|
|
315
|
-
error: snapshot.error,
|
|
316
|
-
theme: theme
|
|
317
|
-
}), snapshot.accounts.map(account => /*#__PURE__*/_jsx(AccountRow, {
|
|
324
|
+
children: [snapshot.accounts.map(account => /*#__PURE__*/_jsx(AccountRow, {
|
|
318
325
|
account: account,
|
|
319
326
|
theme: theme,
|
|
320
327
|
switching: snapshot.switchingAccountId === account.accountId,
|
|
@@ -440,10 +447,7 @@ const SignInView = ({
|
|
|
440
447
|
onCreateAccount
|
|
441
448
|
}) => /*#__PURE__*/_jsxs(View, {
|
|
442
449
|
style: styles.signInBlock,
|
|
443
|
-
children: [/*#__PURE__*/
|
|
444
|
-
error: snapshot.error,
|
|
445
|
-
theme: theme
|
|
446
|
-
}), snapshot.accounts.length > 0 ? /*#__PURE__*/_jsxs(View, {
|
|
450
|
+
children: [snapshot.accounts.length > 0 ? /*#__PURE__*/_jsxs(View, {
|
|
447
451
|
style: styles.rows,
|
|
448
452
|
children: [snapshot.accounts.map(account => /*#__PURE__*/_jsx(AccountRow, {
|
|
449
453
|
account: account,
|
|
@@ -544,7 +548,6 @@ const QrView = ({
|
|
|
544
548
|
passkeyAvailable,
|
|
545
549
|
onSignInWithPasskey,
|
|
546
550
|
passkeyPending,
|
|
547
|
-
passkeyError,
|
|
548
551
|
onCreateAccount
|
|
549
552
|
}) => {
|
|
550
553
|
const {
|
|
@@ -552,25 +555,21 @@ const QrView = ({
|
|
|
552
555
|
commonsAvailability
|
|
553
556
|
} = snapshot;
|
|
554
557
|
const [showQrAnyway, setShowQrAnyway] = useState(false);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
})
|
|
568
|
-
}), passkeyError ? /*#__PURE__*/_jsx(Text, {
|
|
569
|
-
style: [styles.errorText, {
|
|
570
|
-
color: theme.colors.error
|
|
558
|
+
|
|
559
|
+
// A ceremony FAILURE is surfaced as a toast by `handleSignInWithPasskey` — the
|
|
560
|
+
// link itself never renders an inline error (owner mandate).
|
|
561
|
+
const passkeyLink = passkeyAvailable && signIn.phase !== 'authorized' ? /*#__PURE__*/_jsx(Pressable, {
|
|
562
|
+
onPress: onSignInWithPasskey,
|
|
563
|
+
disabled: passkeyPending,
|
|
564
|
+
accessibilityRole: "button",
|
|
565
|
+
style: styles.footerLink,
|
|
566
|
+
testID: "passkey-signin-link",
|
|
567
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
568
|
+
style: [styles.linkText, {
|
|
569
|
+
color: theme.colors.textSecondary
|
|
571
570
|
}],
|
|
572
|
-
children:
|
|
573
|
-
})
|
|
571
|
+
children: passkeyPending ? t('accountSwitcher.passkeySigningIn') || 'Signing in…' : t('accountSwitcher.useIdentityOnDevice') || 'No Commons? Use a passkey on this device instead'
|
|
572
|
+
})
|
|
574
573
|
}) : null;
|
|
575
574
|
|
|
576
575
|
// Native, Commons confirmed NOT installed: a same-device QR is a dead
|
|
@@ -607,14 +606,11 @@ const QrView = ({
|
|
|
607
606
|
});
|
|
608
607
|
}
|
|
609
608
|
if (signIn.phase === 'error') {
|
|
609
|
+
// The failure reason is toasted by the controller subscription above — no
|
|
610
|
+
// inline error copy here (owner mandate).
|
|
610
611
|
return /*#__PURE__*/_jsxs(View, {
|
|
611
612
|
style: styles.centeredBlock,
|
|
612
|
-
children: [/*#__PURE__*/_jsx(
|
|
613
|
-
style: [styles.errorText, {
|
|
614
|
-
color: theme.colors.error
|
|
615
|
-
}],
|
|
616
|
-
children: signIn.error || 'Sign-in failed. Please try again.'
|
|
617
|
-
}), /*#__PURE__*/_jsx(Button, {
|
|
613
|
+
children: [/*#__PURE__*/_jsx(Button, {
|
|
618
614
|
variant: "secondary",
|
|
619
615
|
onPress: onRetry,
|
|
620
616
|
style: styles.secondaryButton,
|
|
@@ -671,7 +667,7 @@ const QrView = ({
|
|
|
671
667
|
// ---------------------------------------------------------------------------
|
|
672
668
|
|
|
673
669
|
/** Debounced username-availability check over the existing SDK method. */
|
|
674
|
-
function useUsernameAvailability(oxyServices) {
|
|
670
|
+
function useUsernameAvailability(oxyServices, t) {
|
|
675
671
|
const [status, setStatus] = useState('idle');
|
|
676
672
|
const timerRef = useRef(null);
|
|
677
673
|
const requestSeqRef = useRef(0);
|
|
@@ -689,10 +685,13 @@ function useUsernameAvailability(oxyServices) {
|
|
|
689
685
|
setStatus(result.available ? 'available' : 'taken');
|
|
690
686
|
}).catch(() => {
|
|
691
687
|
if (seq !== requestSeqRef.current) return;
|
|
692
|
-
|
|
688
|
+
// Reset the (now stale) verdict rather than block on it, and surface
|
|
689
|
+
// the failure as a toast — never an inline error inside the dialog.
|
|
690
|
+
setStatus('idle');
|
|
691
|
+
toast.error(t('accounts.create.username.checkFailed') || 'Could not check availability');
|
|
693
692
|
});
|
|
694
693
|
}, 400);
|
|
695
|
-
}, [oxyServices]);
|
|
694
|
+
}, [oxyServices, t]);
|
|
696
695
|
return {
|
|
697
696
|
status,
|
|
698
697
|
check
|
|
@@ -732,17 +731,17 @@ const SignUpView = ({
|
|
|
732
731
|
theme,
|
|
733
732
|
t,
|
|
734
733
|
oxyServices,
|
|
735
|
-
|
|
734
|
+
passkeyMode,
|
|
736
735
|
onCreateWithPasskey,
|
|
737
736
|
createPending,
|
|
738
|
-
|
|
737
|
+
onOpenHub,
|
|
739
738
|
onBackToSignIn
|
|
740
739
|
}) => {
|
|
741
740
|
const [username, setUsername] = useState('');
|
|
742
741
|
const {
|
|
743
742
|
status: usernameStatus,
|
|
744
743
|
check: checkUsername
|
|
745
|
-
} = useUsernameAvailability(oxyServices);
|
|
744
|
+
} = useUsernameAvailability(oxyServices, t);
|
|
746
745
|
const canSubmit = username.trim().length >= 3 && usernameStatus === 'available' && !createPending;
|
|
747
746
|
|
|
748
747
|
// Native — Commons owns identity creation. Mirrors the QR view's own
|
|
@@ -770,7 +769,7 @@ const SignUpView = ({
|
|
|
770
769
|
// passkey creation is the de-emphasized alternative underneath for someone
|
|
771
770
|
// who doesn't want to install anything — secondary button, introduced by
|
|
772
771
|
// its own "No Commons?" framing, same subordination as the QR view's link.
|
|
773
|
-
if (
|
|
772
|
+
if (passkeyMode === 'direct') {
|
|
774
773
|
return /*#__PURE__*/_jsxs(View, {
|
|
775
774
|
style: styles.signInBlock,
|
|
776
775
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -816,12 +815,7 @@ const SignUpView = ({
|
|
|
816
815
|
status: usernameStatus,
|
|
817
816
|
theme: theme,
|
|
818
817
|
t: t
|
|
819
|
-
}),
|
|
820
|
-
style: [styles.errorText, {
|
|
821
|
-
color: theme.colors.error
|
|
822
|
-
}],
|
|
823
|
-
children: createError
|
|
824
|
-
}) : null, /*#__PURE__*/_jsx(Button, {
|
|
818
|
+
}), /*#__PURE__*/_jsx(Button, {
|
|
825
819
|
variant: "secondary",
|
|
826
820
|
onPress: () => void onCreateWithPasskey(username.trim()),
|
|
827
821
|
disabled: !canSubmit,
|
|
@@ -836,16 +830,23 @@ const SignUpView = ({
|
|
|
836
830
|
});
|
|
837
831
|
}
|
|
838
832
|
|
|
839
|
-
// Web, non-Oxy origin: passkey creation here
|
|
840
|
-
//
|
|
841
|
-
//
|
|
833
|
+
// Web, non-Oxy origin (b2): passkey creation can't run locally here — the
|
|
834
|
+
// same WebAuthn RP-ID boundary as sign-in — so this opens the SAME
|
|
835
|
+
// auth.oxy.so passkey hub popup `QrView`'s "No Commons?" link uses.
|
|
836
|
+
// Completing EITHER a sign-in OR a fresh sign-up there relays the resulting
|
|
837
|
+
// session back via the SAME poll/socket engine `showQr` drives.
|
|
842
838
|
return /*#__PURE__*/_jsxs(View, {
|
|
843
839
|
style: styles.centeredBlock,
|
|
844
840
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
845
841
|
style: [styles.mutedText, {
|
|
846
842
|
color: theme.colors.textSecondary
|
|
847
843
|
}],
|
|
848
|
-
children: t('signup.
|
|
844
|
+
children: t('signup.hubExplainer') || 'Create your Oxy ID in a secure window, then come right back.'
|
|
845
|
+
}), /*#__PURE__*/_jsx(Button, {
|
|
846
|
+
variant: "primary",
|
|
847
|
+
onPress: onOpenHub,
|
|
848
|
+
style: styles.primaryButton,
|
|
849
|
+
children: t('signup.continueInWindow') || 'Continue in a new window'
|
|
849
850
|
}), /*#__PURE__*/_jsx(SignInFooterLink, {
|
|
850
851
|
theme: theme,
|
|
851
852
|
t: t,
|