@buoy-gg/impersonate 7.0.41 → 7.0.43
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/README.md +12 -80
- package/lib/commonjs/impersonate/components/DataNukeSettings.js +8 -675
- package/lib/commonjs/impersonate/components/ImpersonateBanner.js +1 -221
- package/lib/commonjs/impersonate/components/ImpersonateHistoryList.js +1 -181
- package/lib/commonjs/impersonate/components/ImpersonateModal.js +1 -319
- package/lib/commonjs/impersonate/components/ImpersonateOverlay.js +1 -26
- package/lib/commonjs/impersonate/components/ImpersonateStatusBar.js +1 -130
- package/lib/commonjs/impersonate/components/UserAvatar.js +1 -146
- package/lib/commonjs/impersonate/components/UserCard.js +1 -201
- package/lib/commonjs/impersonate/components/UserSearchView.js +1 -227
- package/lib/commonjs/impersonate/hooks/asyncStoragePeer.js +1 -17
- package/lib/commonjs/impersonate/hooks/asyncStoragePeer.web.js +1 -11
- package/lib/commonjs/impersonate/hooks/useAutoClearAsyncStorage.js +1 -134
- package/lib/commonjs/impersonate/hooks/useAutoClearReactQuery.js +1 -155
- package/lib/commonjs/impersonate/hooks/useAutoClearReactQuery.web.js +1 -20
- package/lib/commonjs/impersonate/hooks/useAutoClearRedux.js +1 -188
- package/lib/commonjs/impersonate/hooks/useAutoClearRedux.web.js +1 -27
- package/lib/commonjs/impersonate/hooks/useImpersonate.js +1 -215
- package/lib/commonjs/impersonate/hooks/useImpersonateHistory.js +1 -56
- package/lib/commonjs/impersonate/types/index.js +1 -16
- package/lib/commonjs/impersonate/types/types.js +1 -1
- package/lib/commonjs/impersonate/utils/impersonateListener.js +1 -350
- package/lib/commonjs/impersonate/utils/impersonateStore.js +1 -689
- package/lib/commonjs/index.js +1 -150
- package/lib/commonjs/preset.js +1 -232
- package/lib/commonjs/sync/impersonateSyncAdapter.js +1 -170
- package/lib/module/impersonate/components/DataNukeSettings.js +8 -670
- package/lib/module/impersonate/components/ImpersonateBanner.js +1 -215
- package/lib/module/impersonate/components/ImpersonateHistoryList.js +1 -176
- package/lib/module/impersonate/components/ImpersonateModal.js +1 -314
- package/lib/module/impersonate/components/ImpersonateOverlay.js +1 -21
- package/lib/module/impersonate/components/ImpersonateStatusBar.js +1 -125
- package/lib/module/impersonate/components/UserAvatar.js +1 -140
- package/lib/module/impersonate/components/UserCard.js +1 -196
- package/lib/module/impersonate/components/UserSearchView.js +1 -222
- package/lib/module/impersonate/hooks/asyncStoragePeer.js +1 -13
- package/lib/module/impersonate/hooks/asyncStoragePeer.web.js +1 -6
- package/lib/module/impersonate/hooks/useAutoClearAsyncStorage.js +1 -130
- package/lib/module/impersonate/hooks/useAutoClearReactQuery.js +1 -151
- package/lib/module/impersonate/hooks/useAutoClearReactQuery.web.js +1 -16
- package/lib/module/impersonate/hooks/useAutoClearRedux.js +1 -183
- package/lib/module/impersonate/hooks/useAutoClearRedux.web.js +1 -22
- package/lib/module/impersonate/hooks/useImpersonate.js +1 -212
- package/lib/module/impersonate/hooks/useImpersonateHistory.js +1 -52
- package/lib/module/impersonate/types/index.js +1 -3
- package/lib/module/impersonate/types/types.js +1 -1
- package/lib/module/impersonate/utils/impersonateListener.js +1 -341
- package/lib/module/impersonate/utils/impersonateStore.js +1 -686
- package/lib/module/index.js +1 -117
- package/lib/module/preset.js +1 -216
- package/lib/module/sync/impersonateSyncAdapter.js +1 -162
- package/lib/web/index.mjs +7 -8
- package/package.json +3 -6
- package/lib/typescript/impersonate/components/DataNukeSettings.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/ImpersonateBanner.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/ImpersonateHistoryList.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/ImpersonateModal.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/ImpersonateOverlay.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/ImpersonateStatusBar.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/UserAvatar.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/UserCard.d.ts.map +0 -1
- package/lib/typescript/impersonate/components/UserSearchView.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/asyncStoragePeer.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/asyncStoragePeer.web.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/useAutoClearAsyncStorage.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/useAutoClearReactQuery.web.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/useAutoClearRedux.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/useAutoClearRedux.web.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/useImpersonate.d.ts.map +0 -1
- package/lib/typescript/impersonate/hooks/useImpersonateHistory.d.ts.map +0 -1
- package/lib/typescript/impersonate/types/index.d.ts.map +0 -1
- package/lib/typescript/impersonate/types/types.d.ts.map +0 -1
- package/lib/typescript/impersonate/utils/impersonateListener.d.ts.map +0 -1
- package/lib/typescript/impersonate/utils/impersonateStore.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/sync/impersonateSyncAdapter.d.ts.map +0 -1
|
@@ -1,319 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.ImpersonateModal = ImpersonateModal;
|
|
7
|
-
exports.ImpersonateToolView = ImpersonateToolView;
|
|
8
|
-
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
-
var _reactNative = require("react-native");
|
|
10
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
11
|
-
var _useImpersonate = require("../hooks/useImpersonate");
|
|
12
|
-
var _useAutoClearReactQuery = require("../hooks/useAutoClearReactQuery");
|
|
13
|
-
var _useAutoClearRedux = require("../hooks/useAutoClearRedux");
|
|
14
|
-
var _useAutoClearAsyncStorage = require("../hooks/useAutoClearAsyncStorage");
|
|
15
|
-
var _UserSearchView = require("./UserSearchView");
|
|
16
|
-
var _ImpersonateHistoryList = require("./ImpersonateHistoryList");
|
|
17
|
-
var _DataNukeSettings = require("./DataNukeSettings");
|
|
18
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
19
|
-
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); }
|
|
20
|
-
/**
|
|
21
|
-
* ImpersonateModal Component
|
|
22
|
-
*
|
|
23
|
-
* Main modal for the impersonation tool. Uses JsModal with ModalHeader
|
|
24
|
-
* pattern matching other Buoy dev tools (Network, Storage, etc.).
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
// Type assertion to work around React types compatibility issue with memo components
|
|
28
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
-
const JsModal = _sharedUi.JsModal;
|
|
30
|
-
|
|
31
|
-
// Local Tab type (matches shared-ui's TabSelector)
|
|
32
|
-
|
|
33
|
-
const ALL_TABS = [{
|
|
34
|
-
key: "search",
|
|
35
|
-
label: "Search"
|
|
36
|
-
}, {
|
|
37
|
-
key: "history",
|
|
38
|
-
label: "History"
|
|
39
|
-
}, {
|
|
40
|
-
key: "settings",
|
|
41
|
-
label: "Settings"
|
|
42
|
-
}];
|
|
43
|
-
const TABS_WITHOUT_SETTINGS = [{
|
|
44
|
-
key: "search",
|
|
45
|
-
label: "Search"
|
|
46
|
-
}, {
|
|
47
|
-
key: "history",
|
|
48
|
-
label: "History"
|
|
49
|
-
}];
|
|
50
|
-
function ImpersonateModal({
|
|
51
|
-
visible,
|
|
52
|
-
onClose,
|
|
53
|
-
onBack,
|
|
54
|
-
onMinimize,
|
|
55
|
-
onSearchUsers,
|
|
56
|
-
onClearReactQuery,
|
|
57
|
-
onClearRedux,
|
|
58
|
-
onClearAsyncStorage,
|
|
59
|
-
onClearMMKV,
|
|
60
|
-
showSettingsTab = true,
|
|
61
|
-
inline = false
|
|
62
|
-
}) {
|
|
63
|
-
// Select tabs based on showSettingsTab prop
|
|
64
|
-
const tabs = showSettingsTab ? ALL_TABS : TABS_WITHOUT_SETTINGS;
|
|
65
|
-
const [activeTab, setActiveTab] = (0, _react.useState)("search");
|
|
66
|
-
const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
|
|
67
|
-
// True while the header's clear ✕ is being pressed. With a mouse, pressing
|
|
68
|
-
// the ✕ blurs the input FIRST, and the blur handler closes search — which
|
|
69
|
-
// unmounts the ✕ before its own press can fire, so the query was never
|
|
70
|
-
// cleared (desktop). Press-in lands before the blur, so the blur handler can
|
|
71
|
-
// tell this dismissal apart from a click elsewhere.
|
|
72
|
-
const clearPressRef = (0, _react.useRef)(false);
|
|
73
|
-
const [searchText, setSearchText] = (0, _react.useState)("");
|
|
74
|
-
const searchInputRef = (0, _react.useRef)(null);
|
|
75
|
-
|
|
76
|
-
// Auto-detect React Query and get clear function if user didn't provide one
|
|
77
|
-
const {
|
|
78
|
-
clearCache: autoClearReactQuery,
|
|
79
|
-
isAvailable: isReactQueryDetected
|
|
80
|
-
} = (0, _useAutoClearReactQuery.useAutoClearReactQuery)();
|
|
81
|
-
|
|
82
|
-
// Auto-detect Redux and get reset function if user didn't provide one
|
|
83
|
-
const {
|
|
84
|
-
resetStore: autoClearRedux,
|
|
85
|
-
isAvailable: isReduxDetected
|
|
86
|
-
} = (0, _useAutoClearRedux.useAutoClearRedux)();
|
|
87
|
-
|
|
88
|
-
// Auto-detect AsyncStorage and get clear function if user didn't provide one
|
|
89
|
-
const {
|
|
90
|
-
clearStorage: autoClearAsyncStorage,
|
|
91
|
-
isAvailable: isAsyncStorageDetected
|
|
92
|
-
} = (0, _useAutoClearAsyncStorage.useAutoClearAsyncStorage)();
|
|
93
|
-
|
|
94
|
-
// Use user-provided callbacks, or fall back to auto-detected ones
|
|
95
|
-
const effectiveClearReactQuery = onClearReactQuery ?? autoClearReactQuery ?? undefined;
|
|
96
|
-
const effectiveClearRedux = onClearRedux ?? autoClearRedux ?? undefined;
|
|
97
|
-
const effectiveClearAsyncStorage = onClearAsyncStorage ?? autoClearAsyncStorage ?? undefined;
|
|
98
|
-
|
|
99
|
-
// Detection status for settings display
|
|
100
|
-
// Shows "detected" if either user provided callback OR auto-detected
|
|
101
|
-
const detectionStatus = {
|
|
102
|
-
reactQuery: !!onClearReactQuery || isReactQueryDetected,
|
|
103
|
-
redux: !!onClearRedux || isReduxDetected,
|
|
104
|
-
asyncStorage: !!onClearAsyncStorage || isAsyncStorageDetected,
|
|
105
|
-
mmkv: !!onClearMMKV // TODO: implement auto-detection
|
|
106
|
-
};
|
|
107
|
-
const {
|
|
108
|
-
isActive,
|
|
109
|
-
currentUser,
|
|
110
|
-
headerKey,
|
|
111
|
-
dataNukeSettings,
|
|
112
|
-
showBanner,
|
|
113
|
-
searchQuery,
|
|
114
|
-
searchResults,
|
|
115
|
-
isSearching,
|
|
116
|
-
searchError,
|
|
117
|
-
searchUsers,
|
|
118
|
-
startImpersonation,
|
|
119
|
-
stopImpersonation,
|
|
120
|
-
updateHeaderKey,
|
|
121
|
-
updateDataNukeSettings,
|
|
122
|
-
updateShowBanner,
|
|
123
|
-
history,
|
|
124
|
-
removeFromHistory,
|
|
125
|
-
clearHistory
|
|
126
|
-
} = (0, _useImpersonate.useImpersonate)({
|
|
127
|
-
onSearchUsers,
|
|
128
|
-
onClearReactQuery: effectiveClearReactQuery,
|
|
129
|
-
onClearRedux: effectiveClearRedux,
|
|
130
|
-
onClearAsyncStorage: effectiveClearAsyncStorage,
|
|
131
|
-
onClearMMKV
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Focus input when search becomes active
|
|
135
|
-
(0, _react.useEffect)(() => {
|
|
136
|
-
if (isSearchActive && searchInputRef.current) {
|
|
137
|
-
setTimeout(() => {
|
|
138
|
-
searchInputRef.current?.focus();
|
|
139
|
-
}, 100);
|
|
140
|
-
}
|
|
141
|
-
}, [isSearchActive]);
|
|
142
|
-
|
|
143
|
-
// Reset to search tab if settings tab is hidden while active
|
|
144
|
-
(0, _react.useEffect)(() => {
|
|
145
|
-
if (!showSettingsTab && activeTab === "settings") {
|
|
146
|
-
setActiveTab("search");
|
|
147
|
-
}
|
|
148
|
-
}, [showSettingsTab, activeTab]);
|
|
149
|
-
const handleSearchTextChange = (0, _react.useCallback)(text => {
|
|
150
|
-
setSearchText(text);
|
|
151
|
-
}, []);
|
|
152
|
-
const handleSubmitSearch = (0, _react.useCallback)(() => {
|
|
153
|
-
if (searchText.trim()) {
|
|
154
|
-
searchUsers(searchText.trim());
|
|
155
|
-
}
|
|
156
|
-
setIsSearchActive(false);
|
|
157
|
-
}, [searchText, searchUsers]);
|
|
158
|
-
const handleSelectUser = (0, _react.useCallback)(async user => {
|
|
159
|
-
await startImpersonation(user);
|
|
160
|
-
}, [startImpersonation]);
|
|
161
|
-
const handleSaveSettings = (0, _react.useCallback)(async (newHeaderKey, settings, newShowBanner) => {
|
|
162
|
-
// Save header key, data nuke settings, and show banner
|
|
163
|
-
await updateHeaderKey(newHeaderKey);
|
|
164
|
-
await updateDataNukeSettings(settings);
|
|
165
|
-
await updateShowBanner(newShowBanner);
|
|
166
|
-
}, [updateHeaderKey, updateDataNukeSettings, updateShowBanner]);
|
|
167
|
-
const handleTabChange = (0, _react.useCallback)(tabKey => {
|
|
168
|
-
setActiveTab(tabKey);
|
|
169
|
-
// Close search when switching tabs
|
|
170
|
-
setIsSearchActive(false);
|
|
171
|
-
}, []);
|
|
172
|
-
|
|
173
|
-
// Header content renderer (matching NetworkModal pattern)
|
|
174
|
-
const renderHeaderContent = () => {
|
|
175
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
|
|
176
|
-
children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
|
|
177
|
-
onBack: onBack
|
|
178
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
179
|
-
title: "",
|
|
180
|
-
children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Search, {
|
|
181
|
-
value: searchText,
|
|
182
|
-
onChangeText: handleSearchTextChange
|
|
183
|
-
// ✕ clears and closes; with no text it simply closes.
|
|
184
|
-
,
|
|
185
|
-
onCancel: () => {
|
|
186
|
-
setSearchText("");
|
|
187
|
-
setIsSearchActive(false);
|
|
188
|
-
},
|
|
189
|
-
onDismiss: () => setIsSearchActive(false),
|
|
190
|
-
dismissOnBlur: true,
|
|
191
|
-
placeholder: "Search by email, name, or ID...",
|
|
192
|
-
iconSize: 14,
|
|
193
|
-
testID: "buoy-impersonate-search-input",
|
|
194
|
-
inputProps: {
|
|
195
|
-
onSubmitEditing: handleSubmitSearch
|
|
196
|
-
}
|
|
197
|
-
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
|
|
198
|
-
tabs: tabs,
|
|
199
|
-
activeTab: activeTab,
|
|
200
|
-
onTabChange: handleTabChange
|
|
201
|
-
})
|
|
202
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions, {
|
|
203
|
-
children: activeTab === "search" && !isSearchActive && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
204
|
-
testID: "buoy-impersonate-search-toggle",
|
|
205
|
-
onPress: () => setIsSearchActive(true),
|
|
206
|
-
style: styles.headerActionButton,
|
|
207
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
|
|
208
|
-
size: 14,
|
|
209
|
-
color: _sharedUi.night.color.textSecondary
|
|
210
|
-
})
|
|
211
|
-
})
|
|
212
|
-
})]
|
|
213
|
-
});
|
|
214
|
-
};
|
|
215
|
-
if (!visible) {
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
const body = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
219
|
-
style: styles.container,
|
|
220
|
-
children: [activeTab === "search" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_UserSearchView.UserSearchView, {
|
|
221
|
-
currentUser: currentUser,
|
|
222
|
-
isActive: isActive,
|
|
223
|
-
searchQuery: searchQuery,
|
|
224
|
-
searchResults: searchResults,
|
|
225
|
-
isSearching: isSearching,
|
|
226
|
-
searchError: searchError,
|
|
227
|
-
onSelectUser: handleSelectUser,
|
|
228
|
-
onStopImpersonation: stopImpersonation,
|
|
229
|
-
searchAvailable: !!onSearchUsers
|
|
230
|
-
}), activeTab === "history" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_ImpersonateHistoryList.ImpersonateHistoryList, {
|
|
231
|
-
history: history,
|
|
232
|
-
currentUserId: currentUser?.id ?? null,
|
|
233
|
-
onSelectUser: handleSelectUser,
|
|
234
|
-
onStopImpersonation: stopImpersonation,
|
|
235
|
-
onRemoveFromHistory: removeFromHistory,
|
|
236
|
-
onClearHistory: clearHistory
|
|
237
|
-
}), activeTab === "settings" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_DataNukeSettings.DataNukeSettings, {
|
|
238
|
-
headerKey: headerKey,
|
|
239
|
-
settings: dataNukeSettings,
|
|
240
|
-
showBanner: showBanner,
|
|
241
|
-
onSave: handleSaveSettings,
|
|
242
|
-
onShowBannerChange: updateShowBanner,
|
|
243
|
-
detectionStatus: detectionStatus
|
|
244
|
-
})]
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// The ONLY difference between the phone and the desktop dashboard is which
|
|
248
|
-
// shell the header and body go in. Everything above this line is shared.
|
|
249
|
-
if (inline) {
|
|
250
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TickProvider, {
|
|
251
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
252
|
-
style: styles.inlineRoot,
|
|
253
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
254
|
-
style: styles.inlineHeader,
|
|
255
|
-
..._sharedUi.windowDragProps,
|
|
256
|
-
children: renderHeaderContent()
|
|
257
|
-
}), body]
|
|
258
|
-
})
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TickProvider, {
|
|
262
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(JsModal, {
|
|
263
|
-
visible: visible,
|
|
264
|
-
onClose: onClose,
|
|
265
|
-
onMinimize: onMinimize,
|
|
266
|
-
header: {
|
|
267
|
-
showToggleButton: true,
|
|
268
|
-
customContent: renderHeaderContent()
|
|
269
|
-
},
|
|
270
|
-
enablePersistence: true,
|
|
271
|
-
initialMode: "bottomSheet",
|
|
272
|
-
disableScrollWrapper: true,
|
|
273
|
-
children: body
|
|
274
|
-
})
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* The same impersonation tool as `ImpersonateModal`, rendered INLINE for hosts
|
|
280
|
-
* that place tools in a panel rather than a modal. Exists so there is exactly
|
|
281
|
-
* one implementation — the desktop dashboard used to ship its own copy of this
|
|
282
|
-
* component's header and tab router.
|
|
283
|
-
*/
|
|
284
|
-
function ImpersonateToolView(props) {
|
|
285
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(ImpersonateModal, {
|
|
286
|
-
...props,
|
|
287
|
-
inline: true,
|
|
288
|
-
visible: true,
|
|
289
|
-
onClose: () => {}
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
const styles = _reactNative.StyleSheet.create({
|
|
293
|
-
inlineRoot: {
|
|
294
|
-
flex: 1,
|
|
295
|
-
minHeight: 0,
|
|
296
|
-
// Transparent so the desktop workbench's hoisted sky (one field spanning
|
|
297
|
-
// sidebar + panel) shows through — the shell paints its base beneath it.
|
|
298
|
-
backgroundColor: "transparent"
|
|
299
|
-
},
|
|
300
|
-
inlineHeader: {
|
|
301
|
-
paddingHorizontal: 12,
|
|
302
|
-
paddingVertical: 8,
|
|
303
|
-
borderBottomWidth: 1,
|
|
304
|
-
borderBottomColor: _sharedUi.buoyColors.border,
|
|
305
|
-
// Clear, like Bench — the host's hoisted sky shows through the header.
|
|
306
|
-
backgroundColor: "transparent"
|
|
307
|
-
},
|
|
308
|
-
container: {
|
|
309
|
-
flex: 1
|
|
310
|
-
},
|
|
311
|
-
headerActionButton: {
|
|
312
|
-
width: 28,
|
|
313
|
-
height: 28,
|
|
314
|
-
borderRadius: 6,
|
|
315
|
-
backgroundColor: _sharedUi.night.color.buttonSurface,
|
|
316
|
-
justifyContent: "center",
|
|
317
|
-
alignItems: "center"
|
|
318
|
-
}
|
|
319
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.ImpersonateModal=ImpersonateModal;exports.ImpersonateToolView=ImpersonateToolView;var _react=_interopRequireWildcard(require("react"));var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _useImpersonate=require("../hooks/useImpersonate");var _useAutoClearReactQuery=require("../hooks/useAutoClearReactQuery");var _useAutoClearRedux=require("../hooks/useAutoClearRedux");var _useAutoClearAsyncStorage=require("../hooks/useAutoClearAsyncStorage");var _UserSearchView=require("./UserSearchView");var _ImpersonateHistoryList=require("./ImpersonateHistoryList");var _DataNukeSettings=require("./DataNukeSettings");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireWildcard(i,y){if("function"==typeof WeakMap)var l=new WeakMap,S=new WeakMap;return(_interopRequireWildcard=function(e,c){if(!c&&e&&e.__esModule)return e;var r,s,a={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return a;if(r=c?S:l){if(r.has(e))return r.get(e);r.set(e,a)}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((s=(r=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(s.get||s.set)?r(a,t,s):a[t]=e[t]);return a})(i,y)}const JsModal=_sharedUi.JsModal;const ALL_TABS=[{key:"search",label:"Search"},{key:"history",label:"History"},{key:"settings",label:"Settings"}];const TABS_WITHOUT_SETTINGS=[{key:"search",label:"Search"},{key:"history",label:"History"}];function ImpersonateModal({visible:i,onClose:y,onBack:l,onMinimize:S,onSearchUsers:e,onClearReactQuery:c,onClearRedux:r,onClearAsyncStorage:s,onClearMMKV:a,showSettingsTab:t=true,inline:_=false}){const w=t?ALL_TABS:TABS_WITHOUT_SETTINGS;const[o,p]=(0,_react.useState)("search");const[d,u]=(0,_react.useState)(false);const ae=(0,_react.useRef)(false);const[h,g]=(0,_react.useState)("");const b=(0,_react.useRef)(null);const{clearCache:H,isAvailable:T}=(0,_useAutoClearReactQuery.useAutoClearReactQuery)();const{resetStore:M,isAvailable:D}=(0,_useAutoClearRedux.useAutoClearRedux)();const{clearStorage:q,isAvailable:B}=(0,_useAutoClearAsyncStorage.useAutoClearAsyncStorage)();const P=c??H??void 0;const U=r??M??void 0;const Q=s??q??void 0;const N={reactQuery:!!c||T,redux:!!r||D,asyncStorage:!!s||B,mmkv:!!a};const{isActive:O,currentUser:m,headerKey:V,dataNukeSettings:W,showBanner:E,searchQuery:L,searchResults:z,isSearching:F,searchError:J,searchUsers:v,startImpersonation:C,stopImpersonation:x,updateHeaderKey:A,updateDataNukeSettings:k,updateShowBanner:f,history:K,removeFromHistory:G,clearHistory:X}=(0,_useImpersonate.useImpersonate)({onSearchUsers:e,onClearReactQuery:P,onClearRedux:U,onClearAsyncStorage:Q,onClearMMKV:a});(0,_react.useEffect)(()=>{if(d&&b.current){setTimeout(()=>{b.current?.focus()},100)}},[d]);(0,_react.useEffect)(()=>{if(!t&&o==="settings"){p("search")}},[t,o]);const Y=(0,_react.useCallback)(n=>{g(n)},[]);const Z=(0,_react.useCallback)(()=>{if(h.trim()){v(h.trim())}u(false)},[h,v]);const j=(0,_react.useCallback)(async n=>{await C(n)},[C]);const $=(0,_react.useCallback)(async(n,te,re)=>{await A(n);await k(te);await f(re)},[A,k,f]);const ee=(0,_react.useCallback)(n=>{p(n);u(false)},[]);const R=()=>{return(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[l&&(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:l}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:"",children:d?(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Search,{value:h,onChangeText:Y,onCancel:()=>{g("");u(false)},onDismiss:()=>u(false),dismissOnBlur:true,placeholder:"Search by email, name, or ID...",iconSize:14,testID:"buoy-impersonate-search-input",inputProps:{onSubmitEditing:Z}}):(0,_jsxRuntime.jsx)(_sharedUi.TabSelector,{tabs:w,activeTab:o,onTabChange:ee})}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Actions,{children:o==="search"&&!d&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{testID:"buoy-impersonate-search-toggle",onPress:()=>u(true),style:styles.headerActionButton,children:(0,_jsxRuntime.jsx)(_sharedUi.Search,{size:14,color:_sharedUi.night.color.textSecondary})})})]})};if(!i){return null}const I=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.container,children:[o==="search"&&(0,_jsxRuntime.jsx)(_UserSearchView.UserSearchView,{currentUser:m,isActive:O,searchQuery:L,searchResults:z,isSearching:F,searchError:J,onSelectUser:j,onStopImpersonation:x,searchAvailable:!!e}),o==="history"&&(0,_jsxRuntime.jsx)(_ImpersonateHistoryList.ImpersonateHistoryList,{history:K,currentUserId:m?.id??null,onSelectUser:j,onStopImpersonation:x,onRemoveFromHistory:G,onClearHistory:X}),o==="settings"&&(0,_jsxRuntime.jsx)(_DataNukeSettings.DataNukeSettings,{headerKey:V,settings:W,showBanner:E,onSave:$,onShowBannerChange:f,detectionStatus:N})]});if(_){return(0,_jsxRuntime.jsx)(_sharedUi.TickProvider,{children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.inlineRoot,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.inlineHeader,..._sharedUi.windowDragProps,children:R()}),I]})})}return(0,_jsxRuntime.jsx)(_sharedUi.TickProvider,{children:(0,_jsxRuntime.jsx)(JsModal,{visible:i,onClose:y,onMinimize:S,header:{showToggleButton:true,customContent:R()},enablePersistence:true,initialMode:"bottomSheet",disableScrollWrapper:true,children:I})})}function ImpersonateToolView(i){return(0,_jsxRuntime.jsx)(ImpersonateModal,{...i,inline:true,visible:true,onClose:()=>{}})}const styles=_reactNative.StyleSheet.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:"transparent"},inlineHeader:{paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:_sharedUi.buoyColors.border,backgroundColor:"transparent"},container:{flex:1},headerActionButton:{width:28,height:28,borderRadius:6,backgroundColor:_sharedUi.night.color.buttonSurface,justifyContent:"center",alignItems:"center"}});
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.ImpersonateOverlay = ImpersonateOverlay;
|
|
7
|
-
var _react = _interopRequireDefault(require("react"));
|
|
8
|
-
var _ImpersonateBanner = require("./ImpersonateBanner");
|
|
9
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
10
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
-
/**
|
|
12
|
-
* ImpersonateOverlay
|
|
13
|
-
*
|
|
14
|
-
* Auto-rendered by FloatingDevTools (`@buoy-gg/core`) when
|
|
15
|
-
* `@buoy-gg/impersonate` is installed — the host does NOT need to mount
|
|
16
|
-
* anything. It renders the floating impersonation banner, which self-gates
|
|
17
|
-
* on store state (only visible while an impersonation session is active and
|
|
18
|
-
* `showBanner` is enabled), so it's safe to mount unconditionally.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
function ImpersonateOverlay() {
|
|
22
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_ImpersonateBanner.ImpersonateBanner, {
|
|
23
|
-
position: "top",
|
|
24
|
-
offset: 50
|
|
25
|
-
});
|
|
26
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.ImpersonateOverlay=ImpersonateOverlay;var _react=_interopRequireDefault(require("react"));var _ImpersonateBanner=require("./ImpersonateBanner");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function ImpersonateOverlay(){return(0,_jsxRuntime.jsx)(_ImpersonateBanner.ImpersonateBanner,{position:"top",offset:50})}
|
|
@@ -1,130 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.ImpersonateStatusBar = ImpersonateStatusBar;
|
|
7
|
-
var _react = _interopRequireDefault(require("react"));
|
|
8
|
-
var _reactNative = require("react-native");
|
|
9
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
|
-
var _UserAvatar = require("./UserAvatar");
|
|
11
|
-
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
-
/**
|
|
14
|
-
* ImpersonateStatusBar Component
|
|
15
|
-
*
|
|
16
|
-
* Shows the current impersonation status with user avatar, info and stop button.
|
|
17
|
-
* Displays at the top of the modal when impersonation is active.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
function ImpersonateStatusBar({
|
|
21
|
-
user,
|
|
22
|
-
onStopImpersonation
|
|
23
|
-
}) {
|
|
24
|
-
const displayName = user.displayName || user.email || user.id;
|
|
25
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
26
|
-
style: styles.container,
|
|
27
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
28
|
-
style: styles.userSection,
|
|
29
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_UserAvatar.UserAvatar, {
|
|
30
|
-
userId: user.id,
|
|
31
|
-
name: displayName,
|
|
32
|
-
size: "small",
|
|
33
|
-
showActiveIndicator: false
|
|
34
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
35
|
-
style: styles.userInfo,
|
|
36
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
37
|
-
style: styles.userName,
|
|
38
|
-
numberOfLines: 1,
|
|
39
|
-
children: displayName
|
|
40
|
-
})
|
|
41
|
-
})]
|
|
42
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
43
|
-
style: styles.statusPill,
|
|
44
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
45
|
-
style: styles.pulseDot
|
|
46
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
47
|
-
style: styles.statusText,
|
|
48
|
-
children: "Active"
|
|
49
|
-
})]
|
|
50
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
51
|
-
style: styles.stopButton,
|
|
52
|
-
onPress: onStopImpersonation,
|
|
53
|
-
activeOpacity: 0.7,
|
|
54
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
|
|
55
|
-
size: 12,
|
|
56
|
-
color: _sharedUi.night.color.danger
|
|
57
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
58
|
-
style: styles.stopButtonText,
|
|
59
|
-
children: "Stop"
|
|
60
|
-
})]
|
|
61
|
-
})]
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
const styles = _reactNative.StyleSheet.create({
|
|
65
|
-
container: {
|
|
66
|
-
flexDirection: "row",
|
|
67
|
-
alignItems: "center",
|
|
68
|
-
justifyContent: "space-between",
|
|
69
|
-
paddingVertical: 10,
|
|
70
|
-
paddingHorizontal: 16,
|
|
71
|
-
backgroundColor: _sharedUi.buoyColors.success + "10",
|
|
72
|
-
borderBottomWidth: 1,
|
|
73
|
-
borderBottomColor: _sharedUi.buoyColors.success + "25",
|
|
74
|
-
gap: 12
|
|
75
|
-
},
|
|
76
|
-
userSection: {
|
|
77
|
-
flexDirection: "row",
|
|
78
|
-
alignItems: "center",
|
|
79
|
-
flex: 1,
|
|
80
|
-
gap: 10,
|
|
81
|
-
minWidth: 0
|
|
82
|
-
},
|
|
83
|
-
userInfo: {
|
|
84
|
-
flex: 1,
|
|
85
|
-
minWidth: 0
|
|
86
|
-
},
|
|
87
|
-
userName: {
|
|
88
|
-
fontSize: 14,
|
|
89
|
-
fontWeight: "600",
|
|
90
|
-
color: _sharedUi.night.color.text
|
|
91
|
-
},
|
|
92
|
-
statusPill: {
|
|
93
|
-
flexDirection: "row",
|
|
94
|
-
alignItems: "center",
|
|
95
|
-
gap: 6,
|
|
96
|
-
backgroundColor: _sharedUi.buoyColors.success + "20",
|
|
97
|
-
paddingHorizontal: 10,
|
|
98
|
-
paddingVertical: 5,
|
|
99
|
-
borderRadius: 12
|
|
100
|
-
},
|
|
101
|
-
pulseDot: {
|
|
102
|
-
width: 6,
|
|
103
|
-
height: 6,
|
|
104
|
-
borderRadius: 3,
|
|
105
|
-
backgroundColor: _sharedUi.buoyColors.success
|
|
106
|
-
},
|
|
107
|
-
statusText: {
|
|
108
|
-
fontSize: 11,
|
|
109
|
-
fontWeight: "600",
|
|
110
|
-
color: _sharedUi.buoyColors.success,
|
|
111
|
-
textTransform: "uppercase",
|
|
112
|
-
letterSpacing: 0.5
|
|
113
|
-
},
|
|
114
|
-
stopButton: {
|
|
115
|
-
flexDirection: "row",
|
|
116
|
-
alignItems: "center",
|
|
117
|
-
gap: 4,
|
|
118
|
-
paddingHorizontal: 12,
|
|
119
|
-
paddingVertical: 6,
|
|
120
|
-
backgroundColor: _sharedUi.night.color.dangerSoft,
|
|
121
|
-
borderRadius: 6,
|
|
122
|
-
borderWidth: 1,
|
|
123
|
-
borderColor: _sharedUi.night.color.danger + "30"
|
|
124
|
-
},
|
|
125
|
-
stopButtonText: {
|
|
126
|
-
fontSize: 12,
|
|
127
|
-
fontWeight: "600",
|
|
128
|
-
color: _sharedUi.night.color.danger
|
|
129
|
-
}
|
|
130
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.ImpersonateStatusBar=ImpersonateStatusBar;var _react=_interopRequireDefault(require("react"));var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _UserAvatar=require("./UserAvatar");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}function ImpersonateStatusBar({user:e,onStopImpersonation:r}){const t=e.displayName||e.email||e.id;return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.container,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.userSection,children:[(0,_jsxRuntime.jsx)(_UserAvatar.UserAvatar,{userId:e.id,name:t,size:"small",showActiveIndicator:false}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.userInfo,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.userName,numberOfLines:1,children:t})})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.statusPill,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.pulseDot}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.statusText,children:"Active"})]}),(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.stopButton,onPress:r,activeOpacity:.7,children:[(0,_jsxRuntime.jsx)(_sharedUi.X,{size:12,color:_sharedUi.night.color.danger}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.stopButtonText,children:"Stop"})]})]})}const styles=_reactNative.StyleSheet.create({container:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",paddingVertical:10,paddingHorizontal:16,backgroundColor:_sharedUi.buoyColors.success+"10",borderBottomWidth:1,borderBottomColor:_sharedUi.buoyColors.success+"25",gap:12},userSection:{flexDirection:"row",alignItems:"center",flex:1,gap:10,minWidth:0},userInfo:{flex:1,minWidth:0},userName:{fontSize:14,fontWeight:"600",color:_sharedUi.night.color.text},statusPill:{flexDirection:"row",alignItems:"center",gap:6,backgroundColor:_sharedUi.buoyColors.success+"20",paddingHorizontal:10,paddingVertical:5,borderRadius:12},pulseDot:{width:6,height:6,borderRadius:3,backgroundColor:_sharedUi.buoyColors.success},statusText:{fontSize:11,fontWeight:"600",color:_sharedUi.buoyColors.success,textTransform:"uppercase",letterSpacing:.5},stopButton:{flexDirection:"row",alignItems:"center",gap:4,paddingHorizontal:12,paddingVertical:6,backgroundColor:_sharedUi.night.color.dangerSoft,borderRadius:6,borderWidth:1,borderColor:_sharedUi.night.color.danger+"30"},stopButtonText:{fontSize:12,fontWeight:"600",color:_sharedUi.night.color.danger}});
|
|
@@ -1,146 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.AVATAR_COLORS = void 0;
|
|
7
|
-
exports.UserAvatar = UserAvatar;
|
|
8
|
-
exports.getAvatarColor = getAvatarColor;
|
|
9
|
-
exports.getInitials = getInitials;
|
|
10
|
-
var _react = _interopRequireWildcard(require("react"));
|
|
11
|
-
var _reactNative = require("react-native");
|
|
12
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
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
|
-
* UserAvatar Component
|
|
17
|
-
*
|
|
18
|
-
* Displays a user avatar with initials and deterministic color.
|
|
19
|
-
* Supports size variants and active indicator.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
// Avatar background colors - deterministic based on user ID
|
|
23
|
-
const AVATAR_COLORS = exports.AVATAR_COLORS = ["#6366F1",
|
|
24
|
-
// Indigo
|
|
25
|
-
"#8B5CF6",
|
|
26
|
-
// Purple
|
|
27
|
-
"#EC4899",
|
|
28
|
-
// Pink
|
|
29
|
-
"#F43F5E",
|
|
30
|
-
// Rose
|
|
31
|
-
"#F97316",
|
|
32
|
-
// Orange
|
|
33
|
-
"#EAB308",
|
|
34
|
-
// Yellow
|
|
35
|
-
"#22C55E",
|
|
36
|
-
// Green
|
|
37
|
-
"#14B8A6",
|
|
38
|
-
// Teal
|
|
39
|
-
"#06B6D4",
|
|
40
|
-
// Cyan
|
|
41
|
-
"#3B82F6" // Blue
|
|
42
|
-
];
|
|
43
|
-
const SIZES = {
|
|
44
|
-
small: {
|
|
45
|
-
container: 24,
|
|
46
|
-
font: 10,
|
|
47
|
-
indicator: 8
|
|
48
|
-
},
|
|
49
|
-
medium: {
|
|
50
|
-
container: 36,
|
|
51
|
-
font: 14,
|
|
52
|
-
indicator: 10
|
|
53
|
-
},
|
|
54
|
-
large: {
|
|
55
|
-
container: 44,
|
|
56
|
-
font: 16,
|
|
57
|
-
indicator: 12
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
/**
|
|
61
|
-
* Get initials from a name or email
|
|
62
|
-
*/
|
|
63
|
-
function getInitials(name) {
|
|
64
|
-
if (!name) return "?";
|
|
65
|
-
|
|
66
|
-
// If it's an email, use first letter before @
|
|
67
|
-
if (name.includes("@")) {
|
|
68
|
-
return name.charAt(0).toUpperCase();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Split by spaces and get first letter of first two words
|
|
72
|
-
const parts = name.trim().split(/\s+/);
|
|
73
|
-
if (parts.length === 1) {
|
|
74
|
-
return parts[0].charAt(0).toUpperCase();
|
|
75
|
-
}
|
|
76
|
-
return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Get deterministic color based on user ID
|
|
81
|
-
*/
|
|
82
|
-
function getAvatarColor(userId) {
|
|
83
|
-
let hash = 0;
|
|
84
|
-
for (let i = 0; i < userId.length; i++) {
|
|
85
|
-
hash = userId.charCodeAt(i) + ((hash << 5) - hash);
|
|
86
|
-
}
|
|
87
|
-
return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];
|
|
88
|
-
}
|
|
89
|
-
function UserAvatar({
|
|
90
|
-
userId,
|
|
91
|
-
name,
|
|
92
|
-
size = "medium",
|
|
93
|
-
showActiveIndicator = false,
|
|
94
|
-
backgroundColor
|
|
95
|
-
}) {
|
|
96
|
-
const sizeConfig = SIZES[size];
|
|
97
|
-
const avatarColor = (0, _react.useMemo)(() => backgroundColor ?? getAvatarColor(userId), [userId, backgroundColor]);
|
|
98
|
-
const initials = (0, _react.useMemo)(() => getInitials(name), [name]);
|
|
99
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
100
|
-
style: styles.wrapper,
|
|
101
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
102
|
-
style: [styles.container, {
|
|
103
|
-
width: sizeConfig.container,
|
|
104
|
-
height: sizeConfig.container,
|
|
105
|
-
borderRadius: sizeConfig.container / 2,
|
|
106
|
-
backgroundColor: avatarColor
|
|
107
|
-
}],
|
|
108
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
109
|
-
style: [styles.initials, {
|
|
110
|
-
fontSize: sizeConfig.font
|
|
111
|
-
}],
|
|
112
|
-
children: initials
|
|
113
|
-
})
|
|
114
|
-
}), showActiveIndicator && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
115
|
-
style: [styles.indicator, {
|
|
116
|
-
width: sizeConfig.indicator,
|
|
117
|
-
height: sizeConfig.indicator,
|
|
118
|
-
borderRadius: sizeConfig.indicator / 2,
|
|
119
|
-
right: -1,
|
|
120
|
-
bottom: -1
|
|
121
|
-
}]
|
|
122
|
-
})]
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
const styles = _reactNative.StyleSheet.create({
|
|
126
|
-
wrapper: {
|
|
127
|
-
position: "relative"
|
|
128
|
-
},
|
|
129
|
-
container: {
|
|
130
|
-
alignItems: "center",
|
|
131
|
-
justifyContent: "center"
|
|
132
|
-
},
|
|
133
|
-
initials: {
|
|
134
|
-
color: "#FFFFFF",
|
|
135
|
-
fontWeight: "600",
|
|
136
|
-
letterSpacing: 0.5
|
|
137
|
-
},
|
|
138
|
-
indicator: {
|
|
139
|
-
position: "absolute",
|
|
140
|
-
backgroundColor: _sharedUi.buoyColors.success,
|
|
141
|
-
borderWidth: 2,
|
|
142
|
-
borderColor: _sharedUi.night.color.surface
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Export helper functions for reuse
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.AVATAR_COLORS=void 0;exports.UserAvatar=UserAvatar;exports.getAvatarColor=getAvatarColor;exports.getInitials=getInitials;var _react=_interopRequireWildcard(require("react"));var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireWildcard(r,e){if("function"==typeof WeakMap)var n=new WeakMap,l=new WeakMap;return(_interopRequireWildcard=function(t,i){if(!i&&t&&t.__esModule)return t;var o,a,c={__proto__:null,default:t};if(null===t||"object"!=typeof t&&"function"!=typeof t)return c;if(o=i?l:n){if(o.has(t))return o.get(t);o.set(t,c)}for(const s in t)"default"!==s&&{}.hasOwnProperty.call(t,s)&&((a=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(t,s))&&(a.get||a.set)?o(c,s,a):c[s]=t[s]);return c})(r,e)}const AVATAR_COLORS=exports.AVATAR_COLORS=["#6366F1","#8B5CF6","#EC4899","#F43F5E","#F97316","#EAB308","#22C55E","#14B8A6","#06B6D4","#3B82F6"];const SIZES={small:{container:24,font:10,indicator:8},medium:{container:36,font:14,indicator:10},large:{container:44,font:16,indicator:12}};function getInitials(r){if(!r)return"?";if(r.includes("@")){return r.charAt(0).toUpperCase()}const e=r.trim().split(/\s+/);if(e.length===1){return e[0].charAt(0).toUpperCase()}return(e[0].charAt(0)+e[e.length-1].charAt(0)).toUpperCase()}function getAvatarColor(r){let e=0;for(let n=0;n<r.length;n++){e=r.charCodeAt(n)+((e<<5)-e)}return AVATAR_COLORS[Math.abs(e)%AVATAR_COLORS.length]}function UserAvatar({userId:r,name:e,size:n="medium",showActiveIndicator:l=false,backgroundColor:t}){const i=SIZES[n];const o=(0,_react.useMemo)(()=>t??getAvatarColor(r),[r,t]);const a=(0,_react.useMemo)(()=>getInitials(e),[e]);return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.wrapper,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:[styles.container,{width:i.container,height:i.container,borderRadius:i.container/2,backgroundColor:o}],children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.initials,{fontSize:i.font}],children:a})}),l&&(0,_jsxRuntime.jsx)(_reactNative.View,{style:[styles.indicator,{width:i.indicator,height:i.indicator,borderRadius:i.indicator/2,right:-1,bottom:-1}]})]})}const styles=_reactNative.StyleSheet.create({wrapper:{position:"relative"},container:{alignItems:"center",justifyContent:"center"},initials:{color:"#FFFFFF",fontWeight:"600",letterSpacing:.5},indicator:{position:"absolute",backgroundColor:_sharedUi.buoyColors.success,borderWidth:2,borderColor:_sharedUi.night.color.surface}});
|