diff --git a/src/pages/accounts/index.tsx b/src/pages/accounts/index.tsx deleted file mode 100644 index 6d0387d8..00000000 --- a/src/pages/accounts/index.tsx +++ /dev/null @@ -1,383 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { - CheckCircleIcon, - PlusCircleIcon, - RectangleGroupIcon, - UserPlusIcon, -} from "@heroicons/react/20/solid"; -import { NewspaperIcon } from "@heroicons/react/24/solid"; -import { - JoinedNounspacePostDraft, - useNewPostStore, -} from "@/common/data/stores/useNewPostStore"; -import { hydrate, useAccountStore } from "@/common/data/stores/useAccountStore"; -import { isEmpty } from "lodash"; -import { - AccountPlatformType, - AccountStatusType, -} from "@/constants/accounts"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/common/ui/atoms/card"; -import { Button } from "@/common/ui/atoms/button"; -import { QrCode } from "@/common/ui/components/QrCode"; -import ConnectFarcasterAccountViaHatsProtocol from "@/common/ui/components/ConnectFarcasterAccountViaHatsProtocol"; -import { useAccount } from "wagmi"; -import { - WarpcastLoginStatus, - callCreateSignerRequest, - createSignerRequest, - generateWarpcastSigner, - getWarpcastSignerStatus, -} from "@/common/data/api/warpcastLogin"; -import HelpCard from "@/common/ui/components/HelpCard"; -import { useIsMounted } from "@/common/lib/hooks/useIsMounted"; -import { useRouter } from "next/router"; -import { NeynarAPIClient } from "@neynar/nodejs-sdk"; -import ConfirmOnchainSignerButton from "@/common/ui/components/ConfirmOnchainSignerButton"; -import SwitchWalletButton from "@/common/ui/components/SwitchWalletButton"; -import { APP_FID } from "@/constants/app"; -import SignupForNonLocalAccountCard from "@/common/ui/organisms/SignupForNonLocalAccountCard"; - -enum SignupStateEnum { - "initial", - "connecting", - "done", -} - -type SignupStepType = { - state: SignupStateEnum; - title: string; - description: string; - idx: number; -}; - -const SignupSteps: SignupStepType[] = [ - { - state: SignupStateEnum.initial, - title: "Start adding Farcaster accounts", - description: "Get started with Nounspace", - idx: 0, - }, - { - state: SignupStateEnum.connecting, - title: "Connect account", - description: "Connect your Farcaster account to Nounspace", - idx: 1, - }, - { - state: SignupStateEnum.done, - title: "Start casting", - description: "Start casting and browsing your feed", - idx: 2, - }, -]; - -export default function Accounts() { - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - const { isConnected } = useAccount(); - const isMounted = useIsMounted(); - - const { accounts, addAccount, setAccountActive } = useAccountStore(); - - const { addNewPostDraft } = useNewPostStore(); - - const hasActiveAccounts = - accounts.filter( - (account) => - account.status === AccountStatusType.active && - account.platform !== AccountPlatformType.farcaster_local_readonly - ).length > 0; - const pendingAccounts = accounts.filter( - (account) => - account.status === AccountStatusType.pending && - account.platform === AccountPlatformType.farcaster - ); - const hasOnlyLocalAccounts = - accounts.length && - accounts.every( - (account) => - account.platform === AccountPlatformType.farcaster_local_readonly - ); - const hasPendingNewAccounts = pendingAccounts.length > 0; - const pendingAccount = hasPendingNewAccounts ? pendingAccounts[0] : null; - - const [signupState, setSignupState] = useState( - SignupStateEnum.initial - ); - - useEffect(() => { - if (pendingAccount && signupState === SignupStateEnum.connecting) { - pollForSigner(); - } - }, [signupState, pendingAccount, isMounted]); - - useEffect(() => { - if (hasPendingNewAccounts && signupState === SignupStateEnum.initial) { - setSignupState(SignupStateEnum.connecting); - } - }, [signupState, hasPendingNewAccounts]); - - const onCreateNewAccount = async () => { - const { publicKey, privateKey, signature, requestFid, deadline } = - await generateWarpcastSigner(); - const { token, deeplinkUrl } = await callCreateSignerRequest({ - publicKey, - requestFid, - signature, - deadline, - }); - - try { - setIsLoading(true); - await addAccount({ - account: { - id: null, - platformAccountId: undefined, - status: AccountStatusType.pending, - platform: AccountPlatformType.farcaster, - publicKey, - privateKey, - data: { signerToken: token, deeplinkUrl }, - }, - }); - setIsLoading(false); - setSignupState(1); - } catch (e) { - console.log("error when trying to add account", e); - setIsLoading(false); - } - }; - - const checkStatusAndActiveAccount = async (pendingAccount) => { - if (!pendingAccount?.data?.signerToken) return; - - const { status, data } = await getWarpcastSignerStatus( - pendingAccount.data.signerToken - ); - console.log("checked signer status: ", status, data); - if (status === WarpcastLoginStatus.success) { - const fid = data.userFid; - const neynarClient = new NeynarAPIClient( - process.env.NEYNAR_API_KEY! - ); - const user = (await neynarClient.fetchBulkUsers([fid], {viewerFid: APP_FID})).users[0]; - await setAccountActive(pendingAccount.id, user.username, { - platform_account_id: user.fid.toString(), - data, - }); - await hydrate(); - window.location.reload(); - } - }; - - const pollForSigner = async () => { - let tries = 0; - while (tries < 60) { - tries += 1; - await new Promise((r) => setTimeout(r, 2000)); - checkStatusAndActiveAccount(pendingAccount); - - if (!isMounted()) return; - } - }; - - const onStartCasting = () => { - addNewPostDraft(JoinedNounspacePostDraft); - router.push("/post"); - }; - - const renderCreateSignerStep = () => ( - - - - Connect your Farcaster account - - - Connect with Nounspace to see and publish casts - - - - - - - ); - - const renderConnectAccountStep = () => { - if (isEmpty(pendingAccounts)) return null; - - return ( -
-
- - - - Sign in with Web3 wallet - - - Pay with ETH on Optimism to connect with Nounspace - - - - {isConnected ? ( - - ) : ( - - )} - - - -
-
- - - - Sign in with Warpcast - - Pay with Warps in Warpcast to connect with Nounspace - - - - - Scan the QR code with your mobile camera app to sign in via - Warpcast. - - - - -
- ); - }; - - const renderDoneStep = () => ( - - - - - - You can start casting and browsing your feed - - - -
- - - -
-
-
- ); - - const renderCreateNewOnchainAccountCard = () => ( - - - Create a new Farcaster account onchain - - No need to connect with Warpcast. - Sign up directly with the Farcaster protocol onchain. - - - -
- -
-
-
- ); - - return ( -
- {(hasActiveAccounts || signupState === SignupStateEnum.done) && - renderDoneStep()} -
- {hasOnlyLocalAccounts ? ( -
- -
- ) : ( - <> -
- {signupState === SignupStateEnum.initial && - renderCreateSignerStep()} - {signupState === SignupStateEnum.connecting && - renderConnectAccountStep()} -
-
- {renderCreateNewOnchainAccountCard()} - -
- - - )} -
-
- ); -} diff --git a/src/pages/cast/[hash].tsx b/src/pages/cast/[hash].tsx deleted file mode 100644 index f2e0e997..00000000 --- a/src/pages/cast/[hash].tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from "react"; -import { NeynarAPIClient, isApiErrorResponse, FilterType } from "@neynar/nodejs-sdk"; -import { CastRow } from "@/common/ui/components/CastRow"; -import { CastResponse } from "@neynar/nodejs-sdk/build/neynar-api/v2"; -import { GetStaticPaths, GetStaticProps } from "next"; -import { isUndefined, uniqBy } from "lodash"; - -export const getStaticProps = (async function (context) { - const hash = context.params?.hash; - if (isUndefined(hash) || Array.isArray(hash)) { - return { - notFound: true, - } - } - const client = new NeynarAPIClient(process.env.NEYNAR_API_KEY!); - let cast: CastResponse | undefined; - try { - cast = await client.lookUpCastByHashOrWarpcastUrl(hash, "hash"); - } catch (error) { - // isApiErrorResponse can be used to check for Neynar API errors - if (isApiErrorResponse(error)) { - console.log("API Error", error, error.response.data); - } else { - console.log("Generic Error", error); - } - - return { - notFound: true, - } - } - - return { - props: { - cast - }, - - // Next.js will attempt to re-generate the page: - // - When a request comes in - // - At most once every 60 seconds - revalidate: 60, - }; -}) satisfies GetStaticProps<{ - cast: CastResponse | undefined -}> - -export const getStaticPaths = (async () => { - const client = new NeynarAPIClient(process.env.NEYNAR_API_KEY!); - - const globalFeed = await client.fetchFeed("filter", { - filterType: FilterType.GlobalTrending, - limit: 100, - }); - - const paths = uniqBy( - globalFeed.casts.map(({ hash }) => ({ - params: { - hash - }, - })), - "params.hash" - ); - - return { - paths, - fallback: 'blocking', - }; -}) satisfies GetStaticPaths; - -export default function Cast({ cast }) { - return ( - - ) -} diff --git a/src/pages/channels/index.tsx b/src/pages/channels/index.tsx deleted file mode 100644 index 8f44f619..00000000 --- a/src/pages/channels/index.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import React, { useState } from "react"; -import { useAccountStore } from "@/common/data/stores/useAccountStore"; -import { classNames } from "@/styles/utils/css"; -import { ChannelType } from "@/constants/channels"; -import Toggle from "@/common/ui/components/Toggle"; -import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; -import SortableList, { SortableItem } from "react-easy-sort"; -import { useRouter } from "next/router"; -import { take, isEmpty, findIndex, includes } from "lodash"; - -export default function Channels() { - const [searchTerm, setSearchTerm] = useState(""); - const [showNewChannelModal, setShowNewChannelModal] = useState(false); - const [isPending, setIsPending] = useState(false); - const router = useRouter(); - - const { - addPinnedChannel, - removePinnedChannel, - accounts, - hydratedAt, - allChannels, - updatedPinnedChannelIndices, - } = useAccountStore(); - - const channels = useAccountStore( - (state) => state.accounts[state.selectedAccountIdx]?.channels || [] - ); - - const onSortEnd = (oldIndex: number, newIndex: number) => { - updatedPinnedChannelIndices({ oldIndex, newIndex }); - }; - - const handleSearchChange = (e: event) => { - setSearchTerm(e.target.value.toLowerCase()); - }; - - const renderChannelCard = (channel: ChannelType, idx?: number) => { - const index = findIndex(channels, ["url", channel.url]); - const enabled = index !== -1; - - return ( -
- {enabled && idx !== undefined && ( -
- {idx + 1} -
- )} -
- {channel.icon_url ? ( - - ) : ( -
- )} -
-

- {channel.name} -

-
- - enabled ? removePinnedChannel(channel) : addPinnedChannel(channel) - } - /> -
-
- ); - }; - - const renderEmptyState = () => ( -
- Empty, no channels found -
- ); - - const renderPinnedChannels = () => { - return ( -
-
-
-

- Pinned channels -

-
-
-
    - {isEmpty(channels) ? ( -
    -

    - Start pinning channels and they will appear up here -

    -
    - ) : ( - - {channels.map((channel, idx) => ( - -
    - {renderChannelCard(channel, idx)} -
    -
    - ))} -
    - )} -
-
- ); - }; - - const renderAllChannels = () => { - return ( -
-
-
-

- All channels -

-

- Search and pin new channels -

-
-
- - -
-
-
-
- - -
-
-
-
-
    - {(searchTerm - ? allChannels.filter((channel) => - includes(channel.name.toLowerCase(), searchTerm) - ) - : take(allChannels, 50) - ).map((channel) => ( -
  • - {renderChannelCard(channel)} -
  • - ))} -
-
- ); - }; - - if (!hydratedAt) { - return null; - } - - return isEmpty(accounts) ? ( - renderEmptyState() - ) : ( - <> -
- {renderPinnedChannels()} - {renderAllChannels()} -
- - ); -} diff --git a/src/pages/farcaster-signup/index.tsx b/src/pages/farcaster-signup/index.tsx deleted file mode 100644 index b6b6520f..00000000 --- a/src/pages/farcaster-signup/index.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { ReactNode, useEffect, useState } from "react"; -import { Separator } from "@/common/ui/atoms/separator"; -import { Button } from "@/common/ui/atoms/button"; -import StepSequence from "@/common/ui/components/Steps/StepSequence"; -import RegisterFarcasterUsernameForm from "@/common/ui/components/RegisterFarcasterUsernameForm"; -import CreateFarcasterAccount from "@/common/ui/components/CreateFarcasterAccount"; -import { useAccount } from "wagmi"; -import { useRouter } from "next/router"; -import { useAccountModal, useConnectModal } from "@rainbow-me/rainbowkit"; -import SwitchWalletButton from "@/common/ui/components/SwitchWalletButton"; - -enum FarcasterSignupNav { - login = "LOGIN", - connect_wallet = "CONNECT_WALLET", - create_account_onchain = "CREATE_ACCOUNT_ONCHAIN", - register_username = "REGISTER_USERNAME", - explainer = "EXPLAINER", -} - -const onboardingNavItems = [ - { - title: "Login", - idx: 0, - key: FarcasterSignupNav.login, - }, - { - title: "Connect wallet", - idx: 1, - key: FarcasterSignupNav.connect_wallet, - }, - { - title: "Create account onchain", - idx: 2, - key: FarcasterSignupNav.create_account_onchain, - }, - { - title: "Register username", - idx: 3, - key: FarcasterSignupNav.register_username, - }, - { - title: "Let's go", - idx: 3, - key: FarcasterSignupNav.explainer, - }, -]; - -export default function Welcome() { - const { isConnected } = useAccount(); - const [step, setStep] = useState(onboardingNavItems[1].key); - const router = useRouter(); - - useEffect(() => { - if (isConnected && step === FarcasterSignupNav.connect_wallet) { - setStep(FarcasterSignupNav.create_account_onchain); - } - - if (!isConnected && step === FarcasterSignupNav.create_account_onchain) { - setStep(FarcasterSignupNav.connect_wallet); - } - }, [isConnected]); - - const getStepContent = ( - title: string, - description: string, - children?: ReactNode - ) => ( -
-
-

{title}

-

{description}

-
- - {children} -
- ); - - const renderExplainer = () => ( -
-

- You are fully onboarded to Nounspace 🥳 -

-
- - -
- -

- Use Hats Protocol 🧢 to share this account with onchain permissions -

-
-
-
- {/*
- - // can fill in video embed explainer here - -
*/} -
-
- ); - - const renderStep = (step: FarcasterSignupNav) => { - switch (step) { - case FarcasterSignupNav.login: - return getStepContent( - "Login", - "Congrats, you are already logged in to Nounspace." - ); - case FarcasterSignupNav.connect_wallet: - return getStepContent( - "Connect your wallet", - "We will create a Farcaster account onchain in the next step.", -
- - -
- ); - case FarcasterSignupNav.create_account_onchain: - return getStepContent( - "Create your Farcaster account", - "Let's get you onchain", - setStep(FarcasterSignupNav.register_username)} - /> - ); - case FarcasterSignupNav.register_username: - // skipped for now - return getStepContent( - "Register your username", - "Submit name and bio of your Farcaster account", - setStep(FarcasterSignupNav.explainer)} - /> - ); - case FarcasterSignupNav.explainer: - return getStepContent( - "Let's go 🤩", - "You just created your Farcaster account", - renderExplainer() - ); - default: - return <>; - } - }; - - return ( -
- -
- ); -} diff --git a/src/pages/feed/index.tsx b/src/pages/feed/index.tsx deleted file mode 100644 index 69900afc..00000000 --- a/src/pages/feed/index.tsx +++ /dev/null @@ -1,373 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { - AccountObjectType, - CUSTOM_CHANNELS, - useAccountStore, -} from "@/common/data/stores/useAccountStore"; -import { CastType } from "@/constants/farcaster"; -import { useHotkeys } from "react-hotkeys-hook"; -import { get, isEmpty, uniqBy } from "lodash"; -import { CastRow } from "@/common/ui/components/CastRow"; -import { CastThreadView } from "@/common/ui/components/CastThreadView"; -import { SelectableListWithHotkeys } from "@/common/ui/components/SelectableListWithHotkeys"; -import { Key } from "ts-key-enum"; -import ReplyModal from "@/common/ui/components/ReplyModal"; -import EmbedsModal from "@/common/ui/components/EmbedsModal"; -import { useInView } from "react-intersection-observer"; -import { useRouter } from "next/router"; -import { Button } from "@/common/ui/atoms/button"; -import { FilterType, NeynarAPIClient } from "@neynar/nodejs-sdk"; -import { - CastWithInteractions, - FeedType, -} from "@neynar/nodejs-sdk/build/neynar-api/v2"; -import { Loading } from "@/common/ui/components/Loading"; -import BigOptionSelector from "@/common/ui/components/BigOptionSelector"; - -type FeedsType = { - [key: string]: CastWithInteractions[]; -}; - -const DEFAULT_FEED_PAGE_SIZE = 10; - -const neynarClient = new NeynarAPIClient( - process.env.NEYNAR_API_KEY! -); - -export default function Feed() { - const router = useRouter(); - - const [feeds, setFeeds] = useState({}); - const [isLoadingFeed, setIsLoadingFeed] = useState(false); - const [nextFeedCursor, setNextFeedCursor] = useState(""); - const [selectedFeedIdx, setSelectedFeedIdx] = useState(0); - const [showCastThreadView, setShowCastThreadView] = useState(false); - const [showReplyModal, setShowReplyModal] = useState(false); - const [showEmbedsModal, setShowEmbedsModal] = useState(false); - const [selectedCast, setSelectedCast] = - useState(); - - const { ref: buttonRef, inView } = useInView({ - threshold: 0, - delay: 100, - }); - - const { - accounts, - selectedAccountIdx, - selectedChannelUrl, - setSelectedChannelUrl, - hydratedAt, - } = useAccountStore(); - - const account: AccountObjectType = accounts[selectedAccountIdx]; - - const getFeedKey = ({ - selectedChannelUrl, - account, - }: { - selectedChannelUrl: string | null; - account: AccountObjectType; - }) => { - if (selectedChannelUrl) { - return selectedChannelUrl; - } else if (account) { - return account.platformAccountId; - } else { - return null; - } - }; - - const feedKey = getFeedKey({ selectedChannelUrl, account }); - const feed = feedKey ? get(feeds, feedKey, []) : []; - - const onOpenLinkInCast = (idx: number) => { - // const cast = feed[idx]; - // if (cast?.embeds?.length === 0) return; - - setShowEmbedsModal(true); - }; - - const onSelectCast = (idx: number) => { - setSelectedFeedIdx(idx); - setShowCastThreadView(true); - }; - - useEffect(() => { - if (!showCastThreadView) { - setSelectedCast(feed[selectedFeedIdx]); - - if (selectedFeedIdx === 0) { - window.scrollTo(0, 0); - } else if (selectedFeedIdx === feed.length - 1) { - window.scrollTo(0, document.body.scrollHeight); - } - } - }, [selectedFeedIdx, showCastThreadView]); - - useEffect(() => { - if ( - isLoadingFeed || - isEmpty(feed) || - showCastThreadView || - feed.length < DEFAULT_FEED_PAGE_SIZE || - !account?.platformAccountId - ) - return; - - if (inView) { - getFeed({ - fid: account.platformAccountId!, - parentUrl: selectedChannelUrl, - cursor: nextFeedCursor, - }); - } - }, [ - selectedFeedIdx, - feed, - account, - selectedChannelUrl, - inView, - isLoadingFeed, - ]); - - useHotkeys( - [Key.Escape, "§"], - () => { - setShowCastThreadView(false); - }, - [showCastThreadView, showReplyModal, showEmbedsModal], - { - enableOnFormTags: true, - enableOnContentEditable: true, - enabled: showCastThreadView && !showReplyModal && !showEmbedsModal, - } - ); - - useHotkeys( - "r", - () => { - setShowReplyModal(true); - }, - [showReplyModal], - { - enabled: !showReplyModal, - enableOnFormTags: false, - preventDefault: true, - } - ); - - const getFeedType = (parentUrl: string | undefined) => - parentUrl === CUSTOM_CHANNELS.FOLLOWING - ? FeedType.Following - : FeedType.Filter; - - const getFilterType = (parentUrl: string | undefined) => { - if (parentUrl === CUSTOM_CHANNELS.FOLLOWING) return undefined; - if (parentUrl === CUSTOM_CHANNELS.TRENDING) - return FilterType.GlobalTrending; - return FilterType.ParentUrl; - }; - - const getFeed = async ({ - fid, - parentUrl, - cursor, - }: { - fid: string; - parentUrl?: string; - cursor?: string; - }) => { - if (isLoadingFeed) { - return; - } - setIsLoadingFeed(true); - - const feedOptions = { - filterType: getFilterType(parentUrl), - parentUrl, - cursor, - fid: Number(fid), - limit: DEFAULT_FEED_PAGE_SIZE, - }; - const newFeed = await neynarClient.fetchFeed( - getFeedType(parentUrl), - feedOptions - ); - const feedKey = parentUrl || fid; - const feed = feeds[feedKey] || []; - - setFeeds({ - ...feeds, - [feedKey]: uniqBy(feed.concat(newFeed.casts), "hash"), - }); - if (newFeed?.next?.cursor) { - setNextFeedCursor(newFeed.next.cursor); - } - setIsLoadingFeed(false); - }; - - useEffect(() => { - if (account?.platformAccountId && !showCastThreadView) { - const fid = account.platformAccountId!; - getFeed({ parentUrl: selectedChannelUrl, fid }); - } - }, [account, selectedChannelUrl, showCastThreadView]); - - useEffect(() => { - setShowReplyModal(false); - setShowCastThreadView(false); - setSelectedFeedIdx(0); - }, [selectedChannelUrl]); - - const renderRow = (item: any, idx: number) => ( -
  • - onSelectCast(idx)} - showChannel - /> -
  • - ); - - const getButtonText = (): string => { - if (isLoadingFeed) { - return "Loading..."; - } else if (feed.length === 0) { - return "Load feed"; - } else { - return "Load more"; - } - }; - - const renderLoadMoreButton = () => ( - - ); - - const renderFeed = () => ( - renderRow(item, idx)} - onExpand={onOpenLinkInCast} - onSelect={onSelectCast} - isActive={!(showCastThreadView || showReplyModal || showEmbedsModal)} - /> - ); - - const renderThread = () => ( - setShowCastThreadView(false)} - setSelectedCast={setSelectedCast} - /> - ); - - const renderReplyModal = () => ( - setShowReplyModal(false)} - parentCast={selectedCast} - /> - ); - - const renderEmbedsModal = () => { - return ( - setShowEmbedsModal(false)} - cast={selectedCast} - /> - ); - }; - - const renderWelcomeMessage = () => - feed.length === 0 && - hydratedAt && - !isLoadingFeed && ( -
    - router.push("/accounts"), - }, - { - title: "I have a Farcaster account", - description: - "I signed up for Farcaster before and want to connect my account to Nounspace.", - buttonText: "Connect my account", - onClick: () => router.push("/accounts"), - }, - { - title: "I am new to Farcaster", - description: - "I want to create a new account on Farcaster with Nounspace.", - buttonText: "Create new account", - onClick: () => router.push("/farcaster-signup"), - }, - { - title: "Browse trending feed", - description: - "No need to signup if you just want to checkout Nounspace", - buttonText: "Trending Feed →", - onClick: () => { - getFeed({ parentUrl: CUSTOM_CHANNELS.TRENDING, fid: "1" }); - setSelectedChannelUrl(CUSTOM_CHANNELS.TRENDING) - } - }, - ]} - /> -
    - ); - - const renderContent = () => ( -
    -
    - {isLoadingFeed && isEmpty(feed) && ( -
    - -
    - )} - {showCastThreadView ? ( - renderThread() - ) : ( - <> - {renderFeed()} - {renderWelcomeMessage()} - {feed.length > 0 && - feed.length >= DEFAULT_FEED_PAGE_SIZE && - renderLoadMoreButton()} - - )} -
    - {renderReplyModal()} - {renderEmbedsModal()} -
    - ); - - return renderContent(); -} diff --git a/src/pages/hats/index.tsx b/src/pages/hats/index.tsx deleted file mode 100644 index 773282c8..00000000 --- a/src/pages/hats/index.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import React, { ReactNode, useEffect, useState } from "react"; -import { Separator } from "@/common/ui/atoms/separator"; -import StepSequence from "@/common/ui/components/Steps/StepSequence"; -import { Button } from "@/common/ui/atoms/button"; -import { Input } from "@/common/ui/atoms/input"; -import { Label } from "@/common/ui/atoms/label"; -import { ClipboardDocumentIcon } from "@heroicons/react/20/solid"; -import { useAccount, useReadContract } from "wagmi"; -import { NeynarAPIClient } from "@neynar/nodejs-sdk"; -import { User } from "@neynar/nodejs-sdk/build/neynar-api/v2"; -import { Avatar, AvatarFallback, AvatarImage } from "@/common/ui/atoms/avatar"; -import BigOptionSelector from "@/common/ui/components/BigOptionSelector"; -import SharedAccountOwnershipSetup from "@/common/ui/components/SharedAccountOwnershipSetup"; -import TransferAccountToHatsDelegator from "@/common/ui/components/TransferAccountToHatsDelegator"; -import { openWindow } from "@/common/lib/utils/navigation"; -import { ID_REGISTRY } from "@/constants/contracts/id-registry"; -import { isEmpty } from "lodash"; -import clsx from "clsx"; -import SwitchWalletButton from "@/common/ui/components/SwitchWalletButton"; -import { Loading } from "@/common/ui/components/Loading"; -import { APP_FID } from "@/constants/app"; - -enum HatsSignupNav { - select_account = "SELECT_ACCOUNT", - hats_protocol_setup = "HATS_PROTOCOL_SETUP", - hats_tree = "HATS_TREE", - account_ownership = "ACCOUNT_OWNERSHIP", - transfer_ownership = "TRANSFER_OWNERSHIP", - invite = "INVITE", -} - -const hatsSignupSteps = [ - { - title: "Select account", - idx: 0, - key: HatsSignupNav.select_account, - }, - { - title: "Hats Protocol setup", - idx: 1, - key: HatsSignupNav.hats_protocol_setup, - }, - { - title: "Account ownership", - idx: 2, - key: HatsSignupNav.account_ownership, - }, - { - title: "Transfer ownership", - idx: 3, - key: HatsSignupNav.transfer_ownership, - }, - { - title: "Invite others", - idx: 4, - key: HatsSignupNav.invite, - }, -]; - -export default function HatsProtocolPage() { - const [step, setStep] = useState(hatsSignupSteps[0].key); - const [accountToTransfer, setAccountToTransfer] = useState(); - const [delegatorContractAddress, setDelegatorContractAddress] = useState< - `0x${string}` | null - >(); - const [infoMessage, setInfoMessage] = useState(); - const [didClickCopyShare, setDidClickCopyShare] = useState(false); - const { address, isConnected } = useAccount(); - const [userInput, setUserInput] = useState(""); - const [isLoadingAccount, setIsLoadingAccount] = useState(false); - const shareWithOthersText = `Join my shared Farcaster account with delegator contract - address ${delegatorContractAddress} and FID ${accountToTransfer?.fid}`; - - const { data: fidOfUser, error: idOfUserError } = useReadContract({ - ...ID_REGISTRY, - functionName: address ? "idOf" : undefined, - args: address ? [address] : undefined, - }); - - const fetchUser = async () => { - if (!userInput) return; - - setIsLoadingAccount(true); - try { - const neynarClient = new NeynarAPIClient( - process.env.NEYNAR_API_KEY! - ); - - let fid: number | undefined; - const isNumeric = /^-?\d+$/.test(userInput); - if (isNumeric) { - fid = Number(userInput); - const res = await neynarClient.fetchBulkUsers([fid], { - viewerFid: APP_FID, - }); - console.log("res", res); - setAccountToTransfer(res?.users?.[0]); - } else { - const res = await neynarClient.searchUser(userInput, parseInt(APP_FID)); - console.log("res", res); - setAccountToTransfer(res.result?.users?.[0]); - } - } catch (error) { - console.error(error); - setInfoMessage("User not found, please try again"); - } finally { - setIsLoadingAccount(false); - } - }; - - const getStepContent = ( - title: string, - description: string, - children?: ReactNode - ) => ( -
    -
    -

    {title}

    -

    {description}

    -
    - - {children} -
    - ); - - const renderUserInputForm = () => ( -
    -
    - { - if (accountToTransfer) setAccountToTransfer(null); - if (infoMessage) setInfoMessage(null); - setUserInput(e.target.value); - }} - /> - -
    - - {accountToTransfer && renderAccountToTransferPreview()} - {isLoadingAccount && } -
    - ); - - const renderAccountToTransferPreview = () => - accountToTransfer && ( -
    -
    - - - - {accountToTransfer.username || accountToTransfer.fid} - - -
    -

    - {accountToTransfer?.display_name} -

    - - @{accountToTransfer?.username} · fid: {accountToTransfer?.fid} - -
    -
    -
    - ); - - const renderSelectAccount = () => { - return getStepContent( - "Select account", - "You need to connect your wallet to select a Farcaster account to share", -
    - - {renderUserInputForm()} - {infoMessage && ( -

    {infoMessage}

    - )} - -
    - ); - }; - - const renderInvite = () => { - return ( -
    -
    -

    - Successfully created your shared Farcaster account 🥳 -

    -

    - All users with the Caster Hat in their wallet can now join! -

    -
    -

    - Share this to invite other users: -

    -
    - {didClickCopyShare && ( -

    Copied!

    - )} - { - setDidClickCopyShare(true); - navigator.clipboard.writeText(shareWithOthersText); - setTimeout(() => { - setDidClickCopyShare(false); - }, 2000); - }} - /> -
    -
    -
    -

    - {shareWithOthersText} -

    -
    -
    -
    - ); - }; - - const renderStep = (step: string) => { - switch (step) { - case HatsSignupNav.select_account: - return renderSelectAccount(); - case HatsSignupNav.hats_protocol_setup: - return getStepContent( - "Hats Protocol setup", - "Setup your Hats tree and deploy a delegator contract", -
    - {accountToTransfer && renderAccountToTransferPreview()} - setStep(HatsSignupNav.account_ownership), - }, - { - title: "I need a new Hats tree", - description: - "Start your setup with Hats Protocol in the Hats app", - buttonText: "Get started ↗️", - onClick: () => - openWindow(" https://app.hatsprotocol.xyz/trees/new"), - }, - ]} - /> -
    - ); - case HatsSignupNav.account_ownership: - return getStepContent( - "Account ownership", - "Decide where the Farcaster account will be owned and managed", -
    - {accountToTransfer && renderAccountToTransferPreview()} - setStep(HatsSignupNav.transfer_ownership)} - delegatorContractAddress={delegatorContractAddress} - setDelegatorContractAddress={setDelegatorContractAddress} - /> -
    - ); - case HatsSignupNav.transfer_ownership: - return getStepContent( - "Transfer ownership", - "Send your Farcaster account to the delegator contract", -
    - {accountToTransfer && renderAccountToTransferPreview()} - setStep(HatsSignupNav.invite)} - toAddress={delegatorContractAddress!} - /> -
    - ); - case HatsSignupNav.invite: - return getStepContent( - "Invite others", - "Let other users join your shared account", - renderInvite() - ); - default: - return null; - } - }; - - return ( -
    -
    - -
    -
    - ); -} diff --git a/src/pages/intro/index.tsx b/src/pages/intro/index.tsx deleted file mode 100644 index 73ca2500..00000000 --- a/src/pages/intro/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; - -export default function Intro() { - return ( - <> -
    -
    -

    - Intro to Nounspace -

    -
    -
    - ...coming soon... -
    -
    - - ) -} diff --git a/src/pages/notifications/index.tsx b/src/pages/notifications/index.tsx deleted file mode 100644 index 95a26dd9..00000000 --- a/src/pages/notifications/index.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import { castTextStyle, classNames } from "@/styles/utils/css"; -import { useAccountStore } from "@/common/data/stores/useAccountStore"; -import { SelectableListWithHotkeys } from "@/common/ui/components/SelectableListWithHotkeys"; -import { localize, timeDiff } from "@/common/lib/utils/date"; -import { CastThreadView } from "@/common/ui/components/CastThreadView"; -import { isEmpty } from "lodash"; -import { useHotkeys } from "react-hotkeys-hook"; -import { Key } from "ts-key-enum"; -import { CastType } from "@/constants/farcaster"; -import ReplyModal from "@/common/ui/components/ReplyModal"; -import { NeynarAPIClient } from "@neynar/nodejs-sdk"; -import { CastWithInteractions } from "@neynar/nodejs-sdk/build/neynar-api/v1"; - -enum NotificationTypeEnum { - "cast-reply" = "cast-reply", - "cast-mention" = "cast-mention", -} - -type NotificationType = { - hash: string; - threadHash: string; - parentHash: string; - parentUrl: string; - parentAuthor: { - fid: string; - }; - author: { - fid: string; - username: string; - displayName: string; - pfp: { - url: string; - }; - }; - text: string; - timestamp: string; - embeds: []; - type: NotificationTypeEnum; - reactions: { - count: number; - fids: string[]; - }; - recasts: { - count: number; - fids: string[]; - }; -}; - -enum NotificationNavigationEnum { - mentions = "mentions", - reactions = "reactions", -} - -const Notifications = () => { - const [navigation, setNavigation] = useState( - NotificationNavigationEnum.mentions - ); - const [notifications, setNotifications] = useState( - [] - ); - const [isLoading, setIsLoading] = useState(true); - const [selectedNotificationIdx, setSelectedNotificationIdx] = - useState(0); - const [isLeftColumnSelected, setIsLeftColumnSelected] = - useState(true); - const [selectedParentCast, setSelectedParentCast] = useState( - null - ); - const [showReplyModal, setShowReplyModal] = useState(false); - const [selectedCast, setSelectedCast] = useState(null); - - const viewerFid = useAccountStore( - (state) => state.accounts[state.selectedAccountIdx]?.platformAccountId - ); - const now = new Date(); - - useEffect(() => { - if (!viewerFid) return; - - const loadData = async () => { - const neynarClient = new NeynarAPIClient( - process.env.NEYNAR_API_KEY! - ); - - const resp = await neynarClient.fetchMentionAndReplyNotifications( - Number(viewerFid), - { - viewerFid: Number(viewerFid), - limit: 15, - } - ); - if (resp.result.notifications) { - setNotifications(resp.result.notifications); - } - setIsLoading(false); - }; - - loadData(); - - setShowReplyModal(false); - setIsLeftColumnSelected(true); - setSelectedNotificationIdx(0); - }, [viewerFid]); - - const navigationItems = [ - { - name: "Mentions & Replies", - onClick: () => setNavigation(NotificationNavigationEnum.mentions), - current: navigation == NotificationNavigationEnum.mentions, - }, - { - name: "Reactions", - onClick: () => setNavigation(NotificationNavigationEnum.reactions), - current: navigation == NotificationNavigationEnum.reactions, - }, - ]; - - useHotkeys( - "r", - () => { - setShowReplyModal(true); - }, - [showReplyModal], - { - enabled: !showReplyModal, - enableOnFormTags: false, - preventDefault: true, - } - ); - - useHotkeys( - ["tab", "shift+tab"], - () => { - setIsLeftColumnSelected(!isLeftColumnSelected); - }, - [isLeftColumnSelected], - { - enabled: !showReplyModal, - preventDefault: true, - } - ); - - useHotkeys( - ["l", "o", Key.Enter, Key.ArrowRight], - () => { - setIsLeftColumnSelected(false); - }, - [isLeftColumnSelected], - { - enabled: !showReplyModal, - preventDefault: true, - } - ); - - useHotkeys( - ["h", Key.Escape, Key.ArrowLeft], - () => { - setIsLeftColumnSelected(true); - }, - [isLeftColumnSelected], - { - enabled: !showReplyModal, - preventDefault: true, - } - ); - - const renderNotificationRow = (item: NotificationType, idx: number) => { - const timeAgo = timeDiff(now, new Date(item.timestamp)); - const timeAgoStr = localize(timeAgo[0], timeAgo[1]); - return ( -
  • setSelectedNotificationIdx(idx)} - className={classNames( - idx === selectedNotificationIdx - ? "bg-muted" - : "cursor-pointer bg-background/80", - "flex gap-x-4 px-5 py-4 rounded-sm" - )} - > - - -
    -
    -

    - {item.author.username} - - {item.type === NotificationTypeEnum["cast-reply"] - ? "replied" - : "mentioned you"} - -

    -

    - -

    -
    -

    - {item.text} -

    -
    -
  • - ); - }; - - const renderLeftColumn = () => { - return ( -
    -
    -
    - - renderNotificationRow(item, idx) - } - onSelect={(idx) => setSelectedNotificationIdx(idx)} - onExpand={() => null} - isActive={isLeftColumnSelected && !showReplyModal} - disableScroll - /> -
    -
    -
    - ); - }; - - const renderMainContent = () => { - return !isEmpty(selectedParentCast) ? ( -
    - -
    - ) : null; - }; - - useEffect(() => { - if (selectedNotificationIdx !== -1) { - const notification = notifications[selectedNotificationIdx]; - const hash = - notification?.threadHash || - notification?.parentHash || - notification?.hash; - - // getParentCast(hash) - - const author = notification?.author; - if (!hash) return; - - setSelectedParentCast({ hash, author }); - setSelectedCast(notification as CastType); - } - }, [selectedNotificationIdx, isLoading]); - - const renderReplyModal = () => ( - setShowReplyModal(false)} - parentCast={selectedCast} - /> - ); - - return ( -
    - {/* {renderHeader()} */} - {isLoading && ( -
    -
    Loading...
    -
    - )} - {navigation === NotificationNavigationEnum.mentions ? ( -
    - {renderLeftColumn()} -
    - {renderMainContent()} -
    -
    - ) : ( -
    -
    Coming soon...
    -
    - )} - {renderReplyModal()} -
    - ); -}; - -export default Notifications; diff --git a/src/pages/post/index.tsx b/src/pages/post/index.tsx deleted file mode 100644 index 94a82437..00000000 --- a/src/pages/post/index.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import NewPostEntry from "@/common/ui/components/NewPostEntry"; -import { classNames } from "@/styles/utils/css"; -import { useNewPostStore } from "@/common/data/stores/useNewPostStore"; -import React, { useEffect, useState } from "react"; -import { PlusCircleIcon, TrashIcon } from "@heroicons/react/24/outline"; -import * as Tooltip from "@radix-ui/react-tooltip"; -import HotkeyTooltipWrapper from "@/common/ui/components/HotkeyTooltipWrapper"; -import { Button } from "@/common/ui/atoms/button"; - -export default function NewPost() { - const [showToast, setShowToast] = useState(false); - - const { addNewPostDraft, removeAllPostDrafts } = useNewPostStore(); - const { drafts } = useNewPostStore(); - - useEffect(() => { - if (drafts.length === 0) { - addNewPostDraft({}); - } - }, []); - - return ( - <> -
    -
    -
    - You have {drafts.length}{" "} - {drafts.length !== 1 ? "drafts" : "draft"} -
    -
    - - - - - - - -
    -
    -
    - {drafts.map((draft, draftIdx) => ( -
    - {draft.parentCastId?.hash && ( -
    - Replying to{" "} - - @{draft.parentCastId?.hash} - -
    - )} - null} - /> -
    - ))} -
    -
    - - ); -} diff --git a/src/pages/profile/[slug].tsx b/src/pages/profile/[slug].tsx deleted file mode 100644 index 364b3344..00000000 --- a/src/pages/profile/[slug].tsx +++ /dev/null @@ -1,387 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import { FilterType, NeynarAPIClient, isApiErrorResponse } from "@neynar/nodejs-sdk"; -import { GetStaticPaths } from "next/types"; -import { - AvatarImage, - AvatarFallback, - Avatar, -} from "@/common/ui/atoms/avatar"; -import { CardHeader, Card } from "@/common/ui/atoms/card"; -import { SelectableListWithHotkeys } from "@/common/ui/components/SelectableListWithHotkeys"; -import { CastRow } from "@/common/ui/components/CastRow"; -import { CastWithInteractions } from "@neynar/nodejs-sdk/build/neynar-api/v2/openapi-farcaster/models/cast-with-interactions"; -import { Tabs, TabsList, TabsTrigger } from "@/common/ui/atoms/tabs"; -import { uniqBy } from "lodash"; -import { useHotkeys } from "react-hotkeys-hook"; -import FollowButton from "@/common/ui/components/FollowButton"; -import { useAccountStore } from "@/common/data/stores/useAccountStore"; -import { useDataStore } from "@/common/data/stores/useDataStore"; -import { APP_FID } from "@/constants/app"; -import FrameFidget from "@/fidgets/frame"; -import { RiPencilFill } from "react-icons/ri"; -import Space from "@/common/ui/templates/Space"; - -export async function getStaticProps({ params: { slug } }) { - const client = new NeynarAPIClient(process.env.NEYNAR_API_KEY!); - let user: any = {}; - try { - if (slug.startsWith("fid:")) { - const fid = slug.split(":")[1]; - user = await client.lookupUserByFid(fid); - } else { - user = await client.lookupUserByUsername(slug); - } - } catch (error) { - // isApiErrorResponse can be used to check for Neynar API errors - if (isApiErrorResponse(error)) { - console.log("API Error", error, error.response.data); - } else { - console.log("Generic Error", error); - } - - return { - notFound: true, - } - } - - return { - props: { - profile: user.result.user, - }, - - // Next.js will attempt to re-generate the page: - // - When a request comes in - // - At most once every 60 seconds - revalidate: 60, - }; -} - -export const getStaticPaths = (async () => { - const client = new NeynarAPIClient(process.env.NEYNAR_API_KEY!); - - const globalFeed = await client.fetchFeed("filter", { - filterType: FilterType.GlobalTrending, - limit: 100, - }); - - const paths = uniqBy( - globalFeed.casts.map(({ author }) => ({ - params: { - slug: author.username, - }, - })), - "params.slug" - ); - - return { - paths, - fallback: 'blocking', - }; -}) satisfies GetStaticPaths; - -enum FeedTypeEnum { - "casts" = "Casts", - "likes" = "Likes", -} - -export default function Profile({ profile }) { - const [selectedFeedIdx, setSelectedFeedIdx] = useState(0); - const [casts, setCasts] = useState([]); - const [feedType, setFeedType] = useState(FeedTypeEnum.casts); - - const { addUserProfile } = useDataStore(); - const { accounts, selectedAccountIdx } = useAccountStore(); - - const selectedAccount = accounts[selectedAccountIdx]; - const userFid = Number(selectedAccount?.platformAccountId) || APP_FID; - - const onSelectCast = (idx: number) => { - setSelectedFeedIdx(idx); - }; - - useEffect(() => { - if (!profile) return; - - const getData = async () => { - const neynarClient = new NeynarAPIClient( - process.env.NEYNAR_API_KEY! - ); - const resp = await neynarClient.fetchBulkUsers( - [profile.fid], - { viewerFid: userFid! as number}, - ) - if (resp?.users && resp.users.length === 1) { - addUserProfile({ username: profile.username, data: resp.users[0] }); - } - }; - - getData(); - }, [profile, userFid]); - - useHotkeys( - ["tab", "shift+tab"], - () => { - setFeedType( - feedType === FeedTypeEnum.casts - ? FeedTypeEnum.likes - : FeedTypeEnum.casts - ); - setSelectedFeedIdx(0); - window.scrollTo(0, 0); - }, - [feedType], - { - preventDefault: true, - } - ); - - useEffect(() => { - if (!profile) return; - - const loadFeed = async () => { - const client = new NeynarAPIClient( - process.env.NEYNAR_API_KEY! - ); - - if (feedType === FeedTypeEnum.casts) { - client - .fetchFeed("filter", { - filterType: "fids", - fids: [profile.fid], - withRecasts: true, - limit: 25, - }) - .then(({ casts }) => { - setCasts(casts); - }) - .catch((err) => console.log(`failed to fetch ${err}`)); - } else if (feedType === FeedTypeEnum.likes) { - client - .fetchUserReactions(profile.fid, "likes", { - limit: 25, - }) - .then(({ reactions }) => { - setCasts(reactions.map(({ cast }) => cast)); - }); - } - }; - - loadFeed(); - }, [profile, feedType]); - - const renderEmptyState = () => ( -
    -
    -
    -

    Loading...

    -
    -
    -
    - ); - - const renderRow = (item: CastWithInteractions, idx: number) => ( -
  • - onSelectCast(idx)} - /> -
  • - ); - - const renderFeed = () => ( -
    - - - {Object.keys(FeedTypeEnum).map((key) => { - return ( - setFeedType(FeedTypeEnum[key])} - > - {FeedTypeEnum[key]} - {feedType !== FeedTypeEnum[key] && ( -
    - Switch with   - - Tab - -
    - )} -
    - ); - })} -
    -
    - renderRow(item, idx)} - onExpand={() => null} - onSelect={() => null} - isActive - /> -
    - ); - - function ProfileHeader(){ - return( - - -
    -
    - - - {profile.username} - -
    -

    - {profile.displayName} -

    - - @{profile.username} - -
    -
    - {userFid !== profile.fid && ( - - )} -
    -
    - - {profile.followingCount} Following - - - {profile.followerCount} Followers - -
    - {profile.profile.bio.text} -
    -
    - ) - } - - //// - - const [editMode, setMode] = useState(false); - - //const { getCurrentUser } = useAccountStore(); - const user = useAccountStore.getState().accounts[0]; - - const availableHandles = ["s", "w", "e", "n", "sw", "nw", "se", "ne"]; - - const [fidgetConfigs, setFidgetConfigs] = useState([ - { f: <>, - resizeHandles: availableHandles, - x: 0, - y: 3, - w: 6, - minW: 4, - maxW: 8, - h: 7, - minH: 6, - maxH: 12 - }, - { - f: , - resizeHandles: availableHandles, - x: 0, - y: 0, - w: 6, - minW: 4, - maxW: 8, - h: 3, - minH: 2, - maxH: 6 - }, - { f: , - resizeHandles: availableHandles, - x: 10, - y: 0, - w: 3, - minW: 1, - h: 6, - minH: 1 - }, - { f: , - resizeHandles: availableHandles, - x: 6, - y: 0, - w: 3, - minW: 2, - maxW: 4, - h: 6, - minH: 3, - maxH: 12 - }, - { f: , - resizeHandles: availableHandles, - x: 6, - y: 6, - w: 3, - minW: 2, - maxW: 4, - h: 4, - minH: 3, - maxH: 12 - }, - { f: , - resizeHandles: availableHandles, - x: 10, - y: 6, - w: 3, - minW: 2, - maxW: 4, - h: 3, - minH: 3, - maxH: 12 - } - ]); - - function switchMode() { - setMode(!editMode); - } - - function retrieveConfig(user, space){ - const layoutConfig = { - isDraggable: editMode, - isResizable: editMode, - items: 6 , - cols: 12, - rowHeight: 70, - onLayoutChange: function(){}, - // This turns off compaction so you can place items wherever. - compactType: null, - // This turns off rearrangement so items will not be pushed arround. - preventCollision: true - }; - const layoutID = ""; - - return ({fidgetConfigs, layoutConfig, layoutID}) - } - - //// - - const renderProfile = () => ( -
    -
    - - - {renderFeed()} - -
    - ); - - return !profile ? renderEmptyState() : renderProfile(); -} diff --git a/src/pages/search/index.tsx b/src/pages/search/index.tsx deleted file mode 100644 index 07fd46b1..00000000 --- a/src/pages/search/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React, { useEffect, useRef, useState } from "react"; -import { InformationCircleIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; -import { searchForText, SearchResultCast } from "@/common/lib/utils/searchcaster"; -import { SelectableListWithHotkeys } from "@/common/ui/components/SelectableListWithHotkeys"; -import { debounce } from "lodash"; -import { CastRow } from "@/common/ui/components/CastRow"; -import { CastType } from "@/constants/farcaster"; -import { getUrlsInText } from "@/common/lib/utils/text"; - - -// export type CastType = { -// author: AuthorType -// hash: string -// parent_author: AuthorType | { fid?: string } | null -// parent_hash: string | null -// parent_url: string | null -// reactions: CastReactionsType -// text: string -// thread_hash: string | null -// timestamp: string -// embeds: EmbedType[] -// replies: { count: number } -// } - -// this transform isn't perfect yet -function transformToCastType(searchCasts: SearchResultCast[]): CastType[] { - return searchCasts.map((searchCast) => ({ - author: { - fid: '', - username: searchCast.body.username, - displayName: searchCast.meta.displayName, - pfp_url: searchCast.meta.avatar, - }, - hash: searchCast.merkleRoot, - parent_author: { - fid: '', - username: searchCast.meta.replyParentUsername.username, - displayName: searchCast.meta.replyParentUsername.username, - pfp_url: '', - }, - parent_hash: searchCast.body.data.replyParentMerkleRoot, - parent_url: '', - reactions: { - count: searchCast.meta.reactions.count, - type: searchCast.meta.reactions.type, - }, - text: searchCast.body.data.text, - thread_hash: searchCast.body.data.threadMerkleRoot, - timestamp: new Date(searchCast.body.publishedAt).toISOString(), - embeds: getUrlsInText(searchCast.body.data.text), - replies: { count: searchCast.meta.numReplyChildren }, - })) -} - - - -export default function Search() { - const [search, setSearch] = useState(''); - const [casts, setCasts] = useState([]); - const [selectedIdx, setSelectedIdx] = useState(0); - const [loading, setLoading] = useState(false); - - const onChange = async (text: string) => { - setSearch(text) - } - - const debouncedSearch = useRef( - debounce(async (text) => { - setLoading(true); - const searchCasts = await searchForText(text); - setCasts(transformToCastType(searchCasts)); - setLoading(false); - }, 200) - ).current; - - useEffect(() => { - if (search.length < 3) return; - - debouncedSearch(search); - }, [search]) - - useEffect(() => { - return () => { - debouncedSearch.cancel(); - }; - }, [debouncedSearch]); - - const renderSearchResultRow = (row: CastType, idx: number) => ( -
  • - null} - showChannel - /> -
  • - ) - - return ( -
    -
    - -
    -
    -
    - onChange(e.target.value)} - id="search" - className="block w-full rounded-md border-0 bg-white/20 py-2.5 pl-10 pr-3 text-foreground/80 placeholder:text-foreground focus:bg-white/30 focus:text-foreground focus:ring-0 focus:placeholder:text-gray-200 sm:text-sm sm:leading-6" - placeholder="Search" - type="search" - name="search" - autoFocus - /> -
    -
    -
    -
    -
    -
    -
    -

    early search prototype, liking and recasting doesn't work yet
    use Cmd + Shift + F to cast feedback

    -
    -
    -
    - {loading && ( -
    -
    -
    -
    -
    -
    -
    -
    -
    - )} - setSelectedIdx(idx)} - /> -
    - ) -} diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx deleted file mode 100644 index d68f0b6b..00000000 --- a/src/pages/settings/index.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import React, { useEffect, useState } from "react"; -import AlertDialogDemo from "@/common/ui/components/AlertDialog"; -import HelpCard from "@/common/ui/components/HelpCard"; -import { classNames } from "@/styles/utils/css"; -import { Button } from "@/common/ui/atoms/button"; -import { - AccountObjectType, - PENDING_ACCOUNT_NAME_PLACEHOLDER, - accountCommands, - channelCommands, - hydrate, - useAccountStore, -} from "@/common/data/stores/useAccountStore"; -import { newPostCommands } from "@/common/data/stores/useNewPostStore"; -import { User } from "@supabase/supabase-js"; -import { useRouter } from "next/router"; -import { getNavigationCommands } from "@/common/lib/commands/getNavigationCommands"; -import AccountManagementModal from "@/common/ui/components/AccountManagement/AccountManagementModal"; -import { useAccount } from "wagmi"; -import { useAccountModal, useConnectModal } from "@rainbow-me/rainbowkit"; -import { AccountPlatformType } from "@/constants/accounts"; -import { Loading } from "@/common/ui/components/Loading"; -import { ArrowPathIcon } from "@heroicons/react/20/solid"; -import { getUsernameForFid, updateUsername } from "@/common/lib/utils/farcaster"; -import SwitchWalletButton from "@/common/ui/components/SwitchWalletButton"; -import { createClient } from "@/common/data/database/supabase/clients/component"; - -type SimpleCommand = { - name: string; - shortcut: string; -}; - -export default function Settings() { - const router = useRouter(); - const supabase = createClient(); - - const [isLoading, setIsLoading] = useState(false); - const [user, setUser] = useState(null); - const [open, setOpen] = useState(false); - const [selectedAccount, setSelectedAccount] = - useState(null); - const { address, isConnected } = useAccount(); - - const { - hydratedAt, - accounts, - resetStore, - removeAccount, - updateAccountUsername, - } = useAccountStore(); - - useEffect(() => { - const getUser = async () => { - const { - data: { user }, - } = await supabase.auth.getUser(); - setUser(user); - }; - getUser(); - }, []); - - const onLogout = async () => { - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (session) { - resetStore(); - setUser(null); - await supabase.auth.signOut(); - } - - router.push("/login"); - }; - - const displayEmail = user?.email - ? `${user?.email.slice(0, 5)}...@${user?.email.split("@")[1]}` - : ""; - - const onClickManageAccount = (account: AccountObjectType) => { - setSelectedAccount(account); - setOpen(true); - }; - - const refreshAccountNames = async () => { - setIsLoading(true); - await Promise.all( - accounts.map(async (account) => await updateAccountUsername(account.id)) - ) - .then(() => { - console.log("All account names refreshed successfully"); - hydrate(); - }) - .catch((error) => - console.error("Error refreshing account names:", error) - ); - setIsLoading(false); - }; - - const renderInfoSection = () => { - const allCommands = [ - { name: "Command Palette", shortcut: "cmd+k" }, - { name: "Feed: go to previous cast in list", shortcut: "k" }, - { name: "Feed: go to next cast in list", shortcut: "j" }, - { name: "Feed: Open thread view for cast", shortcut: "Enter or o" }, - { name: "Feed: Open embedded link in new tab", shortcut: "shift+o" }, - ...getNavigationCommands({ router }), - ...newPostCommands, - ...accountCommands, - ...channelCommands, - ]; - - const commandsWithShortcuts: SimpleCommand[] = allCommands.filter( - (command) => command.shortcut !== undefined - ); - - return ( -
    -
    -

    - Hotkeys / Keyboard Shortcuts -

    -
    -
    -
    - {commandsWithShortcuts.map((command) => ( -
    -
    {command.name}
    - {command.shortcut && ( -
    - {command.shortcut.replace(/\+/g, " + ")} -
    - )} -
    - ))} -
    -
    -
    - ); - }; - - return ( -
    -
    -

    - Nounspace account -

    -
    -
    - - Email - - - {displayEmail} - -
    -
    - - -
    -
    -

    - Farcaster accounts -

    - -
    - {!hydratedAt && } -
      - {accounts.map((item: AccountObjectType, idx: number) => ( -
    • -
      -

      - {item.name || PENDING_ACCOUNT_NAME_PLACEHOLDER} -

      - {item.platformAccountId && item.status !== "active" && ( -

      - {item.status} -

      - )} - {item.platform === - AccountPlatformType.farcaster_hats_protocol && ( -

      🧢

      - )} - {item.platformAccountId && item.status === "active" && ( -

      - fid: {item.platformAccountId} -

      - )} - {/* */} - removeAccount(idx)} - /> -
      -
    • - ))} -
    - - {renderInfoSection()} - -
    - ); -} diff --git a/src/pages/welcome/index.tsx b/src/pages/welcome/index.tsx deleted file mode 100644 index 6075f8f3..00000000 --- a/src/pages/welcome/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { } from "react"; -import { Separator } from "@/common/ui/atoms/separator"; -import { useRouter } from "next/router"; -import BigOptionSelector from "@/common/ui/components/BigOptionSelector"; - -export default function Welcome() { - const router = useRouter(); - - return ( -
    -
    -
    -

    Welcome to Nounspace

    -

    Nounspace is a client for Farcaster with focus on keyboard-first desktop experience for power users and teams

    -
    - -
    - router.push("/accounts"), - }, - { - title: "I am new to Farcaster", - description: "I want to create a new account on Farcaster with Nounspace.", - buttonText: "Create new account", - onClick: () => router.push("/farcaster-signup"), - }, - ]} - /> -
    -
    -
    - ); -}