@oxyhq/services 6.9.31 → 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 +15 -92
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +14 -91
- 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 +17 -77
|
@@ -6,29 +6,13 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.default = exports.FollowButton = void 0;
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
|
-
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
|
|
10
9
|
var _OxyContext = require("../context/OxyContext.js");
|
|
11
10
|
var _fonts = require("../styles/fonts.js");
|
|
12
11
|
var _sonner = require("../../lib/sonner");
|
|
13
12
|
var _useFollow = require("../hooks/useFollow.js");
|
|
14
|
-
var
|
|
13
|
+
var _theme = require("@oxyhq/bloom/theme");
|
|
15
14
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
16
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); }
|
|
17
|
-
// Create animated TouchableOpacity
|
|
18
|
-
const AnimatedTouchableOpacity = _reactNativeReanimated.default.createAnimatedComponent(_reactNative.TouchableOpacity);
|
|
19
|
-
/**
|
|
20
|
-
* Inner component that handles all hooks and rendering.
|
|
21
|
-
*
|
|
22
|
-
* Separated from the outer wrapper to avoid a Rules of Hooks violation.
|
|
23
|
-
* The outer wrapper handles the auth/self-follow guard and returns null
|
|
24
|
-
* before any hooks are called. This inner component always renders
|
|
25
|
-
* (all hooks are called unconditionally).
|
|
26
|
-
*
|
|
27
|
-
* Receives oxyServices as a prop instead of calling useOxy(), so it does
|
|
28
|
-
* not subscribe to the OxyContext. This is critical in list contexts where
|
|
29
|
-
* N buttons would all re-render on any context change (session socket events,
|
|
30
|
-
* token refreshes, etc.).
|
|
31
|
-
*/
|
|
32
16
|
const FollowButtonInner = /*#__PURE__*/(0, _react.memo)(function FollowButtonInner({
|
|
33
17
|
userId,
|
|
34
18
|
oxyServices,
|
|
@@ -39,13 +23,11 @@ const FollowButtonInner = /*#__PURE__*/(0, _react.memo)(function FollowButtonInn
|
|
|
39
23
|
textStyle,
|
|
40
24
|
disabled = false,
|
|
41
25
|
showLoadingState = true,
|
|
42
|
-
preventParentActions = true
|
|
43
|
-
theme: _theme = 'light'
|
|
26
|
+
preventParentActions = true
|
|
44
27
|
}) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Uses granular Zustand selectors — only re-renders when THIS user's data changes
|
|
28
|
+
const {
|
|
29
|
+
colors
|
|
30
|
+
} = (0, _theme.useTheme)();
|
|
49
31
|
const {
|
|
50
32
|
isFollowing,
|
|
51
33
|
isLoading,
|
|
@@ -53,102 +35,47 @@ const FollowButtonInner = /*#__PURE__*/(0, _react.memo)(function FollowButtonInn
|
|
|
53
35
|
setFollowStatus,
|
|
54
36
|
fetchStatus
|
|
55
37
|
} = (0, _useFollow.useFollowForButton)(userId, oxyServices);
|
|
56
|
-
|
|
57
|
-
// Animation values
|
|
58
|
-
const scale = (0, _reactNativeReanimated.useSharedValue)(1);
|
|
59
|
-
|
|
60
|
-
// Stable press handler — depends on primitives only
|
|
61
38
|
const handlePress = (0, _react.useCallback)(async event => {
|
|
62
|
-
if (preventParentActions && event
|
|
39
|
+
if (preventParentActions && event?.preventDefault) {
|
|
63
40
|
event.preventDefault();
|
|
64
41
|
event.stopPropagation?.();
|
|
65
42
|
}
|
|
66
43
|
if (disabled || isLoading) return;
|
|
67
|
-
|
|
68
|
-
// Press animation
|
|
69
|
-
scale.value = (0, _reactNativeReanimated.withTiming)(0.95, {
|
|
70
|
-
duration: 100
|
|
71
|
-
}, finished => {
|
|
72
|
-
if (finished) {
|
|
73
|
-
scale.value = (0, _reactNativeReanimated.withSpring)(1, {
|
|
74
|
-
damping: 15,
|
|
75
|
-
stiffness: 200
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
44
|
try {
|
|
80
45
|
await toggleFollow();
|
|
81
|
-
|
|
46
|
+
onFollowChange?.(!isFollowing);
|
|
82
47
|
} catch (err) {
|
|
83
48
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
84
49
|
_sonner.toast.error(error.message || 'Failed to update follow status');
|
|
85
50
|
}
|
|
86
|
-
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions
|
|
87
|
-
|
|
88
|
-
// Set initial follow status on mount if provided and not already set
|
|
51
|
+
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
89
52
|
(0, _react.useEffect)(() => {
|
|
90
53
|
if (userId && !isFollowing && initiallyFollowing) {
|
|
91
54
|
setFollowStatus(initiallyFollowing);
|
|
92
55
|
}
|
|
93
|
-
// Intentional: only run on mount with initial values
|
|
94
56
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
95
57
|
}, [userId, initiallyFollowing]);
|
|
96
|
-
|
|
97
|
-
// Fetch latest follow status from backend on mount
|
|
98
58
|
(0, _react.useEffect)(() => {
|
|
99
|
-
if (userId)
|
|
100
|
-
fetchStatus();
|
|
101
|
-
}
|
|
59
|
+
if (userId) fetchStatus();
|
|
102
60
|
}, [userId, fetchStatus]);
|
|
103
|
-
|
|
104
|
-
// Scale-only animated style
|
|
105
|
-
const animatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => ({
|
|
106
|
-
transform: [{
|
|
107
|
-
scale: scale.value
|
|
108
|
-
}]
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
|
-
// Colors from bloom theme — follows NativeWind convention via useTheme()
|
|
112
|
-
// Not following: bg-primary, border-primary, text white
|
|
113
|
-
// Following: bg-background, border-border, text-foreground
|
|
114
|
-
const buttonColorStyle = isFollowing ? {
|
|
115
|
-
backgroundColor: colors.background,
|
|
116
|
-
borderColor: colors.border
|
|
117
|
-
} : {
|
|
118
|
-
backgroundColor: colors.primary,
|
|
119
|
-
borderColor: colors.primary
|
|
120
|
-
};
|
|
121
|
-
const textColor = isFollowing ? colors.text : '#FFFFFF';
|
|
122
61
|
const baseButtonStyle = getBaseButtonStyle(size, style);
|
|
123
62
|
const baseTextStyle = getBaseTextStyle(size, textStyle);
|
|
124
|
-
return /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
125
|
-
|
|
63
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
64
|
+
className: isFollowing ? 'bg-background border-border' : 'bg-primary border-primary',
|
|
65
|
+
style: baseButtonStyle,
|
|
126
66
|
onPress: handlePress,
|
|
127
67
|
disabled: disabled || isLoading,
|
|
128
68
|
activeOpacity: 0.8,
|
|
129
69
|
children: showLoadingState && isLoading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
|
|
130
70
|
size: "small",
|
|
131
|
-
color:
|
|
71
|
+
color: isFollowing ? colors.text : '#FFFFFF'
|
|
132
72
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}],
|
|
73
|
+
className: isFollowing ? 'text-foreground' : 'text-primary-foreground',
|
|
74
|
+
style: baseTextStyle,
|
|
136
75
|
children: isFollowing ? 'Following' : 'Follow'
|
|
137
76
|
})
|
|
138
77
|
});
|
|
139
78
|
});
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
143
|
-
*
|
|
144
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
145
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
146
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
147
|
-
* to the full OxyContext.
|
|
148
|
-
*
|
|
149
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
150
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
151
|
-
*/
|
|
152
79
|
const FollowButton = props => {
|
|
153
80
|
const {
|
|
154
81
|
oxyServices,
|
|
@@ -157,8 +84,6 @@ const FollowButton = props => {
|
|
|
157
84
|
} = (0, _OxyContext.useOxy)();
|
|
158
85
|
const currentUserId = currentUser?.id ? String(currentUser.id).trim() : '';
|
|
159
86
|
const targetUserId = props.userId ? String(props.userId).trim() : '';
|
|
160
|
-
|
|
161
|
-
// Don't render if not authenticated or viewing own profile
|
|
162
87
|
if (!isAuthenticated || !targetUserId || currentUserId && currentUserId === targetUserId) {
|
|
163
88
|
return null;
|
|
164
89
|
}
|
|
@@ -168,8 +93,6 @@ const FollowButton = props => {
|
|
|
168
93
|
oxyServices: oxyServices
|
|
169
94
|
});
|
|
170
95
|
};
|
|
171
|
-
|
|
172
|
-
// Pure helper functions (no hooks, no state) extracted outside the component
|
|
173
96
|
exports.FollowButton = FollowButton;
|
|
174
97
|
function getBaseButtonStyle(size, style) {
|
|
175
98
|
const baseStyle = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","
|
|
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":[]}
|
|
@@ -2,28 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useCallback, memo } from 'react';
|
|
4
4
|
import { TouchableOpacity, Text, Platform, ActivityIndicator } from 'react-native';
|
|
5
|
-
import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming } from 'react-native-reanimated';
|
|
6
5
|
import { useOxy } from "../context/OxyContext.js";
|
|
7
6
|
import { fontFamilies } from "../styles/fonts.js";
|
|
8
7
|
import { toast } from '../../lib/sonner';
|
|
9
8
|
import { useFollowForButton } from "../hooks/useFollow.js";
|
|
10
9
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
11
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
|
-
// Create animated TouchableOpacity
|
|
13
|
-
const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity);
|
|
14
|
-
/**
|
|
15
|
-
* Inner component that handles all hooks and rendering.
|
|
16
|
-
*
|
|
17
|
-
* Separated from the outer wrapper to avoid a Rules of Hooks violation.
|
|
18
|
-
* The outer wrapper handles the auth/self-follow guard and returns null
|
|
19
|
-
* before any hooks are called. This inner component always renders
|
|
20
|
-
* (all hooks are called unconditionally).
|
|
21
|
-
*
|
|
22
|
-
* Receives oxyServices as a prop instead of calling useOxy(), so it does
|
|
23
|
-
* not subscribe to the OxyContext. This is critical in list contexts where
|
|
24
|
-
* N buttons would all re-render on any context change (session socket events,
|
|
25
|
-
* token refreshes, etc.).
|
|
26
|
-
*/
|
|
27
11
|
const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
28
12
|
userId,
|
|
29
13
|
oxyServices,
|
|
@@ -34,13 +18,11 @@ const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
|
34
18
|
textStyle,
|
|
35
19
|
disabled = false,
|
|
36
20
|
showLoadingState = true,
|
|
37
|
-
preventParentActions = true
|
|
38
|
-
theme: _theme = 'light'
|
|
21
|
+
preventParentActions = true
|
|
39
22
|
}) {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Uses granular Zustand selectors — only re-renders when THIS user's data changes
|
|
23
|
+
const {
|
|
24
|
+
colors
|
|
25
|
+
} = useTheme();
|
|
44
26
|
const {
|
|
45
27
|
isFollowing,
|
|
46
28
|
isLoading,
|
|
@@ -48,102 +30,47 @@ const FollowButtonInner = /*#__PURE__*/memo(function FollowButtonInner({
|
|
|
48
30
|
setFollowStatus,
|
|
49
31
|
fetchStatus
|
|
50
32
|
} = useFollowForButton(userId, oxyServices);
|
|
51
|
-
|
|
52
|
-
// Animation values
|
|
53
|
-
const scale = useSharedValue(1);
|
|
54
|
-
|
|
55
|
-
// Stable press handler — depends on primitives only
|
|
56
33
|
const handlePress = useCallback(async event => {
|
|
57
|
-
if (preventParentActions && event
|
|
34
|
+
if (preventParentActions && event?.preventDefault) {
|
|
58
35
|
event.preventDefault();
|
|
59
36
|
event.stopPropagation?.();
|
|
60
37
|
}
|
|
61
38
|
if (disabled || isLoading) return;
|
|
62
|
-
|
|
63
|
-
// Press animation
|
|
64
|
-
scale.value = withTiming(0.95, {
|
|
65
|
-
duration: 100
|
|
66
|
-
}, finished => {
|
|
67
|
-
if (finished) {
|
|
68
|
-
scale.value = withSpring(1, {
|
|
69
|
-
damping: 15,
|
|
70
|
-
stiffness: 200
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
39
|
try {
|
|
75
40
|
await toggleFollow();
|
|
76
|
-
|
|
41
|
+
onFollowChange?.(!isFollowing);
|
|
77
42
|
} catch (err) {
|
|
78
43
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
79
44
|
toast.error(error.message || 'Failed to update follow status');
|
|
80
45
|
}
|
|
81
|
-
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions
|
|
82
|
-
|
|
83
|
-
// Set initial follow status on mount if provided and not already set
|
|
46
|
+
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
84
47
|
useEffect(() => {
|
|
85
48
|
if (userId && !isFollowing && initiallyFollowing) {
|
|
86
49
|
setFollowStatus(initiallyFollowing);
|
|
87
50
|
}
|
|
88
|
-
// Intentional: only run on mount with initial values
|
|
89
51
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
52
|
}, [userId, initiallyFollowing]);
|
|
91
|
-
|
|
92
|
-
// Fetch latest follow status from backend on mount
|
|
93
53
|
useEffect(() => {
|
|
94
|
-
if (userId)
|
|
95
|
-
fetchStatus();
|
|
96
|
-
}
|
|
54
|
+
if (userId) fetchStatus();
|
|
97
55
|
}, [userId, fetchStatus]);
|
|
98
|
-
|
|
99
|
-
// Scale-only animated style
|
|
100
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
101
|
-
transform: [{
|
|
102
|
-
scale: scale.value
|
|
103
|
-
}]
|
|
104
|
-
}));
|
|
105
|
-
|
|
106
|
-
// Colors from bloom theme — follows NativeWind convention via useTheme()
|
|
107
|
-
// Not following: bg-primary, border-primary, text white
|
|
108
|
-
// Following: bg-background, border-border, text-foreground
|
|
109
|
-
const buttonColorStyle = isFollowing ? {
|
|
110
|
-
backgroundColor: colors.background,
|
|
111
|
-
borderColor: colors.border
|
|
112
|
-
} : {
|
|
113
|
-
backgroundColor: colors.primary,
|
|
114
|
-
borderColor: colors.primary
|
|
115
|
-
};
|
|
116
|
-
const textColor = isFollowing ? colors.text : '#FFFFFF';
|
|
117
56
|
const baseButtonStyle = getBaseButtonStyle(size, style);
|
|
118
57
|
const baseTextStyle = getBaseTextStyle(size, textStyle);
|
|
119
|
-
return /*#__PURE__*/_jsx(
|
|
120
|
-
|
|
58
|
+
return /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
59
|
+
className: isFollowing ? 'bg-background border-border' : 'bg-primary border-primary',
|
|
60
|
+
style: baseButtonStyle,
|
|
121
61
|
onPress: handlePress,
|
|
122
62
|
disabled: disabled || isLoading,
|
|
123
63
|
activeOpacity: 0.8,
|
|
124
64
|
children: showLoadingState && isLoading ? /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
125
65
|
size: "small",
|
|
126
|
-
color:
|
|
66
|
+
color: isFollowing ? colors.text : '#FFFFFF'
|
|
127
67
|
}) : /*#__PURE__*/_jsx(Text, {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}],
|
|
68
|
+
className: isFollowing ? 'text-foreground' : 'text-primary-foreground',
|
|
69
|
+
style: baseTextStyle,
|
|
131
70
|
children: isFollowing ? 'Following' : 'Follow'
|
|
132
71
|
})
|
|
133
72
|
});
|
|
134
73
|
});
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
138
|
-
*
|
|
139
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
140
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
141
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
142
|
-
* to the full OxyContext.
|
|
143
|
-
*
|
|
144
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
145
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
146
|
-
*/
|
|
147
74
|
const FollowButton = props => {
|
|
148
75
|
const {
|
|
149
76
|
oxyServices,
|
|
@@ -152,8 +79,6 @@ const FollowButton = props => {
|
|
|
152
79
|
} = useOxy();
|
|
153
80
|
const currentUserId = currentUser?.id ? String(currentUser.id).trim() : '';
|
|
154
81
|
const targetUserId = props.userId ? String(props.userId).trim() : '';
|
|
155
|
-
|
|
156
|
-
// Don't render if not authenticated or viewing own profile
|
|
157
82
|
if (!isAuthenticated || !targetUserId || currentUserId && currentUserId === targetUserId) {
|
|
158
83
|
return null;
|
|
159
84
|
}
|
|
@@ -163,8 +88,6 @@ const FollowButton = props => {
|
|
|
163
88
|
oxyServices: oxyServices
|
|
164
89
|
});
|
|
165
90
|
};
|
|
166
|
-
|
|
167
|
-
// Pure helper functions (no hooks, no state) extracted outside the component
|
|
168
91
|
function getBaseButtonStyle(size, style) {
|
|
169
92
|
const baseStyle = {
|
|
170
93
|
flexDirection: 'row',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useEffect","useCallback","memo","TouchableOpacity","Text","Platform","ActivityIndicator","
|
|
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;
|
|
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;
|
|
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" />
|
|
@@ -8,12 +8,6 @@ import {
|
|
|
8
8
|
Platform,
|
|
9
9
|
ActivityIndicator
|
|
10
10
|
} from 'react-native';
|
|
11
|
-
import Animated, {
|
|
12
|
-
useSharedValue,
|
|
13
|
-
useAnimatedStyle,
|
|
14
|
-
withSpring,
|
|
15
|
-
withTiming
|
|
16
|
-
} from 'react-native-reanimated';
|
|
17
11
|
import { useOxy } from '../context/OxyContext';
|
|
18
12
|
import { fontFamilies } from '../styles/fonts';
|
|
19
13
|
import { toast } from '../../lib/sonner';
|
|
@@ -21,9 +15,6 @@ import { useFollowForButton } from '../hooks/useFollow';
|
|
|
21
15
|
import { useTheme } from '@oxyhq/bloom/theme';
|
|
22
16
|
import type { OxyServices } from '@oxyhq/core';
|
|
23
17
|
|
|
24
|
-
// Create animated TouchableOpacity
|
|
25
|
-
const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity);
|
|
26
|
-
|
|
27
18
|
export interface FollowButtonProps {
|
|
28
19
|
userId: string;
|
|
29
20
|
initiallyFollowing?: boolean;
|
|
@@ -37,19 +28,6 @@ export interface FollowButtonProps {
|
|
|
37
28
|
theme?: 'light' | 'dark';
|
|
38
29
|
}
|
|
39
30
|
|
|
40
|
-
/**
|
|
41
|
-
* Inner component that handles all hooks and rendering.
|
|
42
|
-
*
|
|
43
|
-
* Separated from the outer wrapper to avoid a Rules of Hooks violation.
|
|
44
|
-
* The outer wrapper handles the auth/self-follow guard and returns null
|
|
45
|
-
* before any hooks are called. This inner component always renders
|
|
46
|
-
* (all hooks are called unconditionally).
|
|
47
|
-
*
|
|
48
|
-
* Receives oxyServices as a prop instead of calling useOxy(), so it does
|
|
49
|
-
* not subscribe to the OxyContext. This is critical in list contexts where
|
|
50
|
-
* N buttons would all re-render on any context change (session socket events,
|
|
51
|
-
* token refreshes, etc.).
|
|
52
|
-
*/
|
|
53
31
|
const FollowButtonInner = memo(function FollowButtonInner({
|
|
54
32
|
userId,
|
|
55
33
|
oxyServices,
|
|
@@ -61,12 +39,9 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
61
39
|
disabled = false,
|
|
62
40
|
showLoadingState = true,
|
|
63
41
|
preventParentActions = true,
|
|
64
|
-
theme: _theme = 'light',
|
|
65
42
|
}: FollowButtonProps & { oxyServices: OxyServices }) {
|
|
66
|
-
const
|
|
67
|
-
const colors = bloomTheme.colors;
|
|
43
|
+
const { colors } = useTheme();
|
|
68
44
|
|
|
69
|
-
// Uses granular Zustand selectors — only re-renders when THIS user's data changes
|
|
70
45
|
const {
|
|
71
46
|
isFollowing,
|
|
72
47
|
isLoading,
|
|
@@ -75,68 +50,43 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
75
50
|
fetchStatus,
|
|
76
51
|
} = useFollowForButton(userId, oxyServices);
|
|
77
52
|
|
|
78
|
-
// Animation values
|
|
79
|
-
const scale = useSharedValue(1);
|
|
80
|
-
|
|
81
|
-
// Stable press handler — depends on primitives only
|
|
82
53
|
const handlePress = useCallback(async (event?: { preventDefault?: () => void; stopPropagation?: () => void }) => {
|
|
83
|
-
if (preventParentActions && event
|
|
54
|
+
if (preventParentActions && event?.preventDefault) {
|
|
84
55
|
event.preventDefault();
|
|
85
56
|
event.stopPropagation?.();
|
|
86
57
|
}
|
|
87
58
|
if (disabled || isLoading) return;
|
|
88
59
|
|
|
89
|
-
// Press animation
|
|
90
|
-
scale.value = withTiming(0.95, { duration: 100 }, (finished) => {
|
|
91
|
-
if (finished) {
|
|
92
|
-
scale.value = withSpring(1, { damping: 15, stiffness: 200 });
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
60
|
try {
|
|
97
61
|
await toggleFollow();
|
|
98
|
-
|
|
62
|
+
onFollowChange?.(!isFollowing);
|
|
99
63
|
} catch (err: unknown) {
|
|
100
64
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
101
65
|
toast.error(error.message || 'Failed to update follow status');
|
|
102
66
|
}
|
|
103
|
-
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions
|
|
67
|
+
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions]);
|
|
104
68
|
|
|
105
|
-
// Set initial follow status on mount if provided and not already set
|
|
106
69
|
useEffect(() => {
|
|
107
70
|
if (userId && !isFollowing && initiallyFollowing) {
|
|
108
71
|
setFollowStatus(initiallyFollowing);
|
|
109
72
|
}
|
|
110
|
-
// Intentional: only run on mount with initial values
|
|
111
73
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
112
74
|
}, [userId, initiallyFollowing]);
|
|
113
75
|
|
|
114
|
-
// Fetch latest follow status from backend on mount
|
|
115
76
|
useEffect(() => {
|
|
116
|
-
if (userId)
|
|
117
|
-
fetchStatus();
|
|
118
|
-
}
|
|
77
|
+
if (userId) fetchStatus();
|
|
119
78
|
}, [userId, fetchStatus]);
|
|
120
79
|
|
|
121
|
-
// Scale-only animated style
|
|
122
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
123
|
-
transform: [{ scale: scale.value }],
|
|
124
|
-
}));
|
|
125
|
-
|
|
126
|
-
// Colors from bloom theme — follows NativeWind convention via useTheme()
|
|
127
|
-
// Not following: bg-primary, border-primary, text white
|
|
128
|
-
// Following: bg-background, border-border, text-foreground
|
|
129
|
-
const buttonColorStyle: ViewStyle = isFollowing
|
|
130
|
-
? { backgroundColor: colors.background, borderColor: colors.border }
|
|
131
|
-
: { backgroundColor: colors.primary, borderColor: colors.primary };
|
|
132
|
-
const textColor = isFollowing ? colors.text : '#FFFFFF';
|
|
133
|
-
|
|
134
80
|
const baseButtonStyle = getBaseButtonStyle(size, style);
|
|
135
81
|
const baseTextStyle = getBaseTextStyle(size, textStyle);
|
|
136
82
|
|
|
137
83
|
return (
|
|
138
|
-
<
|
|
139
|
-
|
|
84
|
+
<TouchableOpacity
|
|
85
|
+
className={isFollowing
|
|
86
|
+
? 'bg-background border-border'
|
|
87
|
+
: 'bg-primary border-primary'
|
|
88
|
+
}
|
|
89
|
+
style={baseButtonStyle}
|
|
140
90
|
onPress={handlePress}
|
|
141
91
|
disabled={disabled || isLoading}
|
|
142
92
|
activeOpacity={0.8}
|
|
@@ -144,35 +94,26 @@ const FollowButtonInner = memo(function FollowButtonInner({
|
|
|
144
94
|
{showLoadingState && isLoading ? (
|
|
145
95
|
<ActivityIndicator
|
|
146
96
|
size="small"
|
|
147
|
-
color={
|
|
97
|
+
color={isFollowing ? colors.text : '#FFFFFF'}
|
|
148
98
|
/>
|
|
149
99
|
) : (
|
|
150
|
-
<Text
|
|
100
|
+
<Text
|
|
101
|
+
className={isFollowing ? 'text-foreground' : 'text-primary-foreground'}
|
|
102
|
+
style={baseTextStyle}
|
|
103
|
+
>
|
|
151
104
|
{isFollowing ? 'Following' : 'Follow'}
|
|
152
105
|
</Text>
|
|
153
106
|
)}
|
|
154
|
-
</
|
|
107
|
+
</TouchableOpacity>
|
|
155
108
|
);
|
|
156
109
|
});
|
|
157
110
|
|
|
158
|
-
/**
|
|
159
|
-
* Outer wrapper that handles the "should we render?" check.
|
|
160
|
-
*
|
|
161
|
-
* This is the ONLY place useOxy() is called — to check authentication and
|
|
162
|
-
* get the current user ID for the self-follow guard. The oxyServices instance
|
|
163
|
-
* is passed down as a prop to the inner component, which avoids subscribing
|
|
164
|
-
* to the full OxyContext.
|
|
165
|
-
*
|
|
166
|
-
* The early return happens BEFORE the inner component mounts, so the inner
|
|
167
|
-
* component's hooks are never called conditionally (no Rules of Hooks violation).
|
|
168
|
-
*/
|
|
169
111
|
const FollowButton: React.FC<FollowButtonProps> = (props) => {
|
|
170
112
|
const { oxyServices, isAuthenticated, user: currentUser } = useOxy();
|
|
171
113
|
|
|
172
114
|
const currentUserId = currentUser?.id ? String(currentUser.id).trim() : '';
|
|
173
115
|
const targetUserId = props.userId ? String(props.userId).trim() : '';
|
|
174
116
|
|
|
175
|
-
// Don't render if not authenticated or viewing own profile
|
|
176
117
|
if (!isAuthenticated || !targetUserId || (currentUserId && currentUserId === targetUserId)) {
|
|
177
118
|
return null;
|
|
178
119
|
}
|
|
@@ -186,7 +127,6 @@ const FollowButton: React.FC<FollowButtonProps> = (props) => {
|
|
|
186
127
|
);
|
|
187
128
|
};
|
|
188
129
|
|
|
189
|
-
// Pure helper functions (no hooks, no state) extracted outside the component
|
|
190
130
|
function getBaseButtonStyle(size: string, style?: StyleProp<ViewStyle>): StyleProp<ViewStyle> {
|
|
191
131
|
const baseStyle: ViewStyle = {
|
|
192
132
|
flexDirection: 'row',
|