@buoy-gg/impersonate 1.0.3-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +58 -0
  2. package/lib/commonjs/impersonate/components/DataNukeSettings.js +715 -0
  3. package/lib/commonjs/impersonate/components/ImpersonateBanner.js +217 -0
  4. package/lib/commonjs/impersonate/components/ImpersonateHistoryList.js +173 -0
  5. package/lib/commonjs/impersonate/components/ImpersonateModal.js +304 -0
  6. package/lib/commonjs/impersonate/components/ImpersonateStatusBar.js +130 -0
  7. package/lib/commonjs/impersonate/components/UserAvatar.js +146 -0
  8. package/lib/commonjs/impersonate/components/UserCard.js +200 -0
  9. package/lib/commonjs/impersonate/components/UserSearchView.js +227 -0
  10. package/lib/commonjs/impersonate/components/index.js +85 -0
  11. package/lib/commonjs/impersonate/hooks/index.js +64 -0
  12. package/lib/commonjs/impersonate/hooks/useAutoClearAsyncStorage.js +144 -0
  13. package/lib/commonjs/impersonate/hooks/useAutoClearReactQuery.js +155 -0
  14. package/lib/commonjs/impersonate/hooks/useAutoClearRedux.js +188 -0
  15. package/lib/commonjs/impersonate/hooks/useImpersonate.js +215 -0
  16. package/lib/commonjs/impersonate/hooks/useImpersonateHistory.js +56 -0
  17. package/lib/commonjs/impersonate/index.js +49 -0
  18. package/lib/commonjs/impersonate/types/index.js +16 -0
  19. package/lib/commonjs/impersonate/types/types.js +1 -0
  20. package/lib/commonjs/impersonate/utils/impersonateListener.js +280 -0
  21. package/lib/commonjs/impersonate/utils/impersonateStore.js +607 -0
  22. package/lib/commonjs/impersonate/utils/index.js +49 -0
  23. package/lib/commonjs/index.js +118 -0
  24. package/lib/commonjs/package.json +1 -0
  25. package/lib/commonjs/preset.js +214 -0
  26. package/lib/module/impersonate/components/DataNukeSettings.js +710 -0
  27. package/lib/module/impersonate/components/ImpersonateBanner.js +211 -0
  28. package/lib/module/impersonate/components/ImpersonateHistoryList.js +168 -0
  29. package/lib/module/impersonate/components/ImpersonateModal.js +300 -0
  30. package/lib/module/impersonate/components/ImpersonateStatusBar.js +125 -0
  31. package/lib/module/impersonate/components/UserAvatar.js +140 -0
  32. package/lib/module/impersonate/components/UserCard.js +195 -0
  33. package/lib/module/impersonate/components/UserSearchView.js +222 -0
  34. package/lib/module/impersonate/components/index.js +11 -0
  35. package/lib/module/impersonate/hooks/index.js +7 -0
  36. package/lib/module/impersonate/hooks/useAutoClearAsyncStorage.js +140 -0
  37. package/lib/module/impersonate/hooks/useAutoClearReactQuery.js +151 -0
  38. package/lib/module/impersonate/hooks/useAutoClearRedux.js +183 -0
  39. package/lib/module/impersonate/hooks/useImpersonate.js +212 -0
  40. package/lib/module/impersonate/hooks/useImpersonateHistory.js +52 -0
  41. package/lib/module/impersonate/index.js +13 -0
  42. package/lib/module/impersonate/types/index.js +3 -0
  43. package/lib/module/impersonate/types/types.js +1 -0
  44. package/lib/module/impersonate/utils/impersonateListener.js +271 -0
  45. package/lib/module/impersonate/utils/impersonateStore.js +604 -0
  46. package/lib/module/impersonate/utils/index.js +4 -0
  47. package/lib/module/index.js +103 -0
  48. package/lib/module/preset.js +209 -0
  49. package/lib/typescript/impersonate/components/DataNukeSettings.d.ts +37 -0
  50. package/lib/typescript/impersonate/components/DataNukeSettings.d.ts.map +1 -0
  51. package/lib/typescript/impersonate/components/ImpersonateBanner.d.ts +40 -0
  52. package/lib/typescript/impersonate/components/ImpersonateBanner.d.ts.map +1 -0
  53. package/lib/typescript/impersonate/components/ImpersonateHistoryList.d.ts +24 -0
  54. package/lib/typescript/impersonate/components/ImpersonateHistoryList.d.ts.map +1 -0
  55. package/lib/typescript/impersonate/components/ImpersonateModal.d.ts +10 -0
  56. package/lib/typescript/impersonate/components/ImpersonateModal.d.ts.map +1 -0
  57. package/lib/typescript/impersonate/components/ImpersonateStatusBar.d.ts +15 -0
  58. package/lib/typescript/impersonate/components/ImpersonateStatusBar.d.ts.map +1 -0
  59. package/lib/typescript/impersonate/components/UserAvatar.d.ts +32 -0
  60. package/lib/typescript/impersonate/components/UserAvatar.d.ts.map +1 -0
  61. package/lib/typescript/impersonate/components/UserCard.d.ts +28 -0
  62. package/lib/typescript/impersonate/components/UserCard.d.ts.map +1 -0
  63. package/lib/typescript/impersonate/components/UserSearchView.d.ts +31 -0
  64. package/lib/typescript/impersonate/components/UserSearchView.d.ts.map +1 -0
  65. package/lib/typescript/impersonate/components/index.d.ts +16 -0
  66. package/lib/typescript/impersonate/components/index.d.ts.map +1 -0
  67. package/lib/typescript/impersonate/hooks/index.d.ts +11 -0
  68. package/lib/typescript/impersonate/hooks/index.d.ts.map +1 -0
  69. package/lib/typescript/impersonate/hooks/useAutoClearAsyncStorage.d.ts +48 -0
  70. package/lib/typescript/impersonate/hooks/useAutoClearAsyncStorage.d.ts.map +1 -0
  71. package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.d.ts +48 -0
  72. package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.d.ts.map +1 -0
  73. package/lib/typescript/impersonate/hooks/useAutoClearRedux.d.ts +78 -0
  74. package/lib/typescript/impersonate/hooks/useAutoClearRedux.d.ts.map +1 -0
  75. package/lib/typescript/impersonate/hooks/useImpersonate.d.ts +76 -0
  76. package/lib/typescript/impersonate/hooks/useImpersonate.d.ts.map +1 -0
  77. package/lib/typescript/impersonate/hooks/useImpersonateHistory.d.ts +43 -0
  78. package/lib/typescript/impersonate/hooks/useImpersonateHistory.d.ts.map +1 -0
  79. package/lib/typescript/impersonate/index.d.ts +5 -0
  80. package/lib/typescript/impersonate/index.d.ts.map +1 -0
  81. package/lib/typescript/impersonate/types/index.d.ts +2 -0
  82. package/lib/typescript/impersonate/types/index.d.ts.map +1 -0
  83. package/lib/typescript/impersonate/types/types.d.ts +177 -0
  84. package/lib/typescript/impersonate/types/types.d.ts.map +1 -0
  85. package/lib/typescript/impersonate/utils/impersonateListener.d.ts +115 -0
  86. package/lib/typescript/impersonate/utils/impersonateListener.d.ts.map +1 -0
  87. package/lib/typescript/impersonate/utils/impersonateStore.d.ts +151 -0
  88. package/lib/typescript/impersonate/utils/impersonateStore.d.ts.map +1 -0
  89. package/lib/typescript/impersonate/utils/index.d.ts +3 -0
  90. package/lib/typescript/impersonate/utils/index.d.ts.map +1 -0
  91. package/lib/typescript/index.d.ts +80 -0
  92. package/lib/typescript/index.d.ts.map +1 -0
  93. package/lib/typescript/preset.d.ts +71 -0
  94. package/lib/typescript/preset.d.ts.map +1 -0
  95. package/package.json +78 -0
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ImpersonateBanner = ImpersonateBanner;
7
+ exports.ImpersonateBannerMinimal = ImpersonateBannerMinimal;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _reactNative = require("react-native");
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
+ var _impersonateStore = require("../utils/impersonateStore");
12
+ var _UserAvatar = require("./UserAvatar");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
+ /**
16
+ * ImpersonateBanner Component
17
+ *
18
+ * A floating banner that shows when impersonation is active.
19
+ * Features polished design with avatar and elegant animations.
20
+ * Can be positioned at top or bottom of the screen.
21
+ */
22
+
23
+ /**
24
+ * Floating banner showing impersonation status
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * function App() {
29
+ * return (
30
+ * <>
31
+ * <MainContent />
32
+ * <ImpersonateBanner
33
+ * position="top"
34
+ * offset={60}
35
+ * onPress={() => openImpersonateModal()}
36
+ * />
37
+ * </>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ function ImpersonateBanner({
43
+ onPress,
44
+ onStopPress,
45
+ position = "top",
46
+ offset = 50
47
+ }) {
48
+ const state = (0, _react.useSyncExternalStore)(_impersonateStore.impersonateStore.subscribe, _impersonateStore.impersonateStore.getSnapshot, _impersonateStore.impersonateStore.getSnapshot);
49
+
50
+ // Don't render if not impersonating or banner is disabled
51
+ if (!state.isActive || !state.currentUser || !state.showBanner) {
52
+ return null;
53
+ }
54
+ const handlePauseResume = async () => {
55
+ if (state.isPaused) {
56
+ await _impersonateStore.impersonateStore.resumeImpersonation();
57
+ } else {
58
+ await _impersonateStore.impersonateStore.pauseImpersonation();
59
+ }
60
+ };
61
+ const displayName = state.currentUser.displayName || state.currentUser.email || state.currentUser.id;
62
+ const positionStyle = position === "top" ? {
63
+ top: offset
64
+ } : {
65
+ bottom: offset
66
+ };
67
+ const handleStop = async () => {
68
+ await _impersonateStore.impersonateStore.stopImpersonation();
69
+ };
70
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
71
+ style: [styles.container, positionStyle],
72
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
73
+ style: styles.banner,
74
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
75
+ style: styles.userSection,
76
+ onPress: onPress,
77
+ activeOpacity: 0.7,
78
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserAvatar.UserAvatar, {
79
+ userId: state.currentUser.id,
80
+ name: displayName,
81
+ size: "small",
82
+ showActiveIndicator: false
83
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
84
+ style: [styles.userName, state.isPaused && styles.userNamePaused],
85
+ numberOfLines: 1,
86
+ children: displayName
87
+ })]
88
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
89
+ isEnabled: !state.isPaused,
90
+ onToggle: handlePauseResume,
91
+ size: "small",
92
+ accessibilityLabel: "Toggle impersonation"
93
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
94
+ style: styles.closeButton,
95
+ onPress: handleStop,
96
+ activeOpacity: 0.7,
97
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
98
+ size: 12,
99
+ color: "rgba(255, 255, 255, 0.5)"
100
+ })
101
+ })]
102
+ })
103
+ });
104
+ }
105
+
106
+ /**
107
+ * Minimal banner variant - just an indicator badge
108
+ */
109
+ function ImpersonateBannerMinimal({
110
+ onPress
111
+ }) {
112
+ const state = (0, _react.useSyncExternalStore)(_impersonateStore.impersonateStore.subscribe, _impersonateStore.impersonateStore.getSnapshot, _impersonateStore.impersonateStore.getSnapshot);
113
+ if (!state.isActive || !state.currentUser) {
114
+ return null;
115
+ }
116
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
117
+ style: styles.minimalBanner,
118
+ onPress: onPress,
119
+ activeOpacity: 0.8,
120
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
121
+ style: styles.minimalDot
122
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
123
+ style: styles.minimalText,
124
+ children: "IMP"
125
+ })]
126
+ });
127
+ }
128
+ const styles = _reactNative.StyleSheet.create({
129
+ container: {
130
+ position: "absolute",
131
+ left: 0,
132
+ right: 0,
133
+ alignItems: "center",
134
+ zIndex: 9999,
135
+ pointerEvents: "box-none"
136
+ },
137
+ banner: {
138
+ flexDirection: "row",
139
+ alignItems: "center",
140
+ backgroundColor: "rgba(20, 20, 20, 0.94)",
141
+ borderWidth: 1,
142
+ borderColor: _sharedUi.buoyColors.success + "40",
143
+ borderRadius: 22,
144
+ paddingVertical: 7,
145
+ paddingLeft: 7,
146
+ paddingRight: 10,
147
+ gap: 10,
148
+ marginTop: 3,
149
+ shadowColor: "#000",
150
+ shadowOffset: {
151
+ width: 0,
152
+ height: 6
153
+ },
154
+ shadowOpacity: 0.3,
155
+ shadowRadius: 14,
156
+ elevation: 10
157
+ },
158
+ userSection: {
159
+ flexDirection: "row",
160
+ alignItems: "center",
161
+ gap: 10
162
+ },
163
+ userName: {
164
+ fontSize: 14,
165
+ fontWeight: "600",
166
+ color: "#fff",
167
+ maxWidth: 180
168
+ },
169
+ userNamePaused: {
170
+ opacity: 0.6
171
+ },
172
+ closeButton: {
173
+ width: 22,
174
+ height: 22,
175
+ borderRadius: 11,
176
+ backgroundColor: "rgba(255, 255, 255, 0.08)",
177
+ alignItems: "center",
178
+ justifyContent: "center",
179
+ marginLeft: -4
180
+ },
181
+ // Minimal variant
182
+ minimalBanner: {
183
+ position: "absolute",
184
+ top: 60,
185
+ right: 16,
186
+ flexDirection: "row",
187
+ alignItems: "center",
188
+ backgroundColor: "rgba(26, 26, 26, 0.92)",
189
+ borderWidth: 1,
190
+ borderColor: _sharedUi.buoyColors.success + "35",
191
+ borderRadius: 12,
192
+ paddingVertical: 5,
193
+ paddingHorizontal: 10,
194
+ gap: 5,
195
+ zIndex: 9999,
196
+ shadowColor: "#000",
197
+ shadowOffset: {
198
+ width: 0,
199
+ height: 2
200
+ },
201
+ shadowOpacity: 0.2,
202
+ shadowRadius: 6,
203
+ elevation: 4
204
+ },
205
+ minimalDot: {
206
+ width: 6,
207
+ height: 6,
208
+ borderRadius: 3,
209
+ backgroundColor: _sharedUi.buoyColors.success
210
+ },
211
+ minimalText: {
212
+ fontSize: 10,
213
+ fontWeight: "700",
214
+ color: _sharedUi.buoyColors.success,
215
+ letterSpacing: 0.5
216
+ }
217
+ });
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ImpersonateHistoryList = ImpersonateHistoryList;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _UserCard = require("./UserCard");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
13
+ /**
14
+ * ImpersonateHistoryList Component
15
+ *
16
+ * List of recently impersonated users for quick switching.
17
+ * Uses polished UserCard component for consistent styling.
18
+ */
19
+
20
+ function ImpersonateHistoryList({
21
+ history,
22
+ currentUserId,
23
+ onSelectUser,
24
+ onStopImpersonation,
25
+ onRemoveFromHistory,
26
+ onClearHistory
27
+ }) {
28
+ const renderHistoryItem = (0, _react.useCallback)(({
29
+ item
30
+ }) => {
31
+ const {
32
+ user,
33
+ lastUsedAt
34
+ } = item;
35
+ const isActive = currentUserId === user.id;
36
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_UserCard.UserCard, {
37
+ user: user,
38
+ isActive: isActive,
39
+ onPress: () => !isActive && onSelectUser(user),
40
+ onStop: isActive ? onStopImpersonation : undefined,
41
+ onRemove: onRemoveFromHistory ? () => onRemoveFromHistory(user.id) : undefined,
42
+ showRemoveButton: !!onRemoveFromHistory,
43
+ lastUsedTime: (0, _sharedUi.formatRelativeTime)(new Date(lastUsedAt))
44
+ });
45
+ }, [currentUserId, onSelectUser, onStopImpersonation, onRemoveFromHistory]);
46
+ if (history.length === 0) {
47
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
48
+ style: styles.emptyContainer,
49
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
50
+ style: styles.emptyIcon,
51
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Clock, {
52
+ size: 28,
53
+ color: _sharedUi.buoyColors.textMuted
54
+ })
55
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
56
+ style: styles.emptyTitle,
57
+ children: "No History"
58
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
59
+ style: styles.emptyDescription,
60
+ children: "Users you impersonate will appear here for quick access"
61
+ })]
62
+ });
63
+ }
64
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
65
+ style: styles.container,
66
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
67
+ style: styles.headerRow,
68
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
69
+ style: styles.headerTitle,
70
+ children: "Recent"
71
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
72
+ style: styles.headerBadge,
73
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
74
+ style: styles.headerBadgeText,
75
+ children: history.length
76
+ })
77
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
78
+ style: styles.headerSpacer
79
+ }), onClearHistory && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
80
+ style: styles.clearButton,
81
+ onPress: onClearHistory,
82
+ activeOpacity: 0.7,
83
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
84
+ size: 12,
85
+ color: _sharedUi.buoyColors.textMuted
86
+ })
87
+ })]
88
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
89
+ data: history,
90
+ keyExtractor: item => item.user.id,
91
+ renderItem: renderHistoryItem,
92
+ ItemSeparatorComponent: () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
93
+ style: styles.separator
94
+ }),
95
+ contentContainerStyle: styles.listContent,
96
+ showsVerticalScrollIndicator: false
97
+ })]
98
+ });
99
+ }
100
+ const styles = _reactNative.StyleSheet.create({
101
+ container: {
102
+ flex: 1,
103
+ paddingHorizontal: 16
104
+ },
105
+ headerRow: {
106
+ flexDirection: "row",
107
+ alignItems: "center",
108
+ paddingVertical: 10,
109
+ gap: 8
110
+ },
111
+ headerTitle: {
112
+ fontSize: 12,
113
+ fontWeight: "600",
114
+ color: _sharedUi.buoyColors.textMuted,
115
+ textTransform: "uppercase",
116
+ letterSpacing: 0.5
117
+ },
118
+ headerBadge: {
119
+ backgroundColor: _sharedUi.buoyColors.hover,
120
+ paddingHorizontal: 6,
121
+ paddingVertical: 2,
122
+ borderRadius: 4
123
+ },
124
+ headerBadgeText: {
125
+ fontSize: 10,
126
+ fontWeight: "600",
127
+ color: _sharedUi.buoyColors.textMuted
128
+ },
129
+ headerSpacer: {
130
+ flex: 1
131
+ },
132
+ clearButton: {
133
+ width: 24,
134
+ height: 24,
135
+ alignItems: "center",
136
+ justifyContent: "center",
137
+ backgroundColor: _sharedUi.buoyColors.hover,
138
+ borderRadius: 4
139
+ },
140
+ listContent: {
141
+ paddingBottom: 20
142
+ },
143
+ separator: {
144
+ height: 8
145
+ },
146
+ emptyContainer: {
147
+ flex: 1,
148
+ alignItems: "center",
149
+ justifyContent: "center",
150
+ paddingHorizontal: 32,
151
+ paddingVertical: 48
152
+ },
153
+ emptyIcon: {
154
+ width: 56,
155
+ height: 56,
156
+ borderRadius: 28,
157
+ backgroundColor: _sharedUi.buoyColors.hover,
158
+ alignItems: "center",
159
+ justifyContent: "center",
160
+ marginBottom: 16
161
+ },
162
+ emptyTitle: {
163
+ fontSize: 16,
164
+ fontWeight: "600",
165
+ color: _sharedUi.buoyColors.text,
166
+ marginBottom: 6
167
+ },
168
+ emptyDescription: {
169
+ fontSize: 14,
170
+ color: _sharedUi.buoyColors.textSecondary,
171
+ textAlign: "center"
172
+ }
173
+ });
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ImpersonateModal = ImpersonateModal;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _useImpersonate = require("../hooks/useImpersonate");
11
+ var _useAutoClearReactQuery = require("../hooks/useAutoClearReactQuery");
12
+ var _useAutoClearRedux = require("../hooks/useAutoClearRedux");
13
+ var _useAutoClearAsyncStorage = require("../hooks/useAutoClearAsyncStorage");
14
+ var _UserSearchView = require("./UserSearchView");
15
+ var _ImpersonateHistoryList = require("./ImpersonateHistoryList");
16
+ var _DataNukeSettings = require("./DataNukeSettings");
17
+ var _jsxRuntime = require("react/jsx-runtime");
18
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
19
+ /**
20
+ * ImpersonateModal Component
21
+ *
22
+ * Main modal for the impersonation tool. Uses JsModal with ModalHeader
23
+ * pattern matching other Buoy dev tools (Network, Storage, etc.).
24
+ */
25
+
26
+ // Type assertion to work around React types compatibility issue with memo components
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ const JsModal = _sharedUi.JsModal;
29
+
30
+ // Local Tab type (matches shared-ui's TabSelector)
31
+
32
+ const ALL_TABS = [{
33
+ key: "search",
34
+ label: "Search"
35
+ }, {
36
+ key: "history",
37
+ label: "History"
38
+ }, {
39
+ key: "settings",
40
+ label: "Settings"
41
+ }];
42
+ const TABS_WITHOUT_SETTINGS = [{
43
+ key: "search",
44
+ label: "Search"
45
+ }, {
46
+ key: "history",
47
+ label: "History"
48
+ }];
49
+ function ImpersonateModal({
50
+ visible,
51
+ onClose,
52
+ onBack,
53
+ onMinimize,
54
+ onSearchUsers,
55
+ onClearReactQuery,
56
+ onClearRedux,
57
+ onClearAsyncStorage,
58
+ onClearMMKV,
59
+ showSettingsTab = true
60
+ }) {
61
+ // Select tabs based on showSettingsTab prop
62
+ const tabs = showSettingsTab ? ALL_TABS : TABS_WITHOUT_SETTINGS;
63
+ const [activeTab, setActiveTab] = (0, _react.useState)("search");
64
+ const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
65
+ const [searchText, setSearchText] = (0, _react.useState)("");
66
+ const searchInputRef = (0, _react.useRef)(null);
67
+
68
+ // Auto-detect React Query and get clear function if user didn't provide one
69
+ const {
70
+ clearCache: autoClearReactQuery,
71
+ isAvailable: isReactQueryDetected
72
+ } = (0, _useAutoClearReactQuery.useAutoClearReactQuery)();
73
+
74
+ // Auto-detect Redux and get reset function if user didn't provide one
75
+ const {
76
+ resetStore: autoClearRedux,
77
+ isAvailable: isReduxDetected
78
+ } = (0, _useAutoClearRedux.useAutoClearRedux)();
79
+
80
+ // Auto-detect AsyncStorage and get clear function if user didn't provide one
81
+ const {
82
+ clearStorage: autoClearAsyncStorage,
83
+ isAvailable: isAsyncStorageDetected
84
+ } = (0, _useAutoClearAsyncStorage.useAutoClearAsyncStorage)();
85
+
86
+ // Use user-provided callbacks, or fall back to auto-detected ones
87
+ const effectiveClearReactQuery = onClearReactQuery ?? autoClearReactQuery ?? undefined;
88
+ const effectiveClearRedux = onClearRedux ?? autoClearRedux ?? undefined;
89
+ const effectiveClearAsyncStorage = onClearAsyncStorage ?? autoClearAsyncStorage ?? undefined;
90
+
91
+ // Detection status for settings display
92
+ // Shows "detected" if either user provided callback OR auto-detected
93
+ const detectionStatus = {
94
+ reactQuery: !!onClearReactQuery || isReactQueryDetected,
95
+ redux: !!onClearRedux || isReduxDetected,
96
+ asyncStorage: !!onClearAsyncStorage || isAsyncStorageDetected,
97
+ mmkv: !!onClearMMKV // TODO: implement auto-detection
98
+ };
99
+ const {
100
+ isActive,
101
+ currentUser,
102
+ headerKey,
103
+ dataNukeSettings,
104
+ showBanner,
105
+ searchQuery,
106
+ searchResults,
107
+ isSearching,
108
+ searchError,
109
+ searchUsers,
110
+ startImpersonation,
111
+ stopImpersonation,
112
+ updateHeaderKey,
113
+ updateDataNukeSettings,
114
+ updateShowBanner,
115
+ history,
116
+ removeFromHistory,
117
+ clearHistory
118
+ } = (0, _useImpersonate.useImpersonate)({
119
+ onSearchUsers,
120
+ onClearReactQuery: effectiveClearReactQuery,
121
+ onClearRedux: effectiveClearRedux,
122
+ onClearAsyncStorage: effectiveClearAsyncStorage,
123
+ onClearMMKV
124
+ });
125
+
126
+ // Focus input when search becomes active
127
+ (0, _react.useEffect)(() => {
128
+ if (isSearchActive && searchInputRef.current) {
129
+ setTimeout(() => {
130
+ searchInputRef.current?.focus();
131
+ }, 100);
132
+ }
133
+ }, [isSearchActive]);
134
+
135
+ // Reset to search tab if settings tab is hidden while active
136
+ (0, _react.useEffect)(() => {
137
+ if (!showSettingsTab && activeTab === "settings") {
138
+ setActiveTab("search");
139
+ }
140
+ }, [showSettingsTab, activeTab]);
141
+ const handleSearchTextChange = (0, _react.useCallback)(text => {
142
+ setSearchText(text);
143
+ }, []);
144
+ const handleSubmitSearch = (0, _react.useCallback)(() => {
145
+ if (searchText.trim()) {
146
+ searchUsers(searchText.trim());
147
+ }
148
+ setIsSearchActive(false);
149
+ }, [searchText, searchUsers]);
150
+ const handleSelectUser = (0, _react.useCallback)(async user => {
151
+ await startImpersonation(user);
152
+ }, [startImpersonation]);
153
+ const handleSaveSettings = (0, _react.useCallback)(async (newHeaderKey, settings, newShowBanner) => {
154
+ // Save header key, data nuke settings, and show banner
155
+ await updateHeaderKey(newHeaderKey);
156
+ await updateDataNukeSettings(settings);
157
+ await updateShowBanner(newShowBanner);
158
+ }, [updateHeaderKey, updateDataNukeSettings, updateShowBanner]);
159
+ const handleTabChange = (0, _react.useCallback)(tabKey => {
160
+ setActiveTab(tabKey);
161
+ // Close search when switching tabs
162
+ setIsSearchActive(false);
163
+ }, []);
164
+
165
+ // Header content renderer (matching NetworkModal pattern)
166
+ const renderHeaderContent = () => {
167
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
168
+ children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
169
+ onBack: onBack
170
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
171
+ title: "",
172
+ children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
173
+ style: styles.headerSearchContainer,
174
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
175
+ size: 14,
176
+ color: _sharedUi.macOSColors.text.secondary
177
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
178
+ ref: searchInputRef,
179
+ style: styles.headerSearchInput,
180
+ placeholder: "Search by email, name, or ID...",
181
+ placeholderTextColor: _sharedUi.macOSColors.text.muted,
182
+ value: searchText,
183
+ onChangeText: handleSearchTextChange,
184
+ onSubmitEditing: handleSubmitSearch,
185
+ onBlur: () => setIsSearchActive(false),
186
+ autoCapitalize: "none",
187
+ autoCorrect: false,
188
+ returnKeyType: "search"
189
+ }), searchText.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
190
+ onPress: () => {
191
+ setSearchText("");
192
+ setIsSearchActive(false);
193
+ },
194
+ style: styles.headerSearchClear,
195
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
196
+ size: 14,
197
+ color: _sharedUi.macOSColors.text.secondary
198
+ })
199
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
200
+ onPress: () => setIsSearchActive(false),
201
+ style: styles.headerSearchClear,
202
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
203
+ size: 14,
204
+ color: _sharedUi.macOSColors.text.muted
205
+ })
206
+ })]
207
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
208
+ tabs: tabs,
209
+ activeTab: activeTab,
210
+ onTabChange: handleTabChange
211
+ })
212
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {
213
+ children: activeTab === "search" && !isSearchActive && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
214
+ onPress: () => setIsSearchActive(true),
215
+ style: styles.headerActionButton,
216
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
217
+ size: 14,
218
+ color: _sharedUi.macOSColors.text.secondary
219
+ })
220
+ })
221
+ })]
222
+ });
223
+ };
224
+ if (!visible) {
225
+ return null;
226
+ }
227
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(JsModal, {
228
+ visible: visible,
229
+ onClose: onClose,
230
+ onMinimize: onMinimize,
231
+ header: {
232
+ showToggleButton: true,
233
+ customContent: renderHeaderContent()
234
+ },
235
+ enablePersistence: true,
236
+ initialMode: "bottomSheet",
237
+ disableScrollWrapper: true,
238
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
239
+ style: styles.container,
240
+ children: [activeTab === "search" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_UserSearchView.UserSearchView, {
241
+ currentUser: currentUser,
242
+ isActive: isActive,
243
+ searchQuery: searchQuery,
244
+ searchResults: searchResults,
245
+ isSearching: isSearching,
246
+ searchError: searchError,
247
+ onSelectUser: handleSelectUser,
248
+ onStopImpersonation: stopImpersonation,
249
+ searchAvailable: !!onSearchUsers
250
+ }), activeTab === "history" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_ImpersonateHistoryList.ImpersonateHistoryList, {
251
+ history: history,
252
+ currentUserId: currentUser?.id ?? null,
253
+ onSelectUser: handleSelectUser,
254
+ onStopImpersonation: stopImpersonation,
255
+ onRemoveFromHistory: removeFromHistory,
256
+ onClearHistory: clearHistory
257
+ }), activeTab === "settings" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_DataNukeSettings.DataNukeSettings, {
258
+ headerKey: headerKey,
259
+ settings: dataNukeSettings,
260
+ showBanner: showBanner,
261
+ onSave: handleSaveSettings,
262
+ onShowBannerChange: updateShowBanner,
263
+ detectionStatus: detectionStatus
264
+ })]
265
+ })
266
+ });
267
+ }
268
+ const styles = _reactNative.StyleSheet.create({
269
+ container: {
270
+ flex: 1
271
+ },
272
+ headerSearchContainer: {
273
+ flexDirection: "row",
274
+ alignItems: "center",
275
+ backgroundColor: _sharedUi.macOSColors.background.input,
276
+ borderRadius: 6,
277
+ borderWidth: 1,
278
+ borderColor: _sharedUi.macOSColors.border.input,
279
+ paddingHorizontal: 12,
280
+ paddingVertical: 8,
281
+ marginTop: 8,
282
+ flex: 1,
283
+ marginBottom: 2
284
+ },
285
+ headerSearchInput: {
286
+ flex: 1,
287
+ color: _sharedUi.macOSColors.text.primary,
288
+ fontSize: 13,
289
+ marginLeft: 6,
290
+ paddingVertical: 2
291
+ },
292
+ headerSearchClear: {
293
+ marginLeft: 6,
294
+ padding: 4
295
+ },
296
+ headerActionButton: {
297
+ width: 28,
298
+ height: 28,
299
+ borderRadius: 6,
300
+ backgroundColor: _sharedUi.macOSColors.background.hover,
301
+ justifyContent: "center",
302
+ alignItems: "center"
303
+ }
304
+ });