├── .cursorrules ├── .github └── workflows │ └── cron.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── apps ├── api │ ├── .env.example │ ├── .prettierignore │ ├── env.d.ts │ ├── package.json │ ├── railway.json │ ├── src │ │ ├── context │ │ │ └── authContext.ts │ │ ├── index.ts │ │ ├── middlewares │ │ │ ├── authMiddleware.ts │ │ │ ├── cors.ts │ │ │ ├── infoLogger.ts │ │ │ ├── rateLimiter.ts │ │ │ └── secretMiddleware.ts │ │ ├── prisma │ │ │ ├── client.ts │ │ │ ├── migrations │ │ │ │ ├── 20250226063758_init │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20250513064236_remove_permissions │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ └── schema.prisma │ │ ├── routes │ │ │ ├── cron │ │ │ │ ├── guild │ │ │ │ │ ├── syncFollowersStandingToGuild.ts │ │ │ │ │ ├── syncHQScoreAccountsToGuild.ts │ │ │ │ │ └── syncSubscribersToGuild.ts │ │ │ │ ├── index.ts │ │ │ │ └── removeExpiredSubscribers │ │ │ │ │ ├── ABI.ts │ │ │ │ │ └── index.ts │ │ │ ├── lens │ │ │ │ ├── authorization.ts │ │ │ │ └── index.ts │ │ │ ├── live │ │ │ │ ├── createLive.ts │ │ │ │ └── index.ts │ │ │ ├── metadata │ │ │ │ ├── getSTS.ts │ │ │ │ └── index.ts │ │ │ ├── oembed │ │ │ │ ├── getOembed.ts │ │ │ │ ├── helpers │ │ │ │ │ ├── getMetadata.ts │ │ │ │ │ └── meta │ │ │ │ │ │ ├── getDescription.ts │ │ │ │ │ │ ├── getMetaContent.ts │ │ │ │ │ │ └── getTitle.ts │ │ │ │ └── index.ts │ │ │ ├── og │ │ │ │ ├── getAccount.ts │ │ │ │ ├── getGroup.ts │ │ │ │ ├── getPost.ts │ │ │ │ └── index.ts │ │ │ ├── ping.ts │ │ │ ├── preferences │ │ │ │ ├── getPreferences.ts │ │ │ │ ├── index.ts │ │ │ │ └── updatePreferences.ts │ │ │ ├── sitemap │ │ │ │ ├── accounts │ │ │ │ │ ├── accountSitemap.ts │ │ │ │ │ └── accountsSitemapIndex.ts │ │ │ │ ├── index.ts │ │ │ │ ├── pagesSitemap.ts │ │ │ │ └── sitemapIndex.ts │ │ │ └── temp │ │ │ │ ├── getJumperData.ts │ │ │ │ └── index.ts │ │ └── utils │ │ │ ├── constants.ts │ │ │ ├── defaultMetadata.ts │ │ │ ├── lensPg.ts │ │ │ ├── redis.ts │ │ │ ├── sha256.ts │ │ │ ├── signer.ts │ │ │ └── syncAddressesToGuild.ts │ └── tsconfig.json └── web │ ├── .env.example │ ├── .prettierignore │ ├── functions │ └── _middleware.ts │ ├── index.html │ ├── package.json │ ├── public │ ├── _headers │ ├── _redirects │ ├── _routes.json │ ├── favicon.ico │ ├── llms.txt │ └── robots.txt │ ├── src │ ├── assets │ │ └── fonts │ │ │ ├── SofiaProSoftBold.woff2 │ │ │ ├── SofiaProSoftMed.woff2 │ │ │ └── SofiaProSoftReg.woff2 │ ├── components │ │ ├── Account │ │ │ ├── AccountFeed.tsx │ │ │ ├── DeletedDetails.tsx │ │ │ ├── Details.tsx │ │ │ ├── FeedType.tsx │ │ │ ├── Followerings.tsx │ │ │ ├── FollowersYouKnowOverview.tsx │ │ │ ├── Menu │ │ │ │ ├── Block.tsx │ │ │ │ ├── CopyLink.tsx │ │ │ │ ├── Mute.tsx │ │ │ │ ├── Report.tsx │ │ │ │ ├── StaffTool.tsx │ │ │ │ └── index.tsx │ │ │ ├── MetaDetails.tsx │ │ │ ├── Shimmer.tsx │ │ │ └── index.tsx │ │ ├── Bookmarks │ │ │ ├── BookmarksFeed.tsx │ │ │ └── index.tsx │ │ ├── Comment │ │ │ ├── CommentFeed.tsx │ │ │ └── NoneRelevantFeed.tsx │ │ ├── Common │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── Layout.tsx │ │ │ ├── MetaTags.tsx │ │ │ └── Providers │ │ │ │ ├── PreferencesProvider.tsx │ │ │ │ ├── Web3Provider.tsx │ │ │ │ └── index.tsx │ │ ├── Composer │ │ │ ├── Actions │ │ │ │ ├── Attachment.tsx │ │ │ │ ├── CollectSettings │ │ │ │ │ ├── AmountConfig.tsx │ │ │ │ │ ├── CollectForm.tsx │ │ │ │ │ ├── CollectLimitConfig.tsx │ │ │ │ │ ├── FollowersConfig.tsx │ │ │ │ │ ├── SplitConfig.tsx │ │ │ │ │ ├── TimeLimitConfig.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Gif │ │ │ │ │ ├── Categories.tsx │ │ │ │ │ ├── GifSelector.tsx │ │ │ │ │ ├── Gifs.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── GroupSettings │ │ │ │ │ ├── GroupSelector.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── LivestreamSettings │ │ │ │ │ ├── LivestreamEditor.tsx │ │ │ │ │ └── index.tsx │ │ │ ├── ChooseThumbnail.tsx │ │ │ ├── Editor │ │ │ │ ├── Editor.tsx │ │ │ │ ├── EditorHandle.tsx │ │ │ │ ├── EditorMenus.tsx │ │ │ │ ├── EmojiPicker.tsx │ │ │ │ ├── InlineMenu.tsx │ │ │ │ ├── MentionPicker.tsx │ │ │ │ ├── Toggle.tsx │ │ │ │ └── index.tsx │ │ │ ├── LicensePicker.tsx │ │ │ ├── LinkPreviews.tsx │ │ │ ├── NewAttachments.tsx │ │ │ ├── NewPost.tsx │ │ │ └── NewPublication.tsx │ │ ├── Explore │ │ │ ├── ExploreFeed.tsx │ │ │ └── index.tsx │ │ ├── Group │ │ │ ├── Details.tsx │ │ │ ├── GroupFeed.tsx │ │ │ ├── MembersCount.tsx │ │ │ ├── Settings │ │ │ │ ├── Monetize │ │ │ │ │ ├── SuperJoin.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Personalize │ │ │ │ │ ├── Form.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Rules │ │ │ │ │ ├── ApprovalRule.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── Shimmer.tsx │ │ │ └── index.tsx │ │ ├── Groups │ │ │ ├── FeedType.tsx │ │ │ ├── List.tsx │ │ │ ├── Sidebar │ │ │ │ └── Create │ │ │ │ │ ├── CreateGroup.tsx │ │ │ │ │ ├── CreateGroupModal.tsx │ │ │ │ │ ├── Minting.tsx │ │ │ │ │ └── Success.tsx │ │ │ └── index.tsx │ │ ├── Home │ │ │ ├── FeedType.tsx │ │ │ ├── ForYou.tsx │ │ │ ├── Hero.tsx │ │ │ ├── Highlights.tsx │ │ │ ├── Suggested.tsx │ │ │ ├── Timeline │ │ │ │ ├── EventType │ │ │ │ │ ├── Combined.tsx │ │ │ │ │ ├── Reposted.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Notification │ │ │ ├── Account.tsx │ │ │ ├── AggregatedNotificationTitle.tsx │ │ │ ├── FeedType.tsx │ │ │ ├── List.tsx │ │ │ ├── Shimmer.tsx │ │ │ ├── Type │ │ │ │ ├── AccountActionExecutedNotification.tsx │ │ │ │ ├── CommentNotification.tsx │ │ │ │ ├── FollowNotification.tsx │ │ │ │ ├── MentionNotification.tsx │ │ │ │ ├── PostActionExecutedNotification.tsx │ │ │ │ ├── QuoteNotification.tsx │ │ │ │ ├── ReactionNotification.tsx │ │ │ │ └── RepostNotification.tsx │ │ │ └── index.tsx │ │ ├── Pages │ │ │ ├── Copyright.tsx │ │ │ ├── Guidelines.tsx │ │ │ ├── Privacy.tsx │ │ │ ├── Support.tsx │ │ │ └── Terms.tsx │ │ ├── Post │ │ │ ├── Actions │ │ │ │ ├── Comment.tsx │ │ │ │ ├── Like.tsx │ │ │ │ ├── Menu │ │ │ │ │ ├── Bookmark.tsx │ │ │ │ │ ├── CopyPostText.tsx │ │ │ │ │ ├── Delete.tsx │ │ │ │ │ ├── HideComment.tsx │ │ │ │ │ ├── NotInterested.tsx │ │ │ │ │ ├── Report.tsx │ │ │ │ │ ├── Share.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Share │ │ │ │ │ ├── Quote.tsx │ │ │ │ │ ├── Repost.tsx │ │ │ │ │ ├── UndoRepost.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── FullPost.tsx │ │ │ ├── HiddenPost.tsx │ │ │ ├── MoreRelevantPeople.tsx │ │ │ ├── OpenAction │ │ │ │ ├── CollectAction │ │ │ │ │ ├── CollectActionBody.tsx │ │ │ │ │ ├── CollectActionButton.tsx │ │ │ │ │ ├── SmallCollectButton.tsx │ │ │ │ │ ├── Splits.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── TipAction │ │ │ │ │ └── index.tsx │ │ │ ├── PostAccount.tsx │ │ │ ├── PostAvatar.tsx │ │ │ ├── PostBody.tsx │ │ │ ├── PostHeader.tsx │ │ │ ├── PostStats.tsx │ │ │ ├── QuotedPost.tsx │ │ │ ├── Quotes.tsx │ │ │ ├── RelevantPeople.tsx │ │ │ ├── Shimmer.tsx │ │ │ ├── SingleImagePost.tsx │ │ │ ├── SinglePost.tsx │ │ │ ├── ThreadBody.tsx │ │ │ ├── Type │ │ │ │ ├── Commented.tsx │ │ │ │ ├── Reposted.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Search │ │ │ ├── Accounts.tsx │ │ │ ├── FeedType.tsx │ │ │ ├── Posts.tsx │ │ │ └── index.tsx │ │ ├── Settings │ │ │ ├── Blocked │ │ │ │ ├── List.tsx │ │ │ │ └── index.tsx │ │ │ ├── Developer │ │ │ │ ├── Tokens.tsx │ │ │ │ └── index.tsx │ │ │ ├── Funds │ │ │ │ ├── Balances.tsx │ │ │ │ ├── Unwrap.tsx │ │ │ │ ├── Withdraw.tsx │ │ │ │ ├── Wrap.tsx │ │ │ │ └── index.tsx │ │ │ ├── Manager │ │ │ │ ├── AccountManager │ │ │ │ │ ├── AddAccountManager.tsx │ │ │ │ │ ├── Management │ │ │ │ │ │ ├── List.tsx │ │ │ │ │ │ ├── Managed.tsx │ │ │ │ │ │ └── Unmanaged.tsx │ │ │ │ │ ├── Managers │ │ │ │ │ │ ├── List.tsx │ │ │ │ │ │ ├── Permission.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Signless.tsx │ │ │ │ └── index.tsx │ │ │ ├── Monetize │ │ │ │ ├── SuperFollow.tsx │ │ │ │ └── index.tsx │ │ │ ├── Personalize │ │ │ │ ├── Form.tsx │ │ │ │ └── index.tsx │ │ │ ├── Preferences │ │ │ │ ├── AppIcon.tsx │ │ │ │ ├── IncludeLowScore.tsx │ │ │ │ └── index.tsx │ │ │ ├── Sessions │ │ │ │ ├── List.tsx │ │ │ │ └── index.tsx │ │ │ ├── Username │ │ │ │ ├── LinkUsername.tsx │ │ │ │ ├── UnlinkUsername.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Shared │ │ │ ├── 404.tsx │ │ │ ├── 500.tsx │ │ │ ├── Account │ │ │ │ ├── AccountLink.tsx │ │ │ │ ├── AccountPreview.tsx │ │ │ │ ├── Accounts.tsx │ │ │ │ ├── DismissRecommendedAccount.tsx │ │ │ │ ├── Follow.tsx │ │ │ │ ├── FollowUnfollowButton.tsx │ │ │ │ ├── FollowWithRulesCheck.tsx │ │ │ │ ├── Fund │ │ │ │ │ └── TopUp │ │ │ │ │ │ ├── Button.tsx │ │ │ │ │ │ ├── Transfer.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── LazySmallSingleAccount.tsx │ │ │ │ ├── SearchAccounts.tsx │ │ │ │ ├── SingleAccount.tsx │ │ │ │ ├── SmallSingleAccount.tsx │ │ │ │ ├── SuperFollow.tsx │ │ │ │ ├── SwitchAccounts.tsx │ │ │ │ ├── TipButton.tsx │ │ │ │ ├── Unfollow.tsx │ │ │ │ └── WalletAccount.tsx │ │ │ ├── Alert │ │ │ │ ├── BlockOrUnblockAccount.tsx │ │ │ │ ├── DeletePost.tsx │ │ │ │ └── MuteOrUnmuteAccount.tsx │ │ │ ├── Audio │ │ │ │ ├── CoverImage.tsx │ │ │ │ ├── Player.tsx │ │ │ │ └── index.tsx │ │ │ ├── Auth │ │ │ │ ├── AuthMessage.tsx │ │ │ │ ├── Login.tsx │ │ │ │ ├── Signup │ │ │ │ │ ├── ChooseUsername.tsx │ │ │ │ │ ├── Minting.tsx │ │ │ │ │ ├── Success.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── SignupCard.tsx │ │ │ │ ├── WalletSelector.tsx │ │ │ │ └── index.tsx │ │ │ ├── AvatarUpload.tsx │ │ │ ├── BackButton.tsx │ │ │ ├── Badges │ │ │ │ ├── Alpha.tsx │ │ │ │ ├── Beta.tsx │ │ │ │ └── New.tsx │ │ │ ├── ChooseFile.tsx │ │ │ ├── CountdownTimer.tsx │ │ │ ├── Cover.tsx │ │ │ ├── CoverUpload.tsx │ │ │ ├── Embed │ │ │ │ ├── Quote.tsx │ │ │ │ └── Wrapper.tsx │ │ │ ├── EmojiPicker │ │ │ │ ├── List.tsx │ │ │ │ └── index.tsx │ │ │ ├── FallbackAccountName.tsx │ │ │ ├── Footer.tsx │ │ │ ├── FullPageLoader.tsx │ │ │ ├── GlobalAlerts.tsx │ │ │ ├── GlobalModals.tsx │ │ │ ├── GlobalShortcuts.tsx │ │ │ ├── Group │ │ │ │ ├── CancelGroupMembershipRequest.tsx │ │ │ │ ├── Join.tsx │ │ │ │ ├── JoinLeaveButton.tsx │ │ │ │ ├── JoinWithRulesCheck.tsx │ │ │ │ ├── Leave.tsx │ │ │ │ ├── SearchGroups.tsx │ │ │ │ ├── SingleGroup.tsx │ │ │ │ └── SuperJoin.tsx │ │ │ ├── Icons │ │ │ │ └── TipIcon.tsx │ │ │ ├── Loader.tsx │ │ │ ├── LoginButton.tsx │ │ │ ├── Markup │ │ │ │ ├── Code.tsx │ │ │ │ ├── MarkupLink │ │ │ │ │ ├── ExternalLink.tsx │ │ │ │ │ ├── Hashtag.tsx │ │ │ │ │ ├── Mention.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── MenuTransition.tsx │ │ │ ├── MetaDetails.tsx │ │ │ ├── Modal │ │ │ │ ├── Followers.tsx │ │ │ │ ├── FollowersYouKnow.tsx │ │ │ │ ├── Following.tsx │ │ │ │ ├── Likes.tsx │ │ │ │ ├── Members │ │ │ │ │ └── index.tsx │ │ │ │ ├── PostExecutors.tsx │ │ │ │ ├── ReportAccount │ │ │ │ │ └── index.tsx │ │ │ │ ├── ReportPost │ │ │ │ │ └── index.tsx │ │ │ │ ├── Reposts.tsx │ │ │ │ └── Subscribe.tsx │ │ │ ├── Navbar │ │ │ │ ├── BottomNavigation.tsx │ │ │ │ ├── MobileDrawerMenu.tsx │ │ │ │ ├── NavItems │ │ │ │ │ ├── Bookmarks.tsx │ │ │ │ │ ├── Groups.tsx │ │ │ │ │ ├── Logout.tsx │ │ │ │ │ ├── Pro.tsx │ │ │ │ │ ├── Settings.tsx │ │ │ │ │ ├── StaffTools.tsx │ │ │ │ │ ├── Support.tsx │ │ │ │ │ ├── SwitchAccount.tsx │ │ │ │ │ ├── ThemeSwitch.tsx │ │ │ │ │ └── YourAccount.tsx │ │ │ │ ├── SignedAccount.tsx │ │ │ │ ├── SignupButton.tsx │ │ │ │ └── index.tsx │ │ │ ├── NoBalanceError.tsx │ │ │ ├── NotLoggedIn.tsx │ │ │ ├── PageLayout.tsx │ │ │ ├── Post │ │ │ │ ├── Attachments.tsx │ │ │ │ ├── CommentWarning.tsx │ │ │ │ ├── ContentFeedType.tsx │ │ │ │ ├── Oembed │ │ │ │ │ ├── Embed.tsx │ │ │ │ │ ├── EmptyOembed.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── PostLink.tsx │ │ │ │ ├── PostWarning.tsx │ │ │ │ ├── PostWrapper.tsx │ │ │ │ └── Video.tsx │ │ │ ├── ProFeatureNotice.tsx │ │ │ ├── Search │ │ │ │ ├── RecentAccounts.tsx │ │ │ │ └── index.tsx │ │ │ ├── Settings │ │ │ │ └── WrongWallet.tsx │ │ │ ├── Shimmer │ │ │ │ ├── AccountListShimmer.tsx │ │ │ │ ├── FollowersYouKnowShimmer.tsx │ │ │ │ ├── GraphStatsShimmer.tsx │ │ │ │ ├── GroupListShimmer.tsx │ │ │ │ ├── PostShimmer.tsx │ │ │ │ ├── PostsShimmer.tsx │ │ │ │ ├── SingleAccountShimmer.tsx │ │ │ │ ├── SingleAccountsShimmer.tsx │ │ │ │ ├── SingleGroupShimmer.tsx │ │ │ │ ├── SmallSingleAccountShimmer.tsx │ │ │ │ └── ThumbnailsShimmer.tsx │ │ │ ├── Sidebar │ │ │ │ ├── ProBanner.tsx │ │ │ │ ├── WhoToFollow.tsx │ │ │ │ └── index.tsx │ │ │ ├── SiteError.tsx │ │ │ ├── Slug.tsx │ │ │ ├── TipMenu.tsx │ │ │ ├── ToggleWithHelper.tsx │ │ │ └── UI │ │ │ │ ├── Alert.tsx │ │ │ │ ├── Badge.tsx │ │ │ │ ├── Button.tsx │ │ │ │ ├── Card.tsx │ │ │ │ ├── CardHeader.tsx │ │ │ │ ├── Checkbox.tsx │ │ │ │ ├── EmptyState.tsx │ │ │ │ ├── ErrorMessage.tsx │ │ │ │ ├── Form.tsx │ │ │ │ ├── HelpTooltip.tsx │ │ │ │ ├── Image.tsx │ │ │ │ ├── Input.tsx │ │ │ │ ├── LightBox.tsx │ │ │ │ ├── Modal.tsx │ │ │ │ ├── RangeSlider.tsx │ │ │ │ ├── Select.tsx │ │ │ │ ├── SettingsHelper.tsx │ │ │ │ ├── Spinner.tsx │ │ │ │ ├── StackedAvatars.tsx │ │ │ │ ├── Tabs.tsx │ │ │ │ ├── TextArea.tsx │ │ │ │ ├── Toggle.tsx │ │ │ │ ├── Tooltip.tsx │ │ │ │ ├── Typography.tsx │ │ │ │ ├── WarningMessage.tsx │ │ │ │ └── index.ts │ │ └── Staff │ │ │ ├── Account │ │ │ ├── Tool │ │ │ │ ├── AccountOverview.tsx │ │ │ │ ├── ManagedAccounts.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ │ └── Overview │ │ │ └── index.tsx │ ├── font.css │ ├── helpers │ │ ├── accountPictureUtils.ts │ │ ├── authLink.ts │ │ ├── cn.ts │ │ ├── collectActionParams.ts │ │ ├── convertToTitleCase.ts │ │ ├── cropUtils.ts │ │ ├── datetime │ │ │ ├── formatDate.ts │ │ │ ├── formatRelativeOrAbsolute.ts │ │ │ ├── getNumberOfDaysFromDate.ts │ │ │ └── getTimeAddedNDay.ts │ │ ├── errorToast.ts │ │ ├── fetcher.ts │ │ ├── generateVideoThumbnails.ts │ │ ├── getAccountAttribute.ts │ │ ├── getAnyKeyValue.ts │ │ ├── getAssetLicense.ts │ │ ├── getBlockedMessage.ts │ │ ├── getCollectActionData.ts │ │ ├── getFavicon.ts │ │ ├── getFileFromDataURL.ts │ │ ├── getMentions.ts │ │ ├── getRpc.ts │ │ ├── getTokenImage.ts │ │ ├── getURLs.ts │ │ ├── getWalletDetails.ts │ │ ├── humanize.ts │ │ ├── injectReferrerToUrl.ts │ │ ├── nFormatter.ts │ │ ├── prosekit │ │ │ ├── extension.ts │ │ │ ├── markdown.ts │ │ │ ├── markdownContent.ts │ │ │ ├── rehypeJoinParagraph.ts │ │ │ ├── remarkBreakHandler.ts │ │ │ └── remarkLinkProtocol.ts │ │ ├── rules.ts │ │ ├── shortcuts.ts │ │ ├── splitNumber.ts │ │ ├── stopEventPropagation.ts │ │ ├── storageClient.ts │ │ ├── trimify.ts │ │ ├── truncateByWords.ts │ │ ├── truncateUrl.ts │ │ ├── uploadMetadata.ts │ │ └── uploadToIPFS.ts │ ├── hooks │ │ ├── prosekit │ │ │ ├── useContentChange.tsx │ │ │ ├── useDebouncedCallback.tsx │ │ │ ├── useEmojis.tsx │ │ │ ├── useFocus.tsx │ │ │ ├── useMentionQuery.tsx │ │ │ └── usePaste.tsx │ │ ├── useCreatePost.tsx │ │ ├── useHandleWrongNetwork.tsx │ │ ├── usePollTransactionStatus.tsx │ │ ├── usePostMetadata.tsx │ │ ├── usePreventScrollOnNumberInput.tsx │ │ ├── useTheme.tsx │ │ ├── useTransactionLifecycle.tsx │ │ └── useUploadAttachments.tsx │ ├── main.tsx │ ├── routes.tsx │ ├── store │ │ ├── non-persisted │ │ │ ├── alert │ │ │ │ ├── useBlockAlertStore.ts │ │ │ │ ├── useDeletePostAlertStore.ts │ │ │ │ └── useMuteAlertStore.ts │ │ │ ├── modal │ │ │ │ ├── useAuthModalStore.ts │ │ │ │ ├── useFundModalStore.ts │ │ │ │ ├── useMobileDrawerModalStore.ts │ │ │ │ ├── useNewPostModalStore.ts │ │ │ │ ├── useProModalStore.ts │ │ │ │ ├── useReportAccountModalStore.ts │ │ │ │ ├── useReportPostModalStore.ts │ │ │ │ ├── useSuperFollowModalStore.ts │ │ │ │ ├── useSuperJoinModalStore.ts │ │ │ │ └── useSwitchAccountModalStore.ts │ │ │ ├── navigation │ │ │ │ ├── useAccountLinkStore.ts │ │ │ │ └── usePostLinkStore.ts │ │ │ └── post │ │ │ │ ├── useCollectActionStore.ts │ │ │ │ ├── usePostAttachmentStore.ts │ │ │ │ ├── usePostAudioStore.ts │ │ │ │ ├── usePostGroupStore.ts │ │ │ │ ├── usePostLicenseStore.ts │ │ │ │ ├── usePostLiveStore.ts │ │ │ │ ├── usePostStore.ts │ │ │ │ └── usePostVideoStore.ts │ │ └── persisted │ │ │ ├── useAccountStore.ts │ │ │ ├── useAuthStore.ts │ │ │ ├── useHomeTabStore.ts │ │ │ ├── usePreferencesStore.ts │ │ │ ├── useProStore.ts │ │ │ └── useSearchStore.ts │ ├── styles.css │ ├── variants.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.mjs ├── biome.json ├── package.json ├── packages ├── config │ ├── base.tsconfig.json │ ├── package.json │ └── react.tsconfig.json ├── data │ ├── constants.ts │ ├── contracts.ts │ ├── enums.ts │ ├── errors.ts │ ├── lens-endpoints.ts │ ├── package.json │ ├── regex.ts │ ├── rpcs.ts │ ├── storage.ts │ ├── tokens.ts │ ├── tsconfig.json │ └── utils │ │ ├── getEnvConfig.ts │ │ └── regexLookbehindAvailable.ts ├── helpers │ ├── formatAddress.ts │ ├── generateUUID.ts │ ├── getAccount.ts │ ├── getAttachmentsData.ts │ ├── getAvatar.ts │ ├── getPostData.ts │ ├── imageKit.ts │ ├── isAccountDeleted.ts │ ├── package.json │ ├── parseJwt.ts │ ├── postHelpers.ts │ ├── sanitizeDStorageUrl.ts │ ├── selfFundedTransactionData.ts │ ├── sponsoredTransactionData.ts │ └── tsconfig.json ├── indexer │ ├── apollo │ │ ├── cache │ │ │ ├── createAccountRequestFieldPolicy.ts │ │ │ ├── createAccountsFieldPolicy.ts │ │ │ ├── createBasicFieldPolicy.ts │ │ │ ├── createPostReactionsFieldPolicy.ts │ │ │ ├── createPostReferencesFieldPolicy.ts │ │ │ ├── createWhoReferencedPostFieldPolicy.ts │ │ │ └── index.ts │ │ ├── client.ts │ │ ├── helpers │ │ │ └── cursorBasedPagination.ts │ │ ├── httpLink.ts │ │ └── retryLink.ts │ ├── codegen.ts │ ├── documents │ │ ├── fragments │ │ │ ├── AnyKeyValue.graphql │ │ │ ├── BooleanValue.graphql │ │ │ ├── Erc20Amount.graphql │ │ │ ├── NativeAmount.graphql │ │ │ ├── PaginatedResultInfo.graphql │ │ │ ├── PayableAmount.graphql │ │ │ ├── TimelineItem.graphql │ │ │ ├── account │ │ │ │ ├── Account.graphql │ │ │ │ ├── AccountFollowRule.graphql │ │ │ │ ├── AccountManager.graphql │ │ │ │ ├── AccountMetadata.graphql │ │ │ │ ├── LoggedInAccountOperations.graphql │ │ │ │ ├── Permissions.graphql │ │ │ │ └── Username.graphql │ │ │ ├── group │ │ │ │ ├── Group.graphql │ │ │ │ ├── GroupMetadata.graphql │ │ │ │ ├── GroupRule.graphql │ │ │ │ └── LoggedInGroupOperations.graphql │ │ │ ├── notifications │ │ │ │ ├── AccountActionExecutedNotification.graphql │ │ │ │ ├── CommentNotification.graphql │ │ │ │ ├── FollowNotification.graphql │ │ │ │ ├── MentionNotification.graphql │ │ │ │ ├── PostActionExecutedNotification.graphql │ │ │ │ ├── QuoteNotification.graphql │ │ │ │ ├── ReactionNotification.graphql │ │ │ │ └── RepostNotification.graphql │ │ │ ├── post │ │ │ │ ├── AccountMention.graphql │ │ │ │ ├── AnyPost.graphql │ │ │ │ ├── GroupMention.graphql │ │ │ │ ├── LoggedInPostOperations.graphql │ │ │ │ ├── Post.graphql │ │ │ │ ├── PostAction.graphql │ │ │ │ ├── PostFeedInfo.graphql │ │ │ │ ├── PostGroupInfo.graphql │ │ │ │ ├── PostMention.graphql │ │ │ │ ├── PostMetadata.graphql │ │ │ │ ├── PostStats.graphql │ │ │ │ ├── ReferencedPost.graphql │ │ │ │ ├── Repost.graphql │ │ │ │ ├── Subscription.graphql │ │ │ │ ├── collect │ │ │ │ │ ├── PayToCollectConfig.graphql │ │ │ │ │ ├── SimpleCollectAction.graphql │ │ │ │ │ └── UnknownPostAction.graphql │ │ │ │ └── post-metadata │ │ │ │ │ ├── ArticleMetadata.graphql │ │ │ │ │ ├── AudioMetadata.graphql │ │ │ │ │ ├── CheckingInMetadata.graphql │ │ │ │ │ ├── EmbedMetadata.graphql │ │ │ │ │ ├── EventMetadata.graphql │ │ │ │ │ ├── ImageMetadata.graphql │ │ │ │ │ ├── LinkMetadata.graphql │ │ │ │ │ ├── LivestreamMetadata.graphql │ │ │ │ │ ├── MetadataAttribute.graphql │ │ │ │ │ ├── MintMetadata.graphql │ │ │ │ │ ├── SpaceMetadata.graphql │ │ │ │ │ ├── StoryMetadata.graphql │ │ │ │ │ ├── TextOnlyMetadata.graphql │ │ │ │ │ ├── ThreeDMetadata.graphql │ │ │ │ │ ├── TransactionMetadata.graphql │ │ │ │ │ ├── UnknownPostMetadata.graphql │ │ │ │ │ ├── VideoMetadata.graphql │ │ │ │ │ └── media-fields │ │ │ │ │ ├── AnyMedia.graphql │ │ │ │ │ ├── MediaAudio.graphql │ │ │ │ │ ├── MediaImage.graphql │ │ │ │ │ └── MediaVideo.graphql │ │ │ └── transaction │ │ │ │ ├── SelfFundedTransactionRequest.graphql │ │ │ │ ├── SponsoredTransactionRequest.graphql │ │ │ │ └── TransactionWillFail.graphql │ │ ├── mutations │ │ │ ├── account │ │ │ │ ├── AddAccountManager.graphql │ │ │ │ ├── AssignUsernameToAccount.graphql │ │ │ │ ├── Block.graphql │ │ │ │ ├── CreateAccountWithUsername.graphql │ │ │ │ ├── EnableSignless.graphql │ │ │ │ ├── ExecuteAccountAction.graphql │ │ │ │ ├── Follow.graphql │ │ │ │ ├── HideManagedAccount.graphql │ │ │ │ ├── Mute.graphql │ │ │ │ ├── RemoveAccountManager.graphql │ │ │ │ ├── ReportAccount.graphql │ │ │ │ ├── RevokeAuthentication.graphql │ │ │ │ ├── SetAccountMetadata.graphql │ │ │ │ ├── UnassignUsernameFromAccount.graphql │ │ │ │ ├── Unblock.graphql │ │ │ │ ├── Unfollow.graphql │ │ │ │ ├── UnhideManagedAccount.graphql │ │ │ │ ├── Unmute.graphql │ │ │ │ ├── UpdateAccountFollowRules.graphql │ │ │ │ ├── UpdateAccountManager.graphql │ │ │ │ └── funds │ │ │ │ │ ├── Deposit.graphql │ │ │ │ │ ├── UnwrapTokens.graphql │ │ │ │ │ ├── Withdraw.graphql │ │ │ │ │ └── WrapTokens.graphql │ │ │ ├── auth │ │ │ │ ├── Authenticate.graphql │ │ │ │ ├── Challenge.graphql │ │ │ │ ├── Refresh.graphql │ │ │ │ └── SwitchAccount.graphql │ │ │ ├── group │ │ │ │ ├── CancelGroupMembershipRequest.graphql │ │ │ │ ├── CreateGroup.graphql │ │ │ │ ├── JoinGroup.graphql │ │ │ │ ├── LeaveGroup.graphql │ │ │ │ ├── RequestGroupMembership.graphql │ │ │ │ ├── SetGroupMetadata.graphql │ │ │ │ └── UpdateGroupRules.graphql │ │ │ ├── ml │ │ │ │ └── MlDismissRecommendedAccounts.graphql │ │ │ └── post │ │ │ │ ├── AddPostNotInterested.graphql │ │ │ │ ├── AddReaction.graphql │ │ │ │ ├── BookmarkPost.graphql │ │ │ │ ├── CreatePost.graphql │ │ │ │ ├── DeletePost.graphql │ │ │ │ ├── ExecutePostAction.graphql │ │ │ │ ├── HideReply.graphql │ │ │ │ ├── ReportPost.graphql │ │ │ │ ├── Repost.graphql │ │ │ │ ├── UndoBookmarkPost.graphql │ │ │ │ ├── UndoPostNotInterested.graphql │ │ │ │ ├── UndoReaction.graphql │ │ │ │ └── UnhideReply.graphql │ │ └── queries │ │ │ ├── TransactionStatus.graphql │ │ │ ├── account │ │ │ ├── Account.graphql │ │ │ ├── AccountBalances.graphql │ │ │ ├── AccountManagers.graphql │ │ │ ├── AccountStats.graphql │ │ │ ├── Accounts.graphql │ │ │ ├── AccountsAvailable.graphql │ │ │ ├── AccountsBlocked.graphql │ │ │ ├── AccountsBulk.graphql │ │ │ ├── Followers.graphql │ │ │ ├── FollowersYouKnow.graphql │ │ │ ├── Following.graphql │ │ │ ├── FullAccount.graphql │ │ │ ├── Me.graphql │ │ │ ├── Notifications.graphql │ │ │ └── Usernames.graphql │ │ │ ├── auth │ │ │ └── AuthenticatedSessions.graphql │ │ │ ├── group │ │ │ ├── Group.graphql │ │ │ ├── GroupMembers.graphql │ │ │ ├── GroupStats.graphql │ │ │ └── Groups.graphql │ │ │ ├── ml │ │ │ ├── AccountRecommendations.graphql │ │ │ ├── PostsExplore.graphql │ │ │ └── PostsForYou.graphql │ │ │ └── post │ │ │ ├── CollectAction.graphql │ │ │ ├── Feed.graphql │ │ │ ├── HiddenComments.graphql │ │ │ ├── Post.graphql │ │ │ ├── PostBookmarks.graphql │ │ │ ├── PostReactions.graphql │ │ │ ├── PostReferences.graphql │ │ │ ├── Posts.graphql │ │ │ ├── Timeline.graphql │ │ │ ├── TimelineHighlights.graphql │ │ │ ├── WhoExecutedActionOnPost.graphql │ │ │ └── WhoReferencedPost.graphql │ ├── generated.ts │ ├── package.json │ ├── possible-types.ts │ └── tsconfig.json └── types │ ├── api.d.ts │ ├── giphy.d.ts │ ├── hey.d.ts │ ├── misc.d.ts │ ├── package.json │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── script ├── clean ├── clean-branches ├── sort-package-json └── update-dependencies /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # testing 7 | coverage 8 | 9 | # expo 10 | .expo/ 11 | expo-env.d.ts 12 | credentials.json 13 | ios/ 14 | android/ 15 | *.ipa 16 | *.apk 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | .eslintcache 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | .env 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | 39 | # JetBrains IDE 40 | .idea 41 | 42 | # Node build artifacts 43 | dist 44 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run biome:check 2 | pnpm typecheck 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "formulahendry.auto-close-tag", 4 | "mikestead.dotenv", 5 | "biomejs.biome", 6 | "GitHub.github-vscode-theme", 7 | "mquandalle.graphql", 8 | "PKief.material-icon-theme", 9 | "Prisma.prisma", 10 | "christian-kohler.npm-intellisense", 11 | "bradlc.vscode-tailwindcss", 12 | "yzhang.markdown-all-in-one", 13 | "yoavbls.pretty-ts-errors" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /apps/api/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_LENS_NETWORK="testnet" 2 | DATABASE_URL="" 3 | LENS_DATABASE_URL="" 4 | REDIS_URL="" 5 | PRIVATE_KEY="0x1d65a3183f35ecef73ce8f7d47920d58abdf3766debc2ff0b4c653b7633707fd" # Testnet private key without funds 6 | EVER_ACCESS_KEY="" 7 | EVER_ACCESS_SECRET="" 8 | SHARED_SECRET="" 9 | -------------------------------------------------------------------------------- /apps/api/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /apps/api/env.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface ProcessEnv { 3 | NEXT_PUBLIC_LENS_NETWORK: string; 4 | DATABASE_URL: string; 5 | LENS_DATABASE_URL: string; 6 | REDIS_URL: string; 7 | PRIVATE_KEY: string; 8 | EVER_ACCESS_KEY: string; 9 | EVER_ACCESS_SECRET: string; 10 | SHARED_SECRET: string; 11 | LIVEPEER_KEY: string; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/api/railway.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://railway.com/railway.schema.json", 3 | "build": { 4 | "builder": "RAILPACK", 5 | "buildCommand": "pnpm --filter @hey/api build", 6 | "watchPatterns": ["apps/api/**", "packages/**/*"] 7 | }, 8 | "deploy": { 9 | "runtime": "V2", 10 | "startCommand": "pnpm --filter @hey/api start", 11 | "healthcheckPath": "/ping", 12 | "healthcheckTimeout": 60, 13 | "restartPolicyType": "ALWAYS", 14 | "sleepApplication": false, 15 | "limitOverride": { 16 | "containers": { 17 | "cpu": 5, 18 | "memoryBytes": 5000000000 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/api/src/context/authContext.ts: -------------------------------------------------------------------------------- 1 | import parseJwt from "@hey/helpers/parseJwt"; 2 | import type { Context, Next } from "hono"; 3 | 4 | const authContext = async (ctx: Context, next: Next) => { 5 | const token = ctx.req.raw.headers.get("X-Access-Token"); 6 | const payload = parseJwt(token as string); 7 | 8 | if (!payload.act.sub) { 9 | ctx.set("account", null); 10 | ctx.set("token", null); 11 | return next(); 12 | } 13 | 14 | ctx.set("account", payload.act.sub); 15 | ctx.set("token", token); 16 | return next(); 17 | }; 18 | 19 | export default authContext; 20 | -------------------------------------------------------------------------------- /apps/api/src/middlewares/authMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { LENS_API_URL } from "@hey/data/constants"; 2 | import type { Context, Next } from "hono"; 3 | import { createRemoteJWKSet, jwtVerify } from "jose"; 4 | 5 | const jwksUri = `${LENS_API_URL.replace("/graphql", "")}/.well-known/jwks.json`; 6 | // Cache the JWKS for 12 hours 7 | const JWKS = createRemoteJWKSet(new URL(jwksUri), { 8 | cacheMaxAge: 60 * 60 * 12 9 | }); 10 | 11 | const authMiddleware = async (c: Context, next: Next) => { 12 | const token = c.get("token"); 13 | 14 | if (!token) { 15 | return c.body("Unauthorized", 401); 16 | } 17 | 18 | try { 19 | await jwtVerify(token, JWKS); 20 | } catch { 21 | return c.body("Unauthorized", 401); 22 | } 23 | 24 | return next(); 25 | }; 26 | 27 | export default authMiddleware; 28 | -------------------------------------------------------------------------------- /apps/api/src/middlewares/cors.ts: -------------------------------------------------------------------------------- 1 | import { cors as corsMiddleware } from "hono/cors"; 2 | 3 | const allowedOrigins = [ 4 | "https://hey.xyz", 5 | "https://testnet.hey.xyz", 6 | "https://staging.hey.xyz", 7 | "http://localhost:4783", 8 | "https://developer.lens.xyz" 9 | ]; 10 | 11 | export const cors = corsMiddleware({ 12 | origin: allowedOrigins, 13 | allowHeaders: ["Content-Type", "X-Access-Token"], 14 | allowMethods: ["GET", "POST", "OPTIONS"], 15 | credentials: true 16 | }); 17 | 18 | export default cors; 19 | -------------------------------------------------------------------------------- /apps/api/src/middlewares/secretMiddleware.ts: -------------------------------------------------------------------------------- 1 | import type { Context, Next } from "hono"; 2 | 3 | const secretMiddleware = async (c: Context, next: Next) => { 4 | const secret = c.req.query("secret"); 5 | 6 | if (secret !== process.env.SHARED_SECRET) { 7 | return c.body("Unauthorized", 401); 8 | } 9 | 10 | return next(); 11 | }; 12 | 13 | export default secretMiddleware; 14 | -------------------------------------------------------------------------------- /apps/api/src/prisma/client.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | const prismaClientSingleton = () => { 4 | return new PrismaClient(); 5 | }; 6 | 7 | type PrismaClientSingleton = ReturnType; 8 | 9 | const globalForPrisma = globalThis as unknown as { 10 | prisma: PrismaClientSingleton | undefined; 11 | }; 12 | 13 | const prisma = globalForPrisma.prisma ?? prismaClientSingleton(); 14 | 15 | export default prisma; 16 | 17 | if (process.env.NODE_ENV !== "production") { 18 | globalForPrisma.prisma = prisma; 19 | } 20 | -------------------------------------------------------------------------------- /apps/api/src/prisma/migrations/20250513064236_remove_permissions/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `AccountPermission` table. If the table is not empty, all the data it contains will be lost. 5 | - You are about to drop the `Permission` table. If the table is not empty, all the data it contains will be lost. 6 | 7 | */ 8 | -- DropForeignKey 9 | ALTER TABLE "AccountPermission" DROP CONSTRAINT "AccountPermission_permissionId_fkey"; 10 | 11 | -- DropTable 12 | DROP TABLE "AccountPermission"; 13 | 14 | -- DropTable 15 | DROP TABLE "Permission"; 16 | 17 | -- DropEnum 18 | DROP TYPE "PermissionType"; 19 | -------------------------------------------------------------------------------- /apps/api/src/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (e.g., Git) 3 | provider = "postgresql" 4 | -------------------------------------------------------------------------------- /apps/api/src/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | url = env("DATABASE_URL") 8 | } 9 | 10 | model Preference { 11 | accountAddress String @id 12 | appIcon Int? @default(0) 13 | includeLowScore Boolean @default(false) 14 | createdAt DateTime @default(now()) 15 | } 16 | -------------------------------------------------------------------------------- /apps/api/src/routes/cron/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import secretMiddleware from "src/middlewares/secretMiddleware"; 3 | import syncFollowersStandingToGuild from "./guild/syncFollowersStandingToGuild"; 4 | import syncHQScoreAccountsToGuild from "./guild/syncHQScoreAccountsToGuild"; 5 | import syncSubscribersToGuild from "./guild/syncSubscribersToGuild"; 6 | import removeExpiredSubscribers from "./removeExpiredSubscribers"; 7 | 8 | const app = new Hono(); 9 | 10 | app.get("/syncSubscribersToGuild", secretMiddleware, syncSubscribersToGuild); 11 | app.get( 12 | "/syncHQScoreAccountsToGuild", 13 | secretMiddleware, 14 | syncHQScoreAccountsToGuild 15 | ); 16 | app.get( 17 | "/syncFollowersStandingToGuild", 18 | secretMiddleware, 19 | syncFollowersStandingToGuild 20 | ); 21 | app.get( 22 | "/removeExpiredSubscribers", 23 | secretMiddleware, 24 | removeExpiredSubscribers 25 | ); 26 | 27 | export default app; 28 | -------------------------------------------------------------------------------- /apps/api/src/routes/lens/authorization.ts: -------------------------------------------------------------------------------- 1 | import { Errors } from "@hey/data/errors"; 2 | import type { Context } from "hono"; 3 | 4 | const authorization = async (ctx: Context) => { 5 | try { 6 | const authHeader = ctx.req.raw.headers.get("authorization"); 7 | 8 | if (!authHeader || !authHeader.startsWith("Bearer ")) { 9 | return ctx.json({ success: false, error: "Unauthorized" }, 401); 10 | } 11 | 12 | const token = authHeader.split(" ")[1]; 13 | 14 | if (token !== process.env.SHARED_SECRET) { 15 | return ctx.json({ success: false, error: "Invalid shared secret" }, 401); 16 | } 17 | 18 | return ctx.json({ 19 | allowed: true, 20 | sponsored: true, 21 | signingKey: process.env.PRIVATE_KEY 22 | }); 23 | } catch { 24 | return ctx.json({ success: false, error: Errors.SomethingWentWrong }, 500); 25 | } 26 | }; 27 | 28 | export default authorization; 29 | -------------------------------------------------------------------------------- /apps/api/src/routes/lens/index.ts: -------------------------------------------------------------------------------- 1 | import { Regex } from "@hey/data/regex"; 2 | import { zValidator } from "@hono/zod-validator"; 3 | import { Hono } from "hono"; 4 | import { z } from "zod"; 5 | import authorization from "./authorization"; 6 | 7 | const app = new Hono(); 8 | 9 | app.post( 10 | "/authorization", 11 | zValidator( 12 | "json", 13 | z.object({ 14 | account: z.string().regex(Regex.evmAddress), 15 | signedBy: z.string().regex(Regex.evmAddress) 16 | }) 17 | ), 18 | authorization 19 | ); 20 | 21 | export default app; 22 | -------------------------------------------------------------------------------- /apps/api/src/routes/live/index.ts: -------------------------------------------------------------------------------- 1 | import { zValidator } from "@hono/zod-validator"; 2 | import { Hono } from "hono"; 3 | import authMiddleware from "src/middlewares/authMiddleware"; 4 | import rateLimiter from "src/middlewares/rateLimiter"; 5 | import { z } from "zod"; 6 | import createLive from "./createLive"; 7 | 8 | const app = new Hono(); 9 | 10 | app.post( 11 | "/create", 12 | rateLimiter({ requests: 10 }), 13 | authMiddleware, 14 | zValidator("json", z.object({ record: z.boolean() })), 15 | createLive 16 | ); 17 | 18 | export default app; 19 | -------------------------------------------------------------------------------- /apps/api/src/routes/metadata/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import rateLimiter from "src/middlewares/rateLimiter"; 3 | import getSTS from "./getSTS"; 4 | 5 | const app = new Hono(); 6 | 7 | app.get("/sts", rateLimiter({ requests: 50 }), getSTS); 8 | 9 | export default app; 10 | -------------------------------------------------------------------------------- /apps/api/src/routes/oembed/helpers/meta/getDescription.ts: -------------------------------------------------------------------------------- 1 | import getMetaContent from "./getMetaContent"; 2 | 3 | const getDescription = (document: Document): null | string => { 4 | const description = 5 | getMetaContent(document, "og:description") || 6 | getMetaContent(document, "twitter:description") || 7 | null; 8 | 9 | return description; 10 | }; 11 | 12 | export default getDescription; 13 | -------------------------------------------------------------------------------- /apps/api/src/routes/oembed/helpers/meta/getMetaContent.ts: -------------------------------------------------------------------------------- 1 | const getMetaContent = (document: Document, name: string): string | null => { 2 | const metaTag = 3 | document.querySelector(`meta[name="${name}"]`) || 4 | document.querySelector(`meta[property="${name}"]`); 5 | 6 | return metaTag ? metaTag.getAttribute("content") : null; 7 | }; 8 | 9 | export default getMetaContent; 10 | -------------------------------------------------------------------------------- /apps/api/src/routes/oembed/helpers/meta/getTitle.ts: -------------------------------------------------------------------------------- 1 | import getMetaContent from "./getMetaContent"; 2 | 3 | const getTitle = (document: Document): null | string => { 4 | const title = 5 | getMetaContent(document, "og:title") || 6 | getMetaContent(document, "twitter:title") || 7 | null; 8 | 9 | return title; 10 | }; 11 | 12 | export default getTitle; 13 | -------------------------------------------------------------------------------- /apps/api/src/routes/oembed/index.ts: -------------------------------------------------------------------------------- 1 | import { zValidator } from "@hono/zod-validator"; 2 | import { Hono } from "hono"; 3 | import rateLimiter from "src/middlewares/rateLimiter"; 4 | import { z } from "zod"; 5 | import getOembed from "./getOembed"; 6 | 7 | const app = new Hono(); 8 | 9 | app.get( 10 | "/get", 11 | rateLimiter({ requests: 500 }), 12 | zValidator("query", z.object({ url: z.string().url() })), 13 | getOembed 14 | ); 15 | 16 | export default app; 17 | -------------------------------------------------------------------------------- /apps/api/src/routes/og/index.ts: -------------------------------------------------------------------------------- 1 | import { Regex } from "@hey/data/regex"; 2 | import { zValidator } from "@hono/zod-validator"; 3 | import { Hono } from "hono"; 4 | import { z } from "zod"; 5 | import getAccount from "./getAccount"; 6 | import getGroup from "./getGroup"; 7 | import getPost from "./getPost"; 8 | 9 | const app = new Hono(); 10 | 11 | app.get( 12 | "/u/:username", 13 | zValidator("param", z.object({ username: z.string() })), 14 | getAccount 15 | ); 16 | 17 | app.get( 18 | "/posts/:slug", 19 | zValidator("param", z.object({ slug: z.string() })), 20 | getPost 21 | ); 22 | 23 | app.get( 24 | "/g/:address", 25 | zValidator( 26 | "param", 27 | z.object({ address: z.string().regex(Regex.evmAddress) }) 28 | ), 29 | getGroup 30 | ); 31 | 32 | export default app; 33 | -------------------------------------------------------------------------------- /apps/api/src/routes/ping.ts: -------------------------------------------------------------------------------- 1 | import type { Context } from "hono"; 2 | 3 | const ping = async (ctx: Context) => { 4 | return ctx.json({ ping: "pong" }); 5 | }; 6 | 7 | export default ping; 8 | -------------------------------------------------------------------------------- /apps/api/src/routes/preferences/index.ts: -------------------------------------------------------------------------------- 1 | import { zValidator } from "@hono/zod-validator"; 2 | import { Hono } from "hono"; 3 | import rateLimiter from "src/middlewares/rateLimiter"; 4 | import { z } from "zod"; 5 | import getPreferences from "./getPreferences"; 6 | import updatePreferences from "./updatePreferences"; 7 | 8 | const app = new Hono(); 9 | 10 | app.get("/get", rateLimiter({ requests: 100 }), getPreferences); 11 | app.post( 12 | "/update", 13 | rateLimiter({ requests: 50 }), 14 | zValidator( 15 | "json", 16 | z.object({ 17 | appIcon: z.number().optional(), 18 | includeLowScore: z.boolean().optional() 19 | }) 20 | ), 21 | updatePreferences 22 | ); 23 | 24 | export default app; 25 | -------------------------------------------------------------------------------- /apps/api/src/routes/sitemap/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono"; 2 | import accountSitemap from "./accounts/accountSitemap"; 3 | import accountsSitemapIndex from "./accounts/accountsSitemapIndex"; 4 | import pagesSitemap from "./pagesSitemap"; 5 | import sitemapIndex from "./sitemapIndex"; 6 | 7 | const app = new Hono(); 8 | 9 | app.get("/all.xml", sitemapIndex); 10 | app.get("/pages.xml", pagesSitemap); 11 | app.get("/accounts.xml", accountsSitemapIndex); 12 | app.get("/accounts/:batch.xml", accountSitemap); 13 | 14 | export default app; 15 | -------------------------------------------------------------------------------- /apps/api/src/routes/temp/getJumperData.ts: -------------------------------------------------------------------------------- 1 | import { Errors } from "@hey/data/errors"; 2 | import type { Context } from "hono"; 3 | 4 | const getJumperData = async (ctx: Context) => { 5 | // const { address, id } = await ctx.req.json(); 6 | 7 | try { 8 | return ctx.json({ success: true }); 9 | } catch { 10 | return ctx.json({ success: false, error: Errors.SomethingWentWrong }, 500); 11 | } 12 | }; 13 | 14 | export default getJumperData; 15 | -------------------------------------------------------------------------------- /apps/api/src/routes/temp/index.ts: -------------------------------------------------------------------------------- 1 | import { Regex } from "@hey/data/regex"; 2 | import { zValidator } from "@hono/zod-validator"; 3 | import { Hono } from "hono"; 4 | import { z } from "zod"; 5 | import getJumperData from "./getJumperData"; 6 | 7 | const app = new Hono(); 8 | 9 | app.post( 10 | "/jumper/get", 11 | zValidator( 12 | "json", 13 | z.object({ 14 | address: z.string().regex(Regex.evmAddress), 15 | id: z.string() 16 | }) 17 | ), 18 | getJumperData 19 | ); 20 | 21 | export default app; 22 | -------------------------------------------------------------------------------- /apps/api/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const HEY_USER_AGENT = "HeyBot (like TwitterBot)"; 2 | export const VERIFICATION_ENDPOINT = "https://api.hey.xyz/lens/verification"; 3 | export const SITEMAP_BATCH_SIZE = 25000; 4 | 5 | // Cache Settings 6 | // Cache settings for different durations 7 | export const CACHE_AGE_1_DAY = "public, s-maxage=86400, max-age=86400"; // Cache for 1 day 8 | -------------------------------------------------------------------------------- /apps/api/src/utils/sha256.ts: -------------------------------------------------------------------------------- 1 | import { createHash } from "node:crypto"; 2 | 3 | const sha256 = (text: string): string => { 4 | return createHash("sha256").update(text).digest("hex"); 5 | }; 6 | 7 | export default sha256; 8 | -------------------------------------------------------------------------------- /apps/api/src/utils/signer.ts: -------------------------------------------------------------------------------- 1 | import { LENS_MAINNET_RPCS } from "@hey/data/rpcs"; 2 | import { chains } from "@lens-chain/sdk/viem"; 3 | import { http, type Hex, createWalletClient } from "viem"; 4 | import { privateKeyToAccount } from "viem/accounts"; 5 | 6 | const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex); 7 | 8 | const signer = createWalletClient({ 9 | account, 10 | chain: chains.mainnet, 11 | transport: http(LENS_MAINNET_RPCS[0]) 12 | }); 13 | 14 | export default signer; 15 | -------------------------------------------------------------------------------- /apps/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@hey/config/base.tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "noEmit": false, 6 | "outDir": "dist", 7 | "baseUrl": "." 8 | }, 9 | "include": ["**/*.ts", "src"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/web/.env.example: -------------------------------------------------------------------------------- 1 | VITE_IS_PRODUCTION=false 2 | NEXT_PUBLIC_LENS_NETWORK="testnet" # mainnet, testnet, staging 3 | -------------------------------------------------------------------------------- /apps/web/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /apps/web/public/_headers: -------------------------------------------------------------------------------- 1 | /* 2 | X-Frame-Options: DENY 3 | X-XSS-Protection: 1; mode=block 4 | X-Content-Type-Options: nosniff 5 | 6 | /assets/* 7 | Cache-Control: public, max-age=31536000, immutable 8 | -------------------------------------------------------------------------------- /apps/web/public/_redirects: -------------------------------------------------------------------------------- 1 | /blog https://world.hey.com/yoginth 2 | /discord https://discord.gg/8TUjVmKkmM 3 | /status https://stats.uptimerobot.com/BRVJQID11O 4 | -------------------------------------------------------------------------------- /apps/web/public/_routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "include": ["/u/*", "/g/*", "/posts/*", "/sitemap.xml", "/sitemap/*"], 4 | "exclude": [] 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyverse/hey/1cbb0a61cd0d524cdf3ecc32a672175dc3f1335a/apps/web/public/favicon.ico -------------------------------------------------------------------------------- /apps/web/public/llms.txt: -------------------------------------------------------------------------------- 1 | # Hey.xyz llms.txt Integration File 2 | 3 | Hey is a decentralized, permissionless social media platform built on top of the Lens. It enables users to own their content, social graph, and identity on-chain—completely free from the control of centralized platforms. 4 | 5 | # Content Types 6 | 7 | - User account: https://hey.xyz/u/{username} 8 | - Post: https://hey.xyz/posts/{postSlug} 9 | - Group: https://hey.xyz/g/{groupAddress} 10 | 11 | Sitemap: https://hey.xyz/sitemap.xml 12 | Accounts Sitemap: https://hey.xyz/sitemap/accounts.xml 13 | -------------------------------------------------------------------------------- /apps/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | 4 | Sitemap: https://hey.xyz/sitemap.xml 5 | -------------------------------------------------------------------------------- /apps/web/src/assets/fonts/SofiaProSoftBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyverse/hey/1cbb0a61cd0d524cdf3ecc32a672175dc3f1335a/apps/web/src/assets/fonts/SofiaProSoftBold.woff2 -------------------------------------------------------------------------------- /apps/web/src/assets/fonts/SofiaProSoftMed.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyverse/hey/1cbb0a61cd0d524cdf3ecc32a672175dc3f1335a/apps/web/src/assets/fonts/SofiaProSoftMed.woff2 -------------------------------------------------------------------------------- /apps/web/src/assets/fonts/SofiaProSoftReg.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heyverse/hey/1cbb0a61cd0d524cdf3ecc32a672175dc3f1335a/apps/web/src/assets/fonts/SofiaProSoftReg.woff2 -------------------------------------------------------------------------------- /apps/web/src/components/Account/Menu/StaffTool.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { MenuItem } from "@headlessui/react"; 3 | import { ShieldCheckIcon } from "@heroicons/react/24/outline"; 4 | import type { AccountFragment } from "@hey/indexer"; 5 | import { Link } from "react-router"; 6 | 7 | interface StaffToolProps { 8 | account: AccountFragment; 9 | } 10 | 11 | const StaffTool = ({ account }: StaffToolProps) => { 12 | return ( 13 | 17 | cn( 18 | { "dropdown-active": focus }, 19 | "m-2 flex cursor-pointer items-center space-x-2 rounded-lg px-2 py-1.5 text-sm" 20 | ) 21 | } 22 | > 23 | 24 |
Staff tools
25 |
26 | ); 27 | }; 28 | 29 | export default StaffTool; 30 | -------------------------------------------------------------------------------- /apps/web/src/components/Account/MetaDetails.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from "react"; 2 | 3 | interface MetaDetailsProps { 4 | children: ReactNode; 5 | icon: ReactNode; 6 | } 7 | 8 | const MetaDetails = ({ children, icon }: MetaDetailsProps) => ( 9 |
10 | {icon} 11 |
{children}
12 |
13 | ); 14 | 15 | export default MetaDetails; 16 | -------------------------------------------------------------------------------- /apps/web/src/components/Bookmarks/index.tsx: -------------------------------------------------------------------------------- 1 | import NotLoggedIn from "@/components/Shared/NotLoggedIn"; 2 | import { PageLayout } from "@/components/Shared/PageLayout"; 3 | import ContentFeedType from "@/components/Shared/Post/ContentFeedType"; 4 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 5 | import type { MainContentFocus } from "@hey/indexer"; 6 | import { useState } from "react"; 7 | import BookmarksFeed from "./BookmarksFeed"; 8 | 9 | const Bookmarks = () => { 10 | const { currentAccount } = useAccountStore(); 11 | const [focus, setFocus] = useState(); 12 | 13 | if (!currentAccount) { 14 | return ; 15 | } 16 | 17 | return ( 18 | 19 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default Bookmarks; 30 | -------------------------------------------------------------------------------- /apps/web/src/components/Common/MetaTags.tsx: -------------------------------------------------------------------------------- 1 | interface MetaTagsProps { 2 | title?: string; 3 | } 4 | 5 | const MetaTags = ({ title = "Hey" }: MetaTagsProps) => { 6 | return {title}; 7 | }; 8 | 9 | export default MetaTags; 10 | -------------------------------------------------------------------------------- /apps/web/src/components/Common/Providers/Web3Provider.tsx: -------------------------------------------------------------------------------- 1 | import getRpc from "@/helpers/getRpc"; 2 | import { CHAIN, IS_MAINNET } from "@hey/data/constants"; 3 | import { familyAccountsConnector } from "family"; 4 | import type { ReactNode } from "react"; 5 | import { WagmiProvider, createConfig } from "wagmi"; 6 | import { injected } from "wagmi/connectors"; 7 | 8 | const connectors = [familyAccountsConnector(), injected()]; 9 | 10 | const config = createConfig({ 11 | chains: [CHAIN], 12 | transports: { 13 | [CHAIN.id]: getRpc({ mainnet: IS_MAINNET }) 14 | }, 15 | connectors 16 | }); 17 | 18 | interface Web3ProviderProps { 19 | children: ReactNode; 20 | } 21 | 22 | const Web3Provider = ({ children }: Web3ProviderProps) => { 23 | return {children}; 24 | }; 25 | 26 | export default Web3Provider; 27 | -------------------------------------------------------------------------------- /apps/web/src/components/Composer/Editor/EditorMenus.tsx: -------------------------------------------------------------------------------- 1 | import EmojiPicker from "./EmojiPicker"; 2 | import InlineMenu from "./InlineMenu"; 3 | import MentionPicker from "./MentionPicker"; 4 | 5 | const EditorMenus = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default EditorMenus; 16 | -------------------------------------------------------------------------------- /apps/web/src/components/Composer/Editor/index.tsx: -------------------------------------------------------------------------------- 1 | import Editor from "./Editor"; 2 | export { Editor }; 3 | export { useEditorContext, withEditorContext } from "./EditorHandle"; 4 | -------------------------------------------------------------------------------- /apps/web/src/components/Composer/LinkPreviews.tsx: -------------------------------------------------------------------------------- 1 | import Oembed from "@/components/Shared/Post/Oembed"; 2 | import getURLs from "@/helpers/getURLs"; 3 | import { usePostAttachmentStore } from "@/store/non-persisted/post/usePostAttachmentStore"; 4 | import { usePostStore } from "@/store/non-persisted/post/usePostStore"; 5 | 6 | const LinkPreviews = () => { 7 | const { postContent, quotedPost } = usePostStore(); 8 | const { attachments } = usePostAttachmentStore((state) => state); 9 | const urls = getURLs(postContent); 10 | 11 | if (!urls.length || attachments.length || quotedPost) { 12 | return null; 13 | } 14 | 15 | return ( 16 |
17 | 18 |
19 | ); 20 | }; 21 | 22 | export default LinkPreviews; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Groups/FeedType.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "@/components/Shared/UI"; 2 | import { GroupsFeedType } from "@hey/data/enums"; 3 | import type { Dispatch, SetStateAction } from "react"; 4 | 5 | interface FocusTypeProps { 6 | feedType: GroupsFeedType; 7 | setFeedType: Dispatch>; 8 | } 9 | 10 | const FeedType = ({ feedType, setFeedType }: FocusTypeProps) => { 11 | const tabs = [ 12 | { name: "Managed groups", type: GroupsFeedType.Managed }, 13 | { name: "Your groups", type: GroupsFeedType.Member } 14 | ]; 15 | 16 | return ( 17 | setFeedType(type as GroupsFeedType)} 21 | className="mx-5 mb-5 md:mx-0" 22 | layoutId="groups_tab" 23 | /> 24 | ); 25 | }; 26 | 27 | export default FeedType; 28 | -------------------------------------------------------------------------------- /apps/web/src/components/Home/FeedType.tsx: -------------------------------------------------------------------------------- 1 | import New from "@/components/Shared/Badges/New"; 2 | import { useHomeTabStore } from "@/store/persisted/useHomeTabStore"; 3 | import { HomeFeedType } from "@hey/data/enums"; 4 | import { Tabs } from "../Shared/UI"; 5 | 6 | const FeedType = () => { 7 | const { feedType, setFeedType } = useHomeTabStore(); 8 | 9 | const tabs = [ 10 | { name: "Following", type: HomeFeedType.FOLLOWING }, 11 | { name: "Highlights", type: HomeFeedType.HIGHLIGHTS }, 12 | { name: "For You", type: HomeFeedType.FORYOU, suffix: } 13 | ]; 14 | 15 | return ( 16 | setFeedType(type as HomeFeedType)} 20 | className="mx-5 mb-5 md:mx-0" 21 | layoutId="home_tab" 22 | /> 23 | ); 24 | }; 25 | 26 | export default FeedType; 27 | -------------------------------------------------------------------------------- /apps/web/src/components/Home/Hero.tsx: -------------------------------------------------------------------------------- 1 | import { STATIC_IMAGES_URL } from "@hey/data/constants"; 2 | 3 | const Hero = () => { 4 | return ( 5 |
13 |
14 |
Welcome to Hey
15 |
16 | a social network built on Lens 17 |
18 |
19 |
20 | ); 21 | }; 22 | 23 | export default Hero; 24 | -------------------------------------------------------------------------------- /apps/web/src/components/Home/Timeline/EventType/Reposted.tsx: -------------------------------------------------------------------------------- 1 | import Accounts from "@/components/Shared/Account/Accounts"; 2 | import { ArrowsRightLeftIcon } from "@heroicons/react/24/outline"; 3 | import type { RepostFragment } from "@hey/indexer"; 4 | 5 | interface RepostedProps { 6 | reposts: RepostFragment[]; 7 | } 8 | 9 | const Reposted = ({ reposts }: RepostedProps) => { 10 | const getRepostedAccounts = () => { 11 | let accounts = reposts.map((repost) => repost.author); 12 | accounts = accounts.filter( 13 | (account, index, self) => 14 | index === self.findIndex((t) => t.address === account.address) 15 | ); 16 | return accounts; 17 | }; 18 | 19 | return ( 20 |
21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | export default Reposted; 28 | -------------------------------------------------------------------------------- /apps/web/src/components/Notification/Shimmer.tsx: -------------------------------------------------------------------------------- 1 | const NotificationShimmer = () => { 2 | return ( 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ); 19 | }; 20 | 21 | export default NotificationShimmer; 22 | -------------------------------------------------------------------------------- /apps/web/src/components/Notification/index.tsx: -------------------------------------------------------------------------------- 1 | import NotLoggedIn from "@/components/Shared/NotLoggedIn"; 2 | import { PageLayout } from "@/components/Shared/PageLayout"; 3 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 4 | import { NotificationFeedType } from "@hey/data/enums"; 5 | import { useState } from "react"; 6 | import FeedType from "./FeedType"; 7 | import List from "./List"; 8 | 9 | const Notification = () => { 10 | const { currentAccount } = useAccountStore(); 11 | const [feedType, setFeedType] = useState( 12 | NotificationFeedType.All 13 | ); 14 | 15 | if (!currentAccount) { 16 | return ; 17 | } 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default Notification; 28 | -------------------------------------------------------------------------------- /apps/web/src/components/Post/HiddenPost.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from "@/components/Shared/UI"; 2 | 3 | interface HiddenPostProps { 4 | type?: string; 5 | } 6 | 7 | const HiddenPost = ({ type = "Post" }: HiddenPostProps) => { 8 | return ( 9 | 10 |
{type} was hidden by the author
11 |
12 | ); 13 | }; 14 | 15 | export default HiddenPost; 16 | -------------------------------------------------------------------------------- /apps/web/src/components/Post/Type/Commented.tsx: -------------------------------------------------------------------------------- 1 | import type { PostFragment } from "@hey/indexer"; 2 | import ThreadBody from "../ThreadBody"; 3 | 4 | interface CommentedProps { 5 | commentOn: PostFragment; 6 | } 7 | 8 | const Commented = ({ commentOn }: CommentedProps) => { 9 | return ; 10 | }; 11 | 12 | export default Commented; 13 | -------------------------------------------------------------------------------- /apps/web/src/components/Post/Type/Reposted.tsx: -------------------------------------------------------------------------------- 1 | import Accounts from "@/components/Shared/Account/Accounts"; 2 | import { ArrowsRightLeftIcon } from "@heroicons/react/24/outline"; 3 | import type { AccountFragment } from "@hey/indexer"; 4 | 5 | interface RepostedProps { 6 | account: AccountFragment; 7 | } 8 | 9 | const Reposted = ({ account }: RepostedProps) => { 10 | return ( 11 |
12 | 13 | 14 |
15 | ); 16 | }; 17 | 18 | export default Reposted; 19 | -------------------------------------------------------------------------------- /apps/web/src/components/Post/Type/index.tsx: -------------------------------------------------------------------------------- 1 | import stopEventPropagation from "@/helpers/stopEventPropagation"; 2 | import type { AnyPostFragment } from "@hey/indexer"; 3 | import Commented from "./Commented"; 4 | import Reposted from "./Reposted"; 5 | 6 | interface PostTypeProps { 7 | post: AnyPostFragment; 8 | showType: boolean; 9 | } 10 | 11 | const PostType = ({ post, showType }: PostTypeProps) => { 12 | const type = post.__typename; 13 | 14 | if (!showType) { 15 | return null; 16 | } 17 | 18 | return ( 19 | 20 | {type === "Repost" ? : null} 21 | {type === "Post" && post.commentOn ? ( 22 | 23 | ) : null} 24 | 25 | ); 26 | }; 27 | 28 | export default PostType; 29 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Blocked/index.tsx: -------------------------------------------------------------------------------- 1 | import BackButton from "@/components/Shared/BackButton"; 2 | import NotLoggedIn from "@/components/Shared/NotLoggedIn"; 3 | import { PageLayout } from "@/components/Shared/PageLayout"; 4 | import { Card, CardHeader } from "@/components/Shared/UI"; 5 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 6 | import List from "./List"; 7 | 8 | const BlockedSettings = () => { 9 | const { currentAccount } = useAccountStore(); 10 | 11 | if (!currentAccount) { 12 | return ; 13 | } 14 | 15 | return ( 16 | 17 | 18 | } 20 | title="Blocked accounts" 21 | /> 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default BlockedSettings; 29 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Developer/index.tsx: -------------------------------------------------------------------------------- 1 | import NotLoggedIn from "@/components/Shared/NotLoggedIn"; 2 | import { PageLayout } from "@/components/Shared/PageLayout"; 3 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 4 | import Tokens from "./Tokens"; 5 | 6 | const DeveloperSettings = () => { 7 | const { currentAccount } = useAccountStore(); 8 | 9 | if (!currentAccount) { 10 | return ; 11 | } 12 | 13 | return ( 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default DeveloperSettings; 21 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Funds/index.tsx: -------------------------------------------------------------------------------- 1 | import BackButton from "@/components/Shared/BackButton"; 2 | import NotLoggedIn from "@/components/Shared/NotLoggedIn"; 3 | import { PageLayout } from "@/components/Shared/PageLayout"; 4 | import { Card, CardHeader } from "@/components/Shared/UI"; 5 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 6 | import Balances from "./Balances"; 7 | 8 | const FundsSettings = () => { 9 | const { currentAccount } = useAccountStore(); 10 | 11 | if (!currentAccount) { 12 | return ; 13 | } 14 | 15 | return ( 16 | 17 | 18 | } 20 | title="Manage account balances" 21 | /> 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default FundsSettings; 29 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Manager/AccountManager/Management/Managed.tsx: -------------------------------------------------------------------------------- 1 | import List from "./List"; 2 | 3 | const Managed = () => { 4 | return ( 5 |
6 |
7 | Accounts under your oversight and management. 8 |
9 |
10 |
11 | 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default Managed; 18 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Manager/AccountManager/Management/Unmanaged.tsx: -------------------------------------------------------------------------------- 1 | import List from "./List"; 2 | 3 | const Unmanaged = () => { 4 | return ( 5 |
6 |
7 | Accounts under your oversight but not under your management. 8 |
9 |
10 |
11 | 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default Unmanaged; 18 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Manager/AccountManager/Managers/index.tsx: -------------------------------------------------------------------------------- 1 | import List from "./List"; 2 | 3 | const Managers = () => { 4 | return ( 5 |
6 |
7 | Accounts with control over your account can act on your behalf. 8 |
9 |
10 | 11 |
12 | ); 13 | }; 14 | 15 | export default Managers; 16 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Personalize/index.tsx: -------------------------------------------------------------------------------- 1 | import NotLoggedIn from "@/components/Shared/NotLoggedIn"; 2 | import { PageLayout } from "@/components/Shared/PageLayout"; 3 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 4 | import PersonalizeSettingsForm from "./Form"; 5 | 6 | const PersonalizeSettings = () => { 7 | const { currentAccount } = useAccountStore(); 8 | 9 | if (!currentAccount) { 10 | return ; 11 | } 12 | 13 | return ( 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default PersonalizeSettings; 21 | -------------------------------------------------------------------------------- /apps/web/src/components/Settings/Sessions/index.tsx: -------------------------------------------------------------------------------- 1 | import BackButton from "@/components/Shared/BackButton"; 2 | import NotLoggedIn from "@/components/Shared/NotLoggedIn"; 3 | import { PageLayout } from "@/components/Shared/PageLayout"; 4 | import { Card, CardHeader } from "@/components/Shared/UI"; 5 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 6 | import List from "./List"; 7 | 8 | const SessionsSettings = () => { 9 | const { currentAccount } = useAccountStore(); 10 | 11 | if (!currentAccount) { 12 | return ; 13 | } 14 | 15 | return ( 16 | 17 | 18 | } title="Sessions" /> 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default SessionsSettings; 26 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/404.tsx: -------------------------------------------------------------------------------- 1 | import { Button, H3 } from "@/components/Shared/UI"; 2 | import { Link } from "react-router"; 3 | import { PageLayout } from "./PageLayout"; 4 | 5 | const Custom404 = () => { 6 | return ( 7 | 8 |
9 |

Oops, Lost‽

10 |
This page could not be found.
11 | 12 | 13 | 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default Custom404; 20 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/500.tsx: -------------------------------------------------------------------------------- 1 | import { PageLayout } from "./PageLayout"; 2 | import SiteError from "./SiteError"; 3 | 4 | const Custom500 = () => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default Custom500; 13 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Account/Fund/TopUp/Button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/Shared/UI"; 2 | import { 3 | type FundingToken, 4 | useFundModalStore 5 | } from "@/store/non-persisted/modal/useFundModalStore"; 6 | 7 | interface TopUpButtonProps { 8 | size?: "sm" | "md"; 9 | outline?: boolean; 10 | className?: string; 11 | token?: FundingToken; 12 | label?: string; 13 | } 14 | 15 | const TopUpButton = ({ 16 | size = "md", 17 | outline = false, 18 | className = "", 19 | token, 20 | label = "Top-up your account" 21 | }: TopUpButtonProps) => { 22 | const { setShowFundModal } = useFundModalStore(); 23 | 24 | return ( 25 | 34 | ); 35 | }; 36 | 37 | export default TopUpButton; 38 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Audio/Player.tsx: -------------------------------------------------------------------------------- 1 | import type { APITypes } from "plyr-react"; 2 | import Plyr from "plyr-react"; 3 | import "plyr-react/plyr.css"; 4 | import type { Ref } from "react"; 5 | import { memo } from "react"; 6 | 7 | interface PlayerProps { 8 | playerRef: Ref; 9 | src: string; 10 | } 11 | 12 | const Player = ({ playerRef, src }: PlayerProps) => { 13 | return ( 14 | 21 | ); 22 | }; 23 | 24 | export default memo(Player); 25 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Auth/AuthMessage.tsx: -------------------------------------------------------------------------------- 1 | import { H4 } from "@/components/Shared/UI"; 2 | 3 | interface AuthMessageProps { 4 | description: string; 5 | title: string; 6 | } 7 | 8 | const AuthMessage = ({ description, title }: AuthMessageProps) => ( 9 |
10 |

{title}

11 |
12 | {description} 13 |
14 |
15 | ); 16 | 17 | export default AuthMessage; 18 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/BackButton.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowLeftIcon } from "@heroicons/react/24/outline"; 2 | import { useNavigate, useNavigationType } from "react-router"; 3 | 4 | interface BackButtonProps { 5 | path?: string; 6 | } 7 | 8 | const BackButton = ({ path }: BackButtonProps) => { 9 | const navigate = useNavigate(); 10 | const navType = useNavigationType(); 11 | 12 | const handleBack = () => { 13 | if (path) { 14 | navigate(path); 15 | } else if (navType === "POP") { 16 | navigate("/"); 17 | } else { 18 | navigate(-1); 19 | } 20 | }; 21 | 22 | return ( 23 | 30 | ); 31 | }; 32 | 33 | export default BackButton; 34 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Badges/Alpha.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@/components/Shared/UI"; 2 | import { PuzzlePieceIcon } from "@heroicons/react/24/outline"; 3 | 4 | const Alpha = () => { 5 | return ( 6 | 7 | 8 | Alpha 🤫 9 | 10 | ); 11 | }; 12 | 13 | export default Alpha; 14 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Badges/Beta.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@/components/Shared/UI"; 2 | import { StarIcon } from "@heroicons/react/24/solid"; 3 | 4 | const Beta = () => { 5 | return ( 6 | 7 | 8 | Beta 9 | 10 | ); 11 | }; 12 | 13 | export default Beta; 14 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Badges/New.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@/components/Shared/UI"; 2 | import { SparklesIcon } from "@heroicons/react/24/solid"; 3 | 4 | const New = () => { 5 | return ( 6 | 7 | 8 |
New
9 |
10 | ); 11 | }; 12 | 13 | export default New; 14 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Embed/Quote.tsx: -------------------------------------------------------------------------------- 1 | import QuotedPost from "@/components/Post/QuotedPost"; 2 | import type { PostFragment } from "@hey/indexer"; 3 | import Wrapper from "./Wrapper"; 4 | 5 | interface QuoteProps { 6 | post: PostFragment; 7 | } 8 | 9 | const Quote = ({ post }: QuoteProps) => { 10 | if (!post) { 11 | return null; 12 | } 13 | 14 | return ( 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default Quote; 22 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Embed/Wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from "@/components/Shared/UI"; 2 | import cn from "@/helpers/cn"; 3 | import stopEventPropagation from "@/helpers/stopEventPropagation"; 4 | import type { ReactNode } from "react"; 5 | 6 | interface WrapperProps { 7 | children: ReactNode; 8 | className?: string; 9 | zeroPadding?: boolean; 10 | } 11 | 12 | const Wrapper = ({ 13 | children, 14 | className = "", 15 | zeroPadding = false 16 | }: WrapperProps) => ( 17 | 22 | {children} 23 | 24 | ); 25 | 26 | export default Wrapper; 27 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/FullPageLoader.tsx: -------------------------------------------------------------------------------- 1 | import { Image } from "@/components/Shared/UI"; 2 | import { STATIC_IMAGES_URL } from "@hey/data/constants"; 3 | 4 | const FullPageLoader = () => { 5 | return ( 6 |
7 | Logo 14 |
15 | ); 16 | }; 17 | 18 | export default FullPageLoader; 19 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/GlobalAlerts.tsx: -------------------------------------------------------------------------------- 1 | import { useBlockAlertStore } from "@/store/non-persisted/alert/useBlockAlertStore"; 2 | import { useMuteAlertStore } from "@/store/non-persisted/alert/useMuteAlertStore"; 3 | import BlockOrUnblockAccount from "./Alert/BlockOrUnblockAccount"; 4 | import DeletePost from "./Alert/DeletePost"; 5 | import MuteOrUnmuteAccount from "./Alert/MuteOrUnmuteAccount"; 6 | 7 | const GlobalAlerts = () => { 8 | const { mutingOrUnmutingAccount } = useMuteAlertStore(); 9 | const { blockingorUnblockingAccount } = useBlockAlertStore(); 10 | 11 | return ( 12 | <> 13 | 14 | {blockingorUnblockingAccount && } 15 | {mutingOrUnmutingAccount && } 16 | 17 | ); 18 | }; 19 | 20 | export default GlobalAlerts; 21 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Loader.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "@/components/Shared/UI"; 2 | import cn from "@/helpers/cn"; 3 | 4 | interface LoaderProps { 5 | className?: string; 6 | message?: string; 7 | small?: boolean; 8 | } 9 | 10 | const Loader = ({ className = "", message, small = false }: LoaderProps) => { 11 | return ( 12 |
13 | 14 | {message &&
{message}
} 15 |
16 | ); 17 | }; 18 | 19 | export default Loader; 20 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Markup/Code.tsx: -------------------------------------------------------------------------------- 1 | const Code = (props: any) => { 2 | return ( 3 | 7 | ); 8 | }; 9 | 10 | export default Code; 11 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Markup/MarkupLink/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import injectReferrerToUrl from "@/helpers/injectReferrerToUrl"; 2 | import stopEventPropagation from "@/helpers/stopEventPropagation"; 3 | import truncateUrl from "@/helpers/truncateUrl"; 4 | import type { MarkupLinkProps } from "@hey/types/misc"; 5 | import { Link } from "react-router"; 6 | 7 | const ExternalLink = ({ title }: MarkupLinkProps) => { 8 | let href = title; 9 | 10 | if (!href) { 11 | return null; 12 | } 13 | 14 | if (!href.includes("://")) { 15 | href = `https://${href}`; 16 | } 17 | 18 | const url = injectReferrerToUrl(href); 19 | 20 | return ( 21 | 27 | {title ? truncateUrl(title, 30) : title} 28 | 29 | ); 30 | }; 31 | 32 | export default ExternalLink; 33 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Markup/MarkupLink/Hashtag.tsx: -------------------------------------------------------------------------------- 1 | import stopEventPropagation from "@/helpers/stopEventPropagation"; 2 | import type { MarkupLinkProps } from "@hey/types/misc"; 3 | import { Link } from "react-router"; 4 | 5 | const Hashtag = ({ title }: MarkupLinkProps) => { 6 | if (!title) { 7 | return null; 8 | } 9 | 10 | return ( 11 | 12 | 13 | 18 | {title} 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default Hashtag; 26 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Markup/MarkupLink/index.tsx: -------------------------------------------------------------------------------- 1 | import type { MarkupLinkProps } from "@hey/types/misc"; 2 | import ExternalLink from "./ExternalLink"; 3 | import Hashtag from "./Hashtag"; 4 | import Mention from "./Mention"; 5 | 6 | const MarkupLink = ({ mentions, title }: MarkupLinkProps) => { 7 | if (!title) { 8 | return null; 9 | } 10 | 11 | if (title.startsWith("@")) { 12 | return ; 13 | } 14 | 15 | if (title.startsWith("#")) { 16 | return ; 17 | } 18 | 19 | return ; 20 | }; 21 | 22 | export default MarkupLink; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/MenuTransition.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import type { ReactNode } from "react"; 3 | import { Fragment } from "react"; 4 | 5 | interface MenuTransitionProps { 6 | children: ReactNode; 7 | show?: boolean; 8 | } 9 | 10 | const MenuTransition = ({ children, show }: MenuTransitionProps) => { 11 | return ( 12 | 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export default MenuTransition; 28 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/Bookmarks.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { BookmarkIcon } from "@heroicons/react/24/outline"; 3 | 4 | interface BookmarksProps { 5 | className?: string; 6 | } 7 | 8 | const Bookmarks = ({ className = "" }: BookmarksProps) => { 9 | return ( 10 |
16 | 17 |
Bookmarks
18 |
19 | ); 20 | }; 21 | 22 | export default Bookmarks; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/Groups.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { UserGroupIcon } from "@heroicons/react/24/outline"; 3 | 4 | interface GroupsProps { 5 | className?: string; 6 | } 7 | 8 | const Groups = ({ className = "" }: GroupsProps) => { 9 | return ( 10 |
16 | 17 |
Groups
18 |
19 | ); 20 | }; 21 | 22 | export default Groups; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/Pro.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip } from "@/components/Shared/UI"; 2 | import { useProModalStore } from "@/store/non-persisted/modal/useProModalStore"; 3 | import { CheckBadgeIcon } from "@heroicons/react/24/outline"; 4 | 5 | const Pro = () => { 6 | const { setShowProModal } = useProModalStore(); 7 | 8 | return ( 9 | 14 | ); 15 | }; 16 | 17 | export default Pro; 18 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/Settings.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { Cog6ToothIcon } from "@heroicons/react/24/outline"; 3 | 4 | interface SettingsProps { 5 | className?: string; 6 | } 7 | 8 | const Settings = ({ className = "" }: SettingsProps) => { 9 | return ( 10 |
16 | 17 |
Settings
18 |
19 | ); 20 | }; 21 | 22 | export default Settings; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/StaffTools.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { ShieldCheckIcon } from "@heroicons/react/24/outline"; 3 | 4 | interface StaffToolsProps { 5 | className?: string; 6 | } 7 | 8 | const StaffTools = ({ className = "" }: StaffToolsProps) => { 9 | return ( 10 |
16 | 17 |
Staff Tools
18 |
19 | ); 20 | }; 21 | 22 | export default StaffTools; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/Support.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { HandRaisedIcon } from "@heroicons/react/24/outline"; 3 | 4 | interface SupportProps { 5 | className?: string; 6 | } 7 | 8 | const Support = ({ className = "" }: SupportProps) => { 9 | return ( 10 |
16 | 17 |
Support
18 |
19 | ); 20 | }; 21 | 22 | export default Support; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/SwitchAccount.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { useSwitchAccountModalStore } from "@/store/non-persisted/modal/useSwitchAccountModalStore"; 3 | import { ArrowsRightLeftIcon } from "@heroicons/react/24/outline"; 4 | 5 | interface SwitchAccountProps { 6 | className?: string; 7 | } 8 | 9 | const SwitchAccount = ({ className = "" }: SwitchAccountProps) => { 10 | const { setShowSwitchAccountModal } = useSwitchAccountModalStore(); 11 | 12 | return ( 13 | 24 | ); 25 | }; 26 | 27 | export default SwitchAccount; 28 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/NavItems/YourAccount.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | import { UserIcon } from "@heroicons/react/24/outline"; 3 | 4 | interface YourAccountProps { 5 | className?: string; 6 | } 7 | 8 | const YourAccount = ({ className = "" }: YourAccountProps) => { 9 | return ( 10 |
16 | 17 |
Your account
18 |
19 | ); 20 | }; 21 | 22 | export default YourAccount; 23 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Navbar/SignupButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/Shared/UI"; 2 | import { useAuthModalStore } from "@/store/non-persisted/modal/useAuthModalStore"; 3 | import { useSignupStore } from "../Auth/Signup"; 4 | 5 | interface SignupButtonProps { 6 | className?: string; 7 | } 8 | 9 | const SignupButton = ({ className }: SignupButtonProps) => { 10 | const { setShowAuthModal } = useAuthModalStore(); 11 | const { setScreen } = useSignupStore(); 12 | 13 | return ( 14 | 25 | ); 26 | }; 27 | 28 | export default SignupButton; 29 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/NoBalanceError.tsx: -------------------------------------------------------------------------------- 1 | interface NoBalanceErrorProps { 2 | assetSymbol?: string; 3 | } 4 | 5 | const NoBalanceError = ({ assetSymbol }: NoBalanceErrorProps) => { 6 | return ( 7 |
8 | You don't have enough {assetSymbol || "funds"} 9 |
10 | ); 11 | }; 12 | 13 | export default NoBalanceError; 14 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/NotLoggedIn.tsx: -------------------------------------------------------------------------------- 1 | import LoginButton from "@/components/Shared/LoginButton"; 2 | import { H3 } from "@/components/Shared/UI"; 3 | import Footer from "./Footer"; 4 | import { PageLayout } from "./PageLayout"; 5 | 6 | const NotLoggedIn = () => { 7 | return ( 8 | }> 9 |
10 |

Not logged in!

11 |
Log in to continue
12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default NotLoggedIn; 19 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Post/CommentWarning.tsx: -------------------------------------------------------------------------------- 1 | import { Card, H6 } from "@/components/Shared/UI"; 2 | import { ChatBubbleLeftIcon } from "@heroicons/react/24/outline"; 3 | 4 | const CommentWarning = () => { 5 | return ( 6 | 7 | 8 |
You can't reply to this post
9 |
10 | ); 11 | }; 12 | 13 | export default CommentWarning; 14 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Post/Oembed/EmptyOembed.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from "@/components/Shared/UI"; 2 | import { LinkIcon } from "@heroicons/react/24/outline"; 3 | 4 | interface EmptyOembedProps { 5 | url: string; 6 | hideLoader?: boolean; 7 | } 8 | 9 | const EmptyOembed = ({ url, hideLoader = false }: EmptyOembedProps) => { 10 | return ( 11 | 12 |
13 |
14 | 15 | {url} 16 |
17 | {!hideLoader && ( 18 |
Loading...
19 | )} 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default EmptyOembed; 26 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Post/Oembed/index.tsx: -------------------------------------------------------------------------------- 1 | import { hono } from "@/helpers/fetcher"; 2 | import { useQuery } from "@tanstack/react-query"; 3 | import Embed from "./Embed"; 4 | import EmptyOembed from "./EmptyOembed"; 5 | 6 | interface OembedProps { 7 | url: string; 8 | } 9 | 10 | const Oembed = ({ url }: OembedProps) => { 11 | const { data, error, isLoading } = useQuery({ 12 | queryFn: () => hono.oembed.get(url), 13 | queryKey: ["oembed", url], 14 | enabled: Boolean(url) 15 | }); 16 | 17 | if (isLoading || error || !data) { 18 | if (error) { 19 | return null; 20 | } 21 | 22 | return ; 23 | } 24 | 25 | const og = { 26 | description: data?.description, 27 | title: data?.title, 28 | url: url as string 29 | }; 30 | 31 | if (!og.title) { 32 | return null; 33 | } 34 | 35 | return ; 36 | }; 37 | 38 | export default Oembed; 39 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Post/PostLink.tsx: -------------------------------------------------------------------------------- 1 | import { usePostLinkStore } from "@/store/non-persisted/navigation/usePostLinkStore"; 2 | import type { AnyPostFragment } from "@hey/indexer"; 3 | import type { ComponentProps, ReactNode } from "react"; 4 | import { Link } from "react-router"; 5 | 6 | interface PostLinkProps extends Omit, "to"> { 7 | post: AnyPostFragment; 8 | children: ReactNode; 9 | } 10 | 11 | const PostLink = ({ post, children, onClick, ...props }: PostLinkProps) => { 12 | const { setCachedPost } = usePostLinkStore(); 13 | 14 | return ( 15 | { 19 | setCachedPost(post); 20 | onClick?.(e); 21 | }} 22 | > 23 | {children} 24 | 25 | ); 26 | }; 27 | 28 | export default PostLink; 29 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Post/PostWarning.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from "../UI"; 2 | 3 | interface PostWarningProps { 4 | message: string; 5 | } 6 | 7 | const PostWarning = ({ message }: PostWarningProps) => { 8 | return ( 9 | 10 |
{message}
11 |
12 | ); 13 | }; 14 | 15 | export default PostWarning; 16 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Settings/WrongWallet.tsx: -------------------------------------------------------------------------------- 1 | import { H5 } from "@/components/Shared/UI"; 2 | import WalletSelector from "../Auth/WalletSelector"; 3 | 4 | const WrongWallet = () => { 5 | return ( 6 |
7 |
8 |
Switch to correct wallet
9 |

10 | You need to switch to correct wallet to manage this account. Please 11 | switch to the correct wallet to manage this account. 12 |

13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | export default WrongWallet; 20 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Shimmer/AccountListShimmer.tsx: -------------------------------------------------------------------------------- 1 | import SingleAccountShimmer from "./SingleAccountShimmer"; 2 | 3 | const AccountListShimmer = () => { 4 | return ( 5 |
6 | {Array.from({ length: 5 }).map((_, index) => ( 7 | 12 | ))} 13 |
14 | ); 15 | }; 16 | 17 | export default AccountListShimmer; 18 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Shimmer/FollowersYouKnowShimmer.tsx: -------------------------------------------------------------------------------- 1 | const FollowersYouKnowShimmer = () => { 2 | return ( 3 |
4 |
5 | {Array.from({ length: 3 }).map((_, index) => ( 6 |
7 | ))} 8 |
9 |
10 |
11 | ); 12 | }; 13 | 14 | export default FollowersYouKnowShimmer; 15 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Shimmer/GraphStatsShimmer.tsx: -------------------------------------------------------------------------------- 1 | interface GraphStatsShimmerProps { 2 | count: number; 3 | } 4 | 5 | const GraphStatsShimmer = ({ count }: GraphStatsShimmerProps) => { 6 | return ( 7 |
8 | {Array.from({ length: count }).map((_, index) => ( 9 |
10 |
11 |
12 |
13 | ))} 14 |
15 | ); 16 | }; 17 | 18 | export default GraphStatsShimmer; 19 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Shimmer/GroupListShimmer.tsx: -------------------------------------------------------------------------------- 1 | import SingleGroupShimmer from "./SingleGroupShimmer"; 2 | 3 | const GroupListShimmer = () => { 4 | return ( 5 |
6 | {Array.from({ length: 5 }).map((_, index) => ( 7 | 8 | ))} 9 |
10 | ); 11 | }; 12 | 13 | export default GroupListShimmer; 14 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Shimmer/PostsShimmer.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from "@/components/Shared/UI"; 2 | import cn from "@/helpers/cn"; 3 | import PostShimmer from "./PostShimmer"; 4 | 5 | interface PostsShimmerProps { 6 | hideCard?: boolean; 7 | } 8 | 9 | const PostsShimmer = ({ hideCard = false }: PostsShimmerProps) => { 10 | return ( 11 | 17 | {Array.from({ length: 3 }).map((_, index) => ( 18 | 19 | ))} 20 | 21 | ); 22 | }; 23 | 24 | export default PostsShimmer; 25 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Shimmer/SmallSingleAccountShimmer.tsx: -------------------------------------------------------------------------------- 1 | import cn from "@/helpers/cn"; 2 | 3 | interface SmallSingleAccountShimmerProps { 4 | hideSlug?: boolean; 5 | smallAvatar?: boolean; 6 | } 7 | 8 | const SmallSingleAccountShimmer = ({ 9 | hideSlug = false, 10 | smallAvatar = false 11 | }: SmallSingleAccountShimmerProps) => { 12 | return ( 13 |
14 |
20 |
21 | {!hideSlug &&
} 22 |
23 | ); 24 | }; 25 | 26 | export default SmallSingleAccountShimmer; 27 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Shimmer/ThumbnailsShimmer.tsx: -------------------------------------------------------------------------------- 1 | import { THUMBNAIL_GENERATE_COUNT } from "@/components/Composer/ChooseThumbnail"; 2 | import { useMemo } from "react"; 3 | 4 | const ThumbnailsShimmer = () => { 5 | const thumbnails = useMemo(() => Array(THUMBNAIL_GENERATE_COUNT).fill(1), []); 6 | 7 | return ( 8 | <> 9 | {thumbnails.map((e, i) => ( 10 |
11 | ))} 12 | 13 | ); 14 | }; 15 | 16 | export default ThumbnailsShimmer; 17 | -------------------------------------------------------------------------------- /apps/web/src/components/Shared/Sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import SignupCard from "@/components/Shared/Auth/SignupCard"; 2 | import Footer from "@/components/Shared/Footer"; 3 | import { useAccountStore } from "@/store/persisted/useAccountStore"; 4 | import { memo } from "react"; 5 | import WhoToFollow from "./WhoToFollow"; 6 | 7 | const Sidebar = () => { 8 | const { currentAccount } = useAccountStore(); 9 | const loggedInWithAccount = Boolean(currentAccount); 10 | const loggedOut = !loggedInWithAccount; 11 | 12 | return ( 13 | <> 14 | {loggedOut && } 15 | {loggedInWithAccount && ( 16 | <> 17 | {/* */} 18 | 19 | 20 | )} 21 |