@oxyhq/services 6.9.32 → 6.9.33
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/ui/components/FollowButton.js +13 -65
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +12 -64
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/typescript/commonjs/ui/components/FollowButton.d.ts +0 -11
- package/lib/typescript/commonjs/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/FollowButton.d.ts +0 -11
- package/lib/typescript/module/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/nativewind-env.d.ts +1 -0
- package/package.json +8 -2
- package/src/nativewind-env.d.ts +1 -0
- package/src/ui/components/FollowButton.tsx +14 -50
|
@@ -10,22 +10,9 @@ var _OxyContext = require("../context/OxyContext.js");
|
|
|
10
10
|
var _fonts = require("../styles/fonts.js");
|
|
11
11
|
var _sonner = require("../../lib/sonner");
|
|
12
12
|
var _useFollow = require("../hooks/useFollow.js");
|
|
13
|
-
var
|
|
13
|
+
var _theme = require("@oxyhq/bloom/theme");
|
|
14
14
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
15
15
|
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); }
|
|
16
|
-
/**
|
|
17
|
-
* Inner component that handles all hooks and rendering.
|
|
18
|
-
*
|
|
19
|
-
* Separated from the outer wrapper to avoid a Rules of Hooks violation.
|
|
20
|
-
* The outer wrapper handles the auth/self-follow guard and returns null
|
|
21
|
-
* before any hooks are called. This inner component always renders
|
|
22
|
-
* (all hooks are called unconditionally).
|
|
23
|
-
*
|
|
24
|
-
* Receives oxyServices as a prop instead of calling useOxy(), so it does
|
|
25
|
-
* not subscribe to the OxyContext. This is critical in list contexts where
|
|
26
|
-
* N buttons would all re-render on any context change (session socket events,
|
|
27
|
-
* token refreshes, etc.).
|
|
28
|
-
*/
|
|
29
16
|
const FollowButtonInner = /*#__PURE__*/(0, _react.memo)(function FollowButtonInner({
|
|
30
17
|
userId,
|
|
31
18
|
oxyServices,
|
|
@@ -36,13 +23,11 @@ const FollowButtonInner = /*#__PURE__*/(0, _react.memo)(function FollowButtonInn
|
|
|
36
23
|
textStyle,
|
|
37
24
|
disabled = false,
|
|
38
25
|
showLoadingState = true,
|
|
39
|
-
preventParentActions = true
|
|
40
|
-
theme: _theme = 'light'
|
|
26
|
+
preventParentActions = true
|
|
41
27
|
}) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Uses granular Zustand selectors — only re-renders when THIS user's data changes
|
|
28
|
+
const {
|
|
29
|
+
colors
|
|
30
|
+
} = (0, _theme.useTheme)();
|
|
46
31
|
const {
|
|
47
32
|
isFollowing,
|
|
48
33
|
isLoading,
|
|
@@ -50,80 +35,47 @@ const FollowButtonInner = /*#__PURE__*/(0, _react.memo)(function FollowButtonInn
|
|
|
50
35
|
setFollowStatus,
|
|
51
36
|
fetchStatus
|
|
52
37
|
} = (0, _useFollow.useFollowForButton)(userId, oxyServices);
|
|
53
|
-
|
|
54
|
-
// Stable press handler — depends on primitives only
|
|
55
38
|
const handlePress = (0, _react.useCallback)(async event => {
|
|
56
|
-
if (preventParentActions && event
|
|
39
|
+
if (preventParentActions && event?.preventDefault) {
|
|
57
40
|
event.preventDefault();
|
|
58
41
|
event.stopPropagation?.();
|
|
59
42
|
}
|
|
60
43
|
if (disabled || isLoading) return;
|
|
61
44
|
try {
|
|
62
45
|
await toggleFollow();
|
|
63
|
-
|
|
46
|
+
onFollowChange?.(!isFollowing);
|
|
64
47
|
} catch (err) {
|
|
65
48
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
66
49
|
_sonner.toast.error(error.message || 'Failed to update follow status');
|
|
67
50
|
}
|
|
68
51
|
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
69
|
-
|
|
70
|
-
// Set initial follow status on mount if provided and not already set
|
|
71
52
|
(0, _react.useEffect)(() => {
|
|
72
53
|
if (userId && !isFollowing && initiallyFollowing) {
|
|
73
54
|
setFollowStatus(initiallyFollowing);
|
|
74
55
|
}
|
|
75
|
-
// Intentional: only run on mount with initial values
|
|
76
56
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
57
|
}, [userId, initiallyFollowing]);
|
|
78
|
-
|
|
79
|
-
// Fetch latest follow status from backend on mount
|
|
80
58
|
(0, _react.useEffect)(() => {
|
|
81
|
-
if (userId)
|
|
82
|
-
fetchStatus();
|
|
83
|
-
}
|
|
59
|
+
if (userId) fetchStatus();
|
|
84
60
|
}, [userId, fetchStatus]);
|
|
85
|
-
|
|
86
|
-
// Colors from bloom theme — follows NativeWind convention via useTheme()
|
|
87
|
-
// Not following: bg-primary, border-primary, text white
|
|
88
|
-
// Following: bg-background, border-border, text-foreground
|
|
89
|
-
const buttonColorStyle = isFollowing ? {
|
|
90
|
-
backgroundColor: colors.background,
|
|
91
|
-
borderColor: colors.border
|
|
92
|
-
} : {
|
|
93
|
-
backgroundColor: colors.primary,
|
|
94
|
-
borderColor: colors.primary
|
|
95
|
-
};
|
|
96
|
-
const textColor = isFollowing ? colors.text : '#FFFFFF';
|
|
97
61
|
const baseButtonStyle = getBaseButtonStyle(size, style);
|
|
98
62
|
const baseTextStyle = getBaseTextStyle(size, textStyle);
|
|
99
63
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
100
|
-
|
|
64
|
+
className: isFollowing ? 'bg-background border-border' : 'bg-primary border-primary',
|
|
65
|
+
style: baseButtonStyle,
|
|
101
66
|
onPress: handlePress,
|
|
102
67
|
disabled: disabled || isLoading,
|
|
103
68
|
activeOpacity: 0.8,
|
|
104
69
|
children: showLoadingState && isLoading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
|
|
105
70
|
size: "small",
|
|
106
|
-
color:
|
|
71
|
+
color: isFollowing ? colors.text : '#FFFFFF'
|
|
107
72
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}],
|
|
73
|
+
className: isFollowing ? 'text-foreground' : 'text-primary-foreground',
|
|
74
|
+
style: baseTextStyle,
|
|
111
75
|
children: isFollowing ? 'Following' : 'Follow'
|
|
112
76
|
})
|
|
113
77
|
});
|
|
114
78
|
});
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
118
|
-
*
|
|
119
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
120
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
121
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
122
|
-
* to the full OxyContext.
|
|
123
|
-
*
|
|
124
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
125
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
126
|
-
*/
|
|
127
79
|
const FollowButton = props => {
|
|
128
80
|
const {
|
|
129
81
|
oxyServices,
|
|
@@ -132,8 +84,6 @@ const FollowButton = props => {
|
|
|
132
84
|
} = (0, _OxyContext.useOxy)();
|
|
133
85
|
const currentUserId = currentUser?.id ? String(currentUser.id).trim() : '';
|
|
134
86
|
const targetUserId = props.userId ? String(props.userId).trim() : '';
|
|
135
|
-
|
|
136
|
-
// Don't render if not authenticated or viewing own profile
|
|
137
87
|
if (!isAuthenticated || !targetUserId || currentUserId && currentUserId === targetUserId) {
|
|
138
88
|
return null;
|
|
139
89
|
}
|
|
@@ -143,8 +93,6 @@ const FollowButton = props => {
|
|
|
143
93
|
oxyServices: oxyServices
|
|
144
94
|
});
|
|
145
95
|
};
|
|
146
|
-
|
|
147
|
-
// Pure helper functions (no hooks, no state) extracted outside the component
|
|
148
96
|
exports.FollowButton = FollowButton;
|
|
149
97
|
function getBaseButtonStyle(size, style) {
|
|
150
98
|
const baseStyle = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_OxyContext","_fonts","_sonner","_useFollow","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_OxyContext","_fonts","_sonner","_useFollow","_theme","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","FollowButtonInner","memo","userId","oxyServices","initiallyFollowing","size","onFollowChange","style","textStyle","disabled","showLoadingState","preventParentActions","colors","useTheme","isFollowing","isLoading","toggleFollow","setFollowStatus","fetchStatus","useFollowForButton","handlePress","useCallback","event","preventDefault","stopPropagation","err","error","Error","String","toast","message","useEffect","baseButtonStyle","getBaseButtonStyle","baseTextStyle","getBaseTextStyle","jsx","TouchableOpacity","className","onPress","activeOpacity","children","ActivityIndicator","color","text","Text","FollowButton","props","isAuthenticated","user","currentUser","useOxy","currentUserId","id","trim","targetUserId","exports","baseStyle","flexDirection","alignItems","justifyContent","borderWidth","Platform","select","web","shadowColor","shadowOffset","width","height","shadowOpacity","shadowRadius","elevation","sizeStyle","paddingVertical","paddingHorizontal","minWidth","borderRadius","fontFamily","fontFamilies","interSemiBold","fontWeight","sizeTextStyle","fontSize","_default"],"sourceRoot":"../../../../src","sources":["ui/components/FollowButton.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -8,19 +8,6 @@ import { toast } from '../../lib/sonner';
|
|
|
8
8
|
import { useFollowForButton } from "../hooks/useFollow.js";
|
|
9
9
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
10
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
-
/**
|
|
12
|
-
* Inner component that handles all hooks and rendering.
|
|
13
|
-
*
|
|
14
|
-
* Separated from the outer wrapper to avoid a Rules of Hooks violation.
|
|
15
|
-
* The outer wrapper handles the auth/self-follow guard and returns null
|
|
16
|
-
* before any hooks are called. This inner component always renders
|
|
17
|
-
* (all hooks are called unconditionally).
|
|
18
|
-
*
|
|
19
|
-
* Receives oxyServices as a prop instead of calling useOxy(), so it does
|
|
20
|
-
* not subscribe to the OxyContext. This is critical in list contexts where
|
|
21
|
-
* N buttons would all re-render on any context change (session socket events,
|
|
22
|
-
* token refreshes, etc.).
|
|
23
|
-
*/
|
|
24
11
|
const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
25
12
|
userId,
|
|
26
13
|
oxyServices,
|
|
@@ -31,13 +18,11 @@ const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
|
31
18
|
textStyle,
|
|
32
19
|
disabled = false,
|
|
33
20
|
showLoadingState = true,
|
|
34
|
-
preventParentActions = true
|
|
35
|
-
theme: _theme = 'light'
|
|
21
|
+
preventParentActions = true
|
|
36
22
|
}) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// Uses granular Zustand selectors — only re-renders when THIS user's data changes
|
|
23
|
+
const {
|
|
24
|
+
colors
|
|
25
|
+
} = useTheme();
|
|
41
26
|
const {
|
|
42
27
|
isFollowing,
|
|
43
28
|
isLoading,
|
|
@@ -45,80 +30,47 @@ const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
|
45
30
|
setFollowStatus,
|
|
46
31
|
fetchStatus
|
|
47
32
|
} = useFollowForButton(userId, oxyServices);
|
|
48
|
-
|
|
49
|
-
// Stable press handler — depends on primitives only
|
|
50
33
|
const handlePress = useCallback(async event => {
|
|
51
|
-
if (preventParentActions && event
|
|
34
|
+
if (preventParentActions && event?.preventDefault) {
|
|
52
35
|
event.preventDefault();
|
|
53
36
|
event.stopPropagation?.();
|
|
54
37
|
}
|
|
55
38
|
if (disabled || isLoading) return;
|
|
56
39
|
try {
|
|
57
40
|
await toggleFollow();
|
|
58
|
-
|
|
41
|
+
onFollowChange?.(!isFollowing);
|
|
59
42
|
} catch (err) {
|
|
60
43
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
61
44
|
toast.error(error.message || 'Failed to update follow status');
|
|
62
45
|
}
|
|
63
46
|
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
64
|
-
|
|
65
|
-
// Set initial follow status on mount if provided and not already set
|
|
66
47
|
useEffect(() => {
|
|
67
48
|
if (userId && !isFollowing && initiallyFollowing) {
|
|
68
49
|
setFollowStatus(initiallyFollowing);
|
|
69
50
|
}
|
|
70
|
-
// Intentional: only run on mount with initial values
|
|
71
51
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
52
|
}, [userId, initiallyFollowing]);
|
|
73
|
-
|
|
74
|
-
// Fetch latest follow status from backend on mount
|
|
75
53
|
useEffect(() => {
|
|
76
|
-
if (userId)
|
|
77
|
-
fetchStatus();
|
|
78
|
-
}
|
|
54
|
+
if (userId) fetchStatus();
|
|
79
55
|
}, [userId, fetchStatus]);
|
|
80
|
-
|
|
81
|
-
// Colors from bloom theme — follows NativeWind convention via useTheme()
|
|
82
|
-
// Not following: bg-primary, border-primary, text white
|
|
83
|
-
// Following: bg-background, border-border, text-foreground
|
|
84
|
-
const buttonColorStyle = isFollowing ? {
|
|
85
|
-
backgroundColor: colors.background,
|
|
86
|
-
borderColor: colors.border
|
|
87
|
-
} : {
|
|
88
|
-
backgroundColor: colors.primary,
|
|
89
|
-
borderColor: colors.primary
|
|
90
|
-
};
|
|
91
|
-
const textColor = isFollowing ? colors.text : '#FFFFFF';
|
|
92
56
|
const baseButtonStyle = getBaseButtonStyle(size, style);
|
|
93
57
|
const baseTextStyle = getBaseTextStyle(size, textStyle);
|
|
94
58
|
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
95
|
-
|
|
59
|
+
className: isFollowing ? 'bg-background border-border' : 'bg-primary border-primary',
|
|
60
|
+
style: baseButtonStyle,
|
|
96
61
|
onPress: handlePress,
|
|
97
62
|
disabled: disabled || isLoading,
|
|
98
63
|
activeOpacity: 0.8,
|
|
99
64
|
children: showLoadingState && isLoading ? /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
100
65
|
size: "small",
|
|
101
|
-
color:
|
|
66
|
+
color: isFollowing ? colors.text : '#FFFFFF'
|
|
102
67
|
}) : /*#__PURE__*/_jsx(Text, {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}],
|
|
68
|
+
className: isFollowing ? 'text-foreground' : 'text-primary-foreground',
|
|
69
|
+
style: baseTextStyle,
|
|
106
70
|
children: isFollowing ? 'Following' : 'Follow'
|
|
107
71
|
})
|
|
108
72
|
});
|
|
109
73
|
});
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
113
|
-
*
|
|
114
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
115
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
116
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
117
|
-
* to the full OxyContext.
|
|
118
|
-
*
|
|
119
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
120
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
121
|
-
*/
|
|
122
74
|
const FollowButton = props => {
|
|
123
75
|
const {
|
|
124
76
|
oxyServices,
|
|
@@ -127,8 +79,6 @@ const FollowButton = props => {
|
|
|
127
79
|
} = useOxy();
|
|
128
80
|
const currentUserId = currentUser?.id ? String(currentUser.id).trim() : '';
|
|
129
81
|
const targetUserId = props.userId ? String(props.userId).trim() : '';
|
|
130
|
-
|
|
131
|
-
// Don't render if not authenticated or viewing own profile
|
|
132
82
|
if (!isAuthenticated || !targetUserId || currentUserId && currentUserId === targetUserId) {
|
|
133
83
|
return null;
|
|
134
84
|
}
|
|
@@ -138,8 +88,6 @@ const FollowButton = props => {
|
|
|
138
88
|
oxyServices: oxyServices
|
|
139
89
|
});
|
|
140
90
|
};
|
|
141
|
-
|
|
142
|
-
// Pure helper functions (no hooks, no state) extracted outside the component
|
|
143
91
|
function getBaseButtonStyle(size, style) {
|
|
144
92
|
const baseStyle = {
|
|
145
93
|
flexDirection: 'row',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useEffect","useCallback","memo","TouchableOpacity","Text","Platform","ActivityIndicator","useOxy","fontFamilies","toast","useFollowForButton","useTheme","jsx","_jsx","FollowButtonInner","userId","oxyServices","initiallyFollowing","size","onFollowChange","style","textStyle","disabled","showLoadingState","preventParentActions","
|
|
1
|
+
{"version":3,"names":["React","useEffect","useCallback","memo","TouchableOpacity","Text","Platform","ActivityIndicator","useOxy","fontFamilies","toast","useFollowForButton","useTheme","jsx","_jsx","FollowButtonInner","userId","oxyServices","initiallyFollowing","size","onFollowChange","style","textStyle","disabled","showLoadingState","preventParentActions","colors","isFollowing","isLoading","toggleFollow","setFollowStatus","fetchStatus","handlePress","event","preventDefault","stopPropagation","err","error","Error","String","message","baseButtonStyle","getBaseButtonStyle","baseTextStyle","getBaseTextStyle","className","onPress","activeOpacity","children","color","text","FollowButton","props","isAuthenticated","user","currentUser","currentUserId","id","trim","targetUserId","baseStyle","flexDirection","alignItems","justifyContent","borderWidth","select","web","default","shadowColor","shadowOffset","width","height","shadowOpacity","shadowRadius","elevation","sizeStyle","paddingVertical","paddingHorizontal","minWidth","borderRadius","fontFamily","interSemiBold","fontWeight","sizeTextStyle","fontSize"],"sourceRoot":"../../../../src","sources":["ui/components/FollowButton.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -12,17 +12,6 @@ export interface FollowButtonProps {
|
|
|
12
12
|
preventParentActions?: boolean;
|
|
13
13
|
theme?: 'light' | 'dark';
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
17
|
-
*
|
|
18
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
19
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
20
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
21
|
-
* to the full OxyContext.
|
|
22
|
-
*
|
|
23
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
24
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
25
|
-
*/
|
|
26
15
|
declare const FollowButton: React.FC<FollowButtonProps>;
|
|
27
16
|
export { FollowButton };
|
|
28
17
|
export default FollowButton;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FollowButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/FollowButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EAGf,MAAM,cAAc,CAAC;AAQtB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"FollowButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/FollowButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EAGf,MAAM,cAAc,CAAC;AAQtB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAkFD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAiB7C,CAAC;AAkDF,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAe,YAAY,CAAC"}
|
|
@@ -12,17 +12,6 @@ export interface FollowButtonProps {
|
|
|
12
12
|
preventParentActions?: boolean;
|
|
13
13
|
theme?: 'light' | 'dark';
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
17
|
-
*
|
|
18
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
19
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
20
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
21
|
-
* to the full OxyContext.
|
|
22
|
-
*
|
|
23
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
24
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
25
|
-
*/
|
|
26
15
|
declare const FollowButton: React.FC<FollowButtonProps>;
|
|
27
16
|
export { FollowButton };
|
|
28
17
|
export default FollowButton;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FollowButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/FollowButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EAGf,MAAM,cAAc,CAAC;AAQtB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"FollowButton.d.ts","sourceRoot":"","sources":["../../../../../src/ui/components/FollowButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,SAAS,EAGf,MAAM,cAAc,CAAC;AAQtB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAkFD,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAiB7C,CAAC;AAkDF,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="nativewind/types" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "6.9.
|
|
3
|
+
"version": "6.9.33",
|
|
4
4
|
"description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -94,6 +94,8 @@
|
|
|
94
94
|
"zustand": "^5.0.9"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
+
"nativewind": "5.0.0-preview.3",
|
|
98
|
+
"react-native-css": "^3.0.0",
|
|
97
99
|
"@biomejs/biome": "^1.9.4",
|
|
98
100
|
"@commitlint/cli": "^17.6.5",
|
|
99
101
|
"@commitlint/config-conventional": "^17.6.5",
|
|
@@ -135,9 +137,13 @@
|
|
|
135
137
|
"react-native-gesture-handler": "~2.30.0",
|
|
136
138
|
"react-native-reanimated": ">=4.2.0",
|
|
137
139
|
"react-native-safe-area-context": "~5.6.0",
|
|
138
|
-
"react-native-svg": ">=13.0.0"
|
|
140
|
+
"react-native-svg": ">=13.0.0",
|
|
141
|
+
"nativewind": ">=4.0.0"
|
|
139
142
|
},
|
|
140
143
|
"peerDependenciesMeta": {
|
|
144
|
+
"nativewind": {
|
|
145
|
+
"optional": true
|
|
146
|
+
},
|
|
141
147
|
"@expo/vector-icons": {
|
|
142
148
|
"optional": true
|
|
143
149
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="nativewind/types" />
|
|
@@ -28,19 +28,6 @@ export interface FollowButtonProps {
|
|
|
28
28
|
theme?: 'light' | 'dark';
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* Inner component that handles all hooks and rendering.
|
|
33
|
-
*
|
|
34
|
-
* Separated from the outer wrapper to avoid a Rules of Hooks violation.
|
|
35
|
-
* The outer wrapper handles the auth/self-follow guard and returns null
|
|
36
|
-
* before any hooks are called. This inner component always renders
|
|
37
|
-
* (all hooks are called unconditionally).
|
|
38
|
-
*
|
|
39
|
-
* Receives oxyServices as a prop instead of calling useOxy(), so it does
|
|
40
|
-
* not subscribe to the OxyContext. This is critical in list contexts where
|
|
41
|
-
* N buttons would all re-render on any context change (session socket events,
|
|
42
|
-
* token refreshes, etc.).
|
|
43
|
-
*/
|
|
44
31
|
const FollowButtonInner = memo(function FollowButtonInner({
|
|
45
32
|
userId,
|
|
46
33
|
oxyServices,
|
|
@@ -52,12 +39,9 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
52
39
|
disabled = false,
|
|
53
40
|
showLoadingState = true,
|
|
54
41
|
preventParentActions = true,
|
|
55
|
-
theme: _theme = 'light',
|
|
56
42
|
}: FollowButtonProps & { oxyServices: OxyServices }) {
|
|
57
|
-
const
|
|
58
|
-
const colors = bloomTheme.colors;
|
|
43
|
+
const { colors } = useTheme();
|
|
59
44
|
|
|
60
|
-
// Uses granular Zustand selectors — only re-renders when THIS user's data changes
|
|
61
45
|
const {
|
|
62
46
|
isFollowing,
|
|
63
47
|
isLoading,
|
|
@@ -66,9 +50,8 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
66
50
|
fetchStatus,
|
|
67
51
|
} = useFollowForButton(userId, oxyServices);
|
|
68
52
|
|
|
69
|
-
// Stable press handler — depends on primitives only
|
|
70
53
|
const handlePress = useCallback(async (event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
71
|
-
if (preventParentActions && event
|
|
54
|
+
if (preventParentActions && event?.preventDefault) {
|
|
72
55
|
event.preventDefault();
|
|
73
56
|
event.stopPropagation?.();
|
|
74
57
|
}
|
|
@@ -76,43 +59,34 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
76
59
|
|
|
77
60
|
try {
|
|
78
61
|
await toggleFollow();
|
|
79
|
-
|
|
62
|
+
onFollowChange?.(!isFollowing);
|
|
80
63
|
} catch (err: unknown) {
|
|
81
64
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
82
65
|
toast.error(error.message || 'Failed to update follow status');
|
|
83
66
|
}
|
|
84
67
|
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
85
68
|
|
|
86
|
-
// Set initial follow status on mount if provided and not already set
|
|
87
69
|
useEffect(() => {
|
|
88
70
|
if (userId && !isFollowing && initiallyFollowing) {
|
|
89
71
|
setFollowStatus(initiallyFollowing);
|
|
90
72
|
}
|
|
91
|
-
// Intentional: only run on mount with initial values
|
|
92
73
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
74
|
}, [userId, initiallyFollowing]);
|
|
94
75
|
|
|
95
|
-
// Fetch latest follow status from backend on mount
|
|
96
76
|
useEffect(() => {
|
|
97
|
-
if (userId)
|
|
98
|
-
fetchStatus();
|
|
99
|
-
}
|
|
77
|
+
if (userId) fetchStatus();
|
|
100
78
|
}, [userId, fetchStatus]);
|
|
101
79
|
|
|
102
|
-
// Colors from bloom theme — follows NativeWind convention via useTheme()
|
|
103
|
-
// Not following: bg-primary, border-primary, text white
|
|
104
|
-
// Following: bg-background, border-border, text-foreground
|
|
105
|
-
const buttonColorStyle: ViewStyle = isFollowing
|
|
106
|
-
? { backgroundColor: colors.background, borderColor: colors.border }
|
|
107
|
-
: { backgroundColor: colors.primary, borderColor: colors.primary };
|
|
108
|
-
const textColor = isFollowing ? colors.text : '#FFFFFF';
|
|
109
|
-
|
|
110
80
|
const baseButtonStyle = getBaseButtonStyle(size, style);
|
|
111
81
|
const baseTextStyle = getBaseTextStyle(size, textStyle);
|
|
112
82
|
|
|
113
83
|
return (
|
|
114
84
|
<TouchableOpacity
|
|
115
|
-
|
|
85
|
+
className={isFollowing
|
|
86
|
+
? 'bg-background border-border'
|
|
87
|
+
: 'bg-primary border-primary'
|
|
88
|
+
}
|
|
89
|
+
style={baseButtonStyle}
|
|
116
90
|
onPress={handlePress}
|
|
117
91
|
disabled={disabled || isLoading}
|
|
118
92
|
activeOpacity={0.8}
|
|
@@ -120,10 +94,13 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
120
94
|
{showLoadingState && isLoading ? (
|
|
121
95
|
<ActivityIndicator
|
|
122
96
|
size="small"
|
|
123
|
-
color={
|
|
97
|
+
color={isFollowing ? colors.text : '#FFFFFF'}
|
|
124
98
|
/>
|
|
125
99
|
) : (
|
|
126
|
-
<Text
|
|
100
|
+
<Text
|
|
101
|
+
className={isFollowing ? 'text-foreground' : 'text-primary-foreground'}
|
|
102
|
+
style={baseTextStyle}
|
|
103
|
+
>
|
|
127
104
|
{isFollowing ? 'Following' : 'Follow'}
|
|
128
105
|
</Text>
|
|
129
106
|
)}
|
|
@@ -131,24 +108,12 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
131
108
|
);
|
|
132
109
|
});
|
|
133
110
|
|
|
134
|
-
/**
|
|
135
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
136
|
-
*
|
|
137
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
138
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
139
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
140
|
-
* to the full OxyContext.
|
|
141
|
-
*
|
|
142
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
143
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
144
|
-
*/
|
|
145
111
|
const FollowButton: React.FC<FollowButtonProps> = (props) => {
|
|
146
112
|
const { oxyServices, isAuthenticated, user: currentUser } = useOxy();
|
|
147
113
|
|
|
148
114
|
const currentUserId = currentUser?.id ? String(currentUser.id).trim() : '';
|
|
149
115
|
const targetUserId = props.userId ? String(props.userId).trim() : '';
|
|
150
116
|
|
|
151
|
-
// Don't render if not authenticated or viewing own profile
|
|
152
117
|
if (!isAuthenticated || !targetUserId || (currentUserId && currentUserId === targetUserId)) {
|
|
153
118
|
return null;
|
|
154
119
|
}
|
|
@@ -162,7 +127,6 @@ const FollowButton: React.FC<FollowButtonProps> = (props) => {
|
|
|
162
127
|
);
|
|
163
128
|
};
|
|
164
129
|
|
|
165
|
-
// Pure helper functions (no hooks, no state) extracted outside the component
|
|
166
130
|
function getBaseButtonStyle(size: string, style?: StyleProp<ViewStyle>): StyleProp<ViewStyle> {
|
|
167
131
|
const baseStyle: ViewStyle = {
|
|
168
132
|
flexDirection: 'row',
|