blob: e209bb3db1e9ddfbddd63124ecea697e4b505b2a (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
// import spinner from "@/assets/icons/spinner.svg";
import Composer from "@/components/composer/Composer";
import PostList from "@/components/feed/PostList";
import ProfileEditor from "@/components/ProfileEditor";
import useLocalState, { useStore } from "@/state/state";
import type { Ship } from "@/types/urbit";
import "@/styles/ProfileEditor.css";
import Icon from "@/components/Icon";
import toast from "react-hot-toast";
import { useState } from "react";
import type { FC } from "@/types/trill";
function UserFeed({ p }: { p: Ship }) {
const { api } = useLocalState((s) => ({
api: s.api,
}));
// auto updating on SSE doesn't work if we do shallow
const { following } = useStore();
const feed = following.get(p);
const refetch = () => feed;
const isOwnProfile = p === api?.airlock.our;
const isFollowing = following.has(p);
const [isFollowLoading, setIsFollowLoading] = useState(false);
const [isAccessLoading, setIsAccessLoading] = useState(false);
const [fc, setFC] = useState<FC>();
const handleFollow = async () => {
if (!api) return;
setIsFollowLoading(true);
try {
if (isFollowing) {
await api.unfollow(p);
toast.success(`Unfollowed ${p}`);
} else {
await api.follow(p);
toast.success(`Now following ${p}`);
}
} catch (error) {
toast.error(`Failed to ${isFollowing ? "unfollow" : "follow"} ${p}`);
console.error("Follow error:", error);
} finally {
setIsFollowLoading(false);
}
};
const handleRequestAccess = async () => {
if (!api) return;
setIsAccessLoading(true);
try {
const res = await api.peekFeed(p);
toast.success(`Access request sent to ${p}`);
if ("error" in res) toast.error(res.error);
else setFC(res.ok);
} catch (error) {
toast.error(`Failed to request access from ${p}`);
console.error("Access request error:", error);
} finally {
setIsAccessLoading(false);
}
};
return (
<div id="user-page">
<ProfileEditor ship={p} />
{!isOwnProfile && (
<div className="user-actions">
<button
onClick={handleFollow}
disabled={isFollowLoading}
className={`action-btn ${isFollowing ? "following" : "follow"}`}
>
{isFollowLoading ? (
<>
<Icon name="settings" size={16} />
{isFollowing ? "Unfollowing..." : "Following..."}
</>
) : (
<>
<Icon name={isFollowing ? "bell" : "pals"} size={16} />
{isFollowing ? "Unfollow" : "Follow"}
</>
)}
</button>
<button
onClick={handleRequestAccess}
disabled={isAccessLoading}
className="action-btn access"
>
{isAccessLoading ? (
<>
<Icon name="settings" size={16} />
Requesting...
</>
) : (
<>
<Icon name="key" size={16} />
Request Access
</>
)}
</button>
</div>
)}
{feed ? (
<div id="feed-proper">
<Composer />
<PostList data={feed} refetch={refetch} />
</div>
) : fc ? (
<div id="feed-proper">
<Composer />
<PostList data={fc} refetch={refetch} />
</div>
) : null}
{!isOwnProfile && !feed && !fc && (
<div id="other-user-feed">
<div className="empty-feed-message">
<Icon name="messages" size={48} color="textMuted" />
<h3>No Posts Available</h3>
<p>
This user's posts are not publicly visible.
{!isFollowing && " Try following them"} or request temporary
access to see their content.
</p>
</div>
</div>
)}
</div>
);
}
export default UserFeed;
|