summaryrefslogtreecommitdiff
path: root/front/src/pages/User.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'front/src/pages/User.tsx')
-rw-r--r--front/src/pages/User.tsx138
1 files changed, 128 insertions, 10 deletions
diff --git a/front/src/pages/User.tsx b/front/src/pages/User.tsx
index a1e26f1..e209bb3 100644
--- a/front/src/pages/User.tsx
+++ b/front/src/pages/User.tsx
@@ -1,20 +1,138 @@
// import spinner from "@/assets/icons/spinner.svg";
import Composer from "@/components/composer/Composer";
import PostList from "@/components/feed/PostList";
-import useLocalState from "@/state/state";
+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, following } = useLocalState();
- const feed = following.get(api!.airlock.our!);
+ 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;
- if (p === api!.airlock.our)
- return (
- <div id="feed-proper">
- <Composer />
- <PostList data={feed!} refetch={refetch} />
- </div>
- );
+ 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;