diff options
Diffstat (limited to 'gui/src/pages')
| -rw-r--r-- | gui/src/pages/Error.tsx | 60 | ||||
| -rw-r--r-- | gui/src/pages/Feed.tsx | 15 | ||||
| -rw-r--r-- | gui/src/pages/Thread.tsx | 2 | ||||
| -rw-r--r-- | gui/src/pages/User.tsx | 219 |
4 files changed, 116 insertions, 180 deletions
diff --git a/gui/src/pages/Error.tsx b/gui/src/pages/Error.tsx new file mode 100644 index 0000000..c29e6a6 --- /dev/null +++ b/gui/src/pages/Error.tsx @@ -0,0 +1,60 @@ +import "@/styles/ErrorPage.css"; +import Icon from "@/components/Icon"; +import { Link } from "wouter"; +export function P404() { + return ( + <div className="error-page"> + <div className="error-content"> + <div className="error-icon-wrapper"> + <Icon name="crow" size={80} /> + </div> + <h1 className="error-title">404</h1> + <h2 className="error-subtitle">Page Not Found</h2> + <p className="error-message"> + The page you're looking for doesn't exist or has been moved. + </p> + <div className="error-actions"> + <Link href="/apps/nostrill/f/nostr"> + <button className="error-btn primary"> + <Icon name="home" size={18} /> + Go to Feed + </button> + </Link> + <Link href="/apps/nostrill/sets"> + <button className="error-btn secondary"> + <Icon name="settings" size={18} /> + Settings + </button> + </Link> + </div> + </div> + </div> + ); +} + +export function ErrorPage({ msg }: { msg: string }) { + return ( + <div> + <P404 /> + <h3>{msg}</h3> + <div className="error-page"> + <div className="error-content"> + <div className="error-icon-wrapper"> + <Icon name="crow" size={80} /> + </div> + <h1 className="error-title">Oops!</h1> + <h2 className="error-subtitle">Something went wrong</h2> + <p className="error-message">{msg}</p> + <div className="error-actions"> + <Link href="/apps/nostrill/f/nostr"> + <button className="error-btn primary"> + <Icon name="home" size={18} /> + Go to Feed + </button> + </Link> + </div> + </div> + </div> + </div> + ); +} diff --git a/gui/src/pages/Feed.tsx b/gui/src/pages/Feed.tsx index 02f7b1a..bb001d4 100644 --- a/gui/src/pages/Feed.tsx +++ b/gui/src/pages/Feed.tsx @@ -1,35 +1,30 @@ -// import spinner from "@/assets/icons/spinner.svg"; import "@/styles/trill.css"; import "@/styles/feed.css"; -import UserLoader from "./User"; import PostList from "@/components/feed/PostList"; import useLocalState from "@/state/state"; import { useParams } from "wouter"; import spinner from "@/assets/triangles.svg"; import { useState } from "react"; import Composer from "@/components/composer/Composer"; -import { ErrorPage } from "@/Router"; +import { ErrorPage } from "@/pages/Error"; import NostrFeed from "@/components/nostr/Feed"; type FeedType = "global" | "following" | "nostr"; function Loader() { - // const { api } = useLocalState(); const params = useParams(); console.log({ params }); - // const [loc, navigate] = useLocation(); - // console.log({ loc }); - // const our = api!.airlock.ship; + if (!params.taip) return <FeedPage t="nostr" />; if (params.taip === "global") return <FeedPage t={"global"} />; + if (params.taip === "following") return <FeedPage t={"following"} />; if (params.taip === "nostr") return <FeedPage t={"nostr"} />; // else if (param === FeedType.Rumors) return <Rumors />; // else if (param === FeedType.Home) return <UserFeed p={our} />; - else if (params.taip) return <UserLoader userString={params.taip!} />; else return <ErrorPage msg="No such page" />; } function FeedPage({ t }: { t: FeedType }) { const [active, setActive] = useState<FeedType>(t); return ( - <main> + <> <div id="top-tabs"> <div className={active === "global" ? "active" : ""} @@ -60,7 +55,7 @@ function FeedPage({ t }: { t: FeedType }) { <NostrFeed /> ) : null} </div> - </main> + </> ); } // {active === "global" ? ( diff --git a/gui/src/pages/Thread.tsx b/gui/src/pages/Thread.tsx index dec8946..fc215f2 100644 --- a/gui/src/pages/Thread.tsx +++ b/gui/src/pages/Thread.tsx @@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import useLocalState from "@/state/state"; import Icon from "@/components/Icon"; import spinner from "@/assets/triangles.svg"; -import { ErrorPage } from "@/Router"; +import { ErrorPage } from "@/pages/Error"; import "@/styles/trill.css"; import "@/styles/feed.css"; import Post from "@/components/post/Post"; diff --git a/gui/src/pages/User.tsx b/gui/src/pages/User.tsx index b73cd96..1611037 100644 --- a/gui/src/pages/User.tsx +++ b/gui/src/pages/User.tsx @@ -9,204 +9,85 @@ import { useEffect, useState } from "react"; import type { FC } from "@/types/trill"; import type { UserType } from "@/types/nostrill"; import { isValidPatp } from "urbit-ob"; -import { isValidNostrPubkey } from "@/logic/nostrill"; -import { ErrorPage } from "@/Router"; - -function UserLoader({ userString }: { userString: string }) { - const { api, pubkey } = useLocalState((s) => ({ - api: s.api, - pubkey: s.pubkey, - })); - // auto updating on SSE doesn't work if we do shallow - - const user = isValidPatp(userString) - ? { urbit: userString } - : isValidNostrPubkey(userString) - ? { nostr: userString } - : { error: "" }; - - const isOwnProfile = - "urbit" in user - ? user.urbit === api?.airlock.our - : "nostr" in user - ? pubkey === user.nostr - : false; - if ("error" in user) return <ErrorPage msg={"Invalid user"} />; - else - return <UserFeed user={user} userString={userString} isMe={isOwnProfile} />; +import { ErrorPage } from "@/pages/Error"; +import { useParams } from "wouter"; +import { isValidNostrKey } from "@/logic/nostr"; +import TrillFeed from "@/components/trill/User"; +import NostrFeed from "@/components/nostr/User"; + +function UserLoader() { + const params = useParams(); + console.log({ params }); + const userString = params.user; + if (!userString) return <ErrorPage msg="no such user" />; + else if (isValidPatp(userString)) + return <UserFeed user={{ urbit: userString }} userString={userString} />; + else if (isValidNostrKey(userString)) + return <UserFeed user={{ nostr: userString }} userString={userString} />; + else return <ErrorPage msg="no such user" />; } function UserFeed({ user, userString, - isMe, }: { user: UserType; userString: string; - isMe: boolean; }) { - const { api, addProfile, addNotification, lastFact } = useLocalState((s) => ({ + const { api, pubkey } = useLocalState((s) => ({ api: s.api, addProfile: s.addProfile, addNotification: s.addNotification, lastFact: s.lastFact, + pubkey: s.pubkey, })); + const isMe = + "urbit" in user + ? user.urbit === api?.airlock.our + : "nostr" in user + ? pubkey === user.nostr + : false; // auto updating on SSE doesn't work if we do shallow const { following } = useStore(); const feed = following.get(userString); const hasFeed = !feed ? false : Object.entries(feed).length > 0; const refetch = () => feed; - const isFollowing = following.has(userString); const [isFollowLoading, setIsFollowLoading] = useState(false); const [isAccessLoading, setIsAccessLoading] = useState(false); - const [fc, setFC] = useState<FC>(); - - useEffect(() => { - console.log("fact", lastFact); - console.log(isFollowLoading); - if (!isFollowLoading) return; - const follow = lastFact?.fols; - if (!follow) return; - if ("new" in follow) { - if (userString !== follow.new.user) return; - toast.success(`Now following ${userString}`); - setIsFollowLoading(false); - addNotification({ - type: "follow", - from: userString, - message: `You are now following ${userString}`, - }); - } else if ("quit" in follow) { - toast.success(`Unfollowed ${userString}`); - setIsFollowLoading(false); - addNotification({ - type: "unfollow", - from: userString, - message: `You unfollowed ${userString}`, - }); - } - }, [lastFact, userString, isFollowLoading]); - - const handleFollow = async () => { - if (!api) return; - - setIsFollowLoading(true); - try { - if (isFollowing) { - await api.unfollow(user); - } else { - await api.follow(user); - toast.success(`Follow request sent to ${userString}`); - } - } catch (error) { - toast.error( - `Failed to ${isFollowing ? "unfollow" : "follow"} ${userString}`, - ); - setIsFollowLoading(false); - console.error("Follow error:", error); - } - }; - - const handleRequestAccess = async () => { - if (!api) return; - if (!("urbit" in user)) return; - - setIsAccessLoading(true); - try { - const res = await api.peekFeed(user.urbit); - toast.success(`Access request sent to ${user.urbit}`); - addNotification({ - type: "access_request", - from: userString, - message: `Access request sent to ${userString}`, - }); - if ("error" in res) toast.error(res.error); - else { - console.log("peeked", res.ok.feed); - setFC(res.ok.feed); - if (res.ok.profile) addProfile(userString, res.ok.profile); - } - } catch (error) { - toast.error(`Failed to request access from ${user.urbit}`); - console.error("Access request error:", error); - } finally { - setIsAccessLoading(false); - } - }; - console.log({ user, userString, feed, fc }); return ( <div id="user-page"> <Profile user={user} userString={userString} isMe={isMe} /> - - {!isMe && ( - <div className="user-actions"> - <button - onClick={handleFollow} - disabled={isFollowLoading} - className={`action-btn ${isFollowing ? "" : "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 && hasFeed ? ( - <div id="feed-proper"> - <Composer /> - <PostList data={feed} refetch={refetch} /> - </div> - ) : fc ? ( - <div id="feed-proper"> - <Composer /> - <PostList data={fc} refetch={refetch} /> - </div> + {isMe ? ( + <MyFeed /> + ) : "urbit" in user ? ( + <TrillFeed + user={user} + userString={userString} + feed={feed} + isFollowLoading={isFollowLoading} + setIsFollowLoading={setIsFollowLoading} + isAccessLoading={isAccessLoading} + setIsAccessLoading={setIsAccessLoading} + /> + ) : "nostr" in user ? ( + <NostrFeed + user={user} + userString={userString} + feed={feed} + isFollowLoading={isFollowLoading} + setIsFollowLoading={setIsFollowLoading} + isAccessLoading={isAccessLoading} + setIsAccessLoading={setIsAccessLoading} + /> ) : null} - - {!isMe && !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 UserLoader; + +function MyFeed() { + return <></>; +} |
