├── .codesandbox └── tasks.json ├── .devcontainer └── devcontainer.json ├── .eslintrc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .lighthouserc.js ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── config ├── README.md ├── default.json ├── dev.json └── nestr.json ├── custom.d.ts ├── docker-compose.yml ├── index.html ├── package.json ├── playwright.config.ts ├── postcss.config.js ├── public ├── .well-known │ └── assetlinks.json ├── _redirects ├── cashu │ ├── assets │ │ ├── AlreadyRunning.3bb200a1.js │ │ ├── AndroidPWAPrompt.302a1c60.js │ │ ├── AndroidPWAPrompt.eb57d776.css │ │ ├── BlankLayout.41d1327b.js │ │ ├── ErrorNotFound.0a671c4a.js │ │ ├── FullscreenLayout.02ec423b.js │ │ ├── KFOkCnqEu92Fr1MmgVxIIzQ.34e9582c.woff │ │ ├── KFOlCnqEu92Fr1MmEU9fBBc-.9ce7f3ac.woff │ │ ├── KFOlCnqEu92Fr1MmSU5fBBc-.bf14c7d7.woff │ │ ├── KFOlCnqEu92Fr1MmWUlfBBc-.e0fd57c0.woff │ │ ├── KFOlCnqEu92Fr1MmYUtfBBc-.f6537e32.woff │ │ ├── KFOmCnqEu92Fr1Mu4mxM.f2abf7fb.woff │ │ ├── MainLayout.87d4a103.js │ │ ├── MainLayout.aee306ae.css │ │ ├── QHeader.76ba7108.js │ │ ├── QInput.265349d8.js │ │ ├── QItem.18ca9374.js │ │ ├── QLayout.098b1f25.js │ │ ├── QLinearProgress.00741bf9.js │ │ ├── QList.1f08d45c.js │ │ ├── QResizeObserver.d45b2ee0.js │ │ ├── QSpinnerHourglass.12e6d1e7.js │ │ ├── QToolbar.4ef97f48.js │ │ ├── QToolbarTitle.7349d924.js │ │ ├── Restore.ceb30b82.js │ │ ├── Settings.b7424e6d.js │ │ ├── Settings.f9af94b6.css │ │ ├── TermsPage.8329ea15.css │ │ ├── TermsPage.ae1dd3e0.js │ │ ├── WalletPage.072ef1f1.js │ │ ├── WalletPage.d7bc9a06.css │ │ ├── WelcomePage.9146aeed.js │ │ ├── WelcomePage.b07b1606.css │ │ ├── WelcomeSlide4.4f3ce9ed.js │ │ ├── WelcomeSlide4.8505cd1c.css │ │ ├── base.72e2c220.js │ │ ├── cashu.5d75870c.js │ │ ├── flUhRq6tzZclQEJ-Vdg-IuiaDsNa.fd84f88b.woff │ │ ├── flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.4a4dbc62.woff2 │ │ ├── focusout.be8db32e.js │ │ ├── global-components.f92d7ae1.js │ │ ├── index.14146baf.css │ │ ├── index.42f06193.js │ │ ├── index.916cb52e.js │ │ ├── material-icons-v50.fbba257d.woff2 │ │ ├── npubcash.daddea67.js │ │ ├── private.use-form.be065f06.js │ │ ├── qr-scanner-worker.min.56d417f3.js │ │ ├── restore.025c481c.js │ │ ├── scroll.b2adc88e.js │ │ ├── selection.7b8f2356.js │ │ ├── touch.9135741d.js │ │ ├── ui.b6121134.js │ │ ├── use-checkbox.bd46c6a9.js │ │ ├── use-timeout.c52bf43b.js │ │ ├── vue-qrcode.esm.c5e039a4.js │ │ ├── web.050b05b0.js │ │ ├── web.e3bae036.js │ │ └── welcome.62644ccc.js │ ├── clean.png │ ├── favicon.ico │ ├── icons │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-167x167.png │ │ ├── apple-icon-180x180.png │ │ ├── apple-launch-1080x2340.png │ │ ├── apple-launch-1125x2436.png │ │ ├── apple-launch-1170x2532.png │ │ ├── apple-launch-1179x2556.png │ │ ├── apple-launch-1242x2208.png │ │ ├── apple-launch-1242x2688.png │ │ ├── apple-launch-1284x2778.png │ │ ├── apple-launch-1290x2796.png │ │ ├── apple-launch-1536x2048.png │ │ ├── apple-launch-1620x2160.png │ │ ├── apple-launch-1668x2224.png │ │ ├── apple-launch-1668x2388.png │ │ ├── apple-launch-2048x2732.png │ │ ├── apple-launch-750x1334.png │ │ ├── apple-launch-828x1792.png │ │ ├── favicon-128x128.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── icon-128x128.png │ │ ├── icon-192x192.png │ │ ├── icon-256x256.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── ms-icon-144x144.png │ │ └── safari-pinned-tab.svg │ ├── index.html │ ├── nostr-icon.svg │ ├── screenshots │ │ ├── narrow-1.png │ │ ├── narrow-2.png │ │ ├── wide-1.png │ │ └── wide-2.png │ └── x-logo.svg ├── clean.png ├── favicon.png ├── img │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── icon128.png │ ├── irisconnects.png │ ├── maskable_icon.png │ ├── maskable_icon_x192.png │ └── zap.png ├── manifest.json └── robots.txt ├── scripts └── updateSocialGraph.ts ├── src ├── assets │ ├── Bitcoin.png │ ├── alby-logo.avif │ ├── banner.jpg │ ├── chrome-logo.png │ ├── default-repo-pic.webp │ ├── default_profile_pic.jpg │ ├── feed-icon.png │ ├── firefox-logo.png │ ├── git-logo.png │ ├── highlights-icon.png │ ├── hornet-storage.png │ ├── landing-page-img-coder.avif │ ├── landing-page-img-nostrich.avif │ ├── long-form-icon.png │ ├── nestr-logo.png │ ├── note-icon.png │ ├── quotes-icon.png │ ├── reels-icon.png │ ├── running-ostrich.gif │ ├── satoshi-white.png │ ├── satoshi.svg │ ├── wallet-icon.png │ └── wallet-icon.svg ├── index.css ├── main.tsx ├── pages │ ├── HelpPage.tsx │ ├── NostrLinkHandler.tsx │ ├── Page404.tsx │ ├── chats │ │ ├── NewChat.tsx │ │ ├── components │ │ │ └── ChatContainer.tsx │ │ ├── index.tsx │ │ ├── list │ │ │ ├── ChatList.tsx │ │ │ └── ChatListItem.tsx │ │ ├── message │ │ │ ├── Message.tsx │ │ │ ├── MessageForm.tsx │ │ │ ├── MessageFormReplyPreview.tsx │ │ │ └── ReplyPreview.tsx │ │ ├── private │ │ │ ├── PrivateChat.tsx │ │ │ ├── PrivateChatCreation.tsx │ │ │ └── PrivateChatHeader.tsx │ │ ├── public │ │ │ ├── PopularChannelItem.tsx │ │ │ ├── PopularChannels.tsx │ │ │ ├── PublicChat.tsx │ │ │ ├── PublicChatContext.tsx │ │ │ ├── PublicChatCreation.tsx │ │ │ ├── PublicChatDetails.tsx │ │ │ └── PublicChatHeader.tsx │ │ ├── reaction │ │ │ ├── MessageReactionButton.tsx │ │ │ └── MessageReactions.tsx │ │ └── utils │ │ │ ├── channelMetadata.ts │ │ │ ├── channelSearch.ts │ │ │ ├── constants.ts │ │ │ ├── doubleRatchetUsers.ts │ │ │ └── messageGrouping.ts │ ├── home │ │ ├── feed │ │ │ └── components │ │ │ │ ├── HomeFeedEvents.tsx │ │ │ │ ├── Wallet.tsx │ │ │ │ └── WalletFeedItem.tsx │ │ └── index.tsx │ ├── index.tsx │ ├── notifications │ │ ├── Notifications.tsx │ │ └── NotificationsFeedItem.tsx │ ├── search │ │ └── index.tsx │ ├── settings │ │ ├── Account.tsx │ │ ├── Appearance.tsx │ │ ├── Backup.tsx │ │ ├── Content.tsx │ │ ├── IrisAccount │ │ │ ├── AccountName.tsx │ │ │ ├── ActiveAccount.tsx │ │ │ ├── ChallengeForm.tsx │ │ │ ├── IrisAccount.tsx │ │ │ ├── IrisSettings.tsx │ │ │ └── RegisterForm.tsx │ │ ├── Mediaservers.tsx │ │ ├── Network.tsx │ │ ├── NotificationSettings.tsx │ │ ├── Privacy.tsx │ │ ├── Profile.tsx │ │ ├── SocialGraphSettings.tsx │ │ ├── SystemSettings.tsx │ │ ├── WalletSettings.tsx │ │ └── index.tsx │ ├── subscription │ │ └── index.tsx │ ├── thread │ │ └── index.tsx │ ├── user │ │ ├── ProfileHeader.tsx │ │ ├── components │ │ │ ├── FollowList.tsx │ │ │ ├── FollowerCount.tsx │ │ │ ├── FollowsCount.tsx │ │ │ ├── MediaTab.tsx │ │ │ ├── ProfileAvatar.tsx │ │ │ ├── ProfileDetails.tsx │ │ │ ├── ProfileItem.tsx │ │ │ └── ProfileName.tsx │ │ └── index.tsx │ └── wallet │ │ └── WalletPage.tsx ├── service-worker.ts ├── shared │ ├── components │ │ ├── Footer.tsx │ │ ├── HyperText.tsx │ │ ├── Icons │ │ │ ├── Icon.tsx │ │ │ └── icons.svg │ │ ├── InstallPWAPrompt.tsx │ │ ├── Layout.tsx │ │ ├── LoadingFallback.tsx │ │ ├── NotificationPrompt.tsx │ │ ├── ProxyImg.tsx │ │ ├── QRScanner.tsx │ │ ├── RightColumn.tsx │ │ ├── button │ │ │ ├── CopyButton.tsx │ │ │ ├── FollowButton.tsx │ │ │ └── UploadButton.tsx │ │ ├── connection │ │ │ └── ConnectionStatus.tsx │ │ ├── create │ │ │ ├── NoteCreator.tsx │ │ │ └── Textarea.tsx │ │ ├── embed │ │ │ ├── Audio.tsx │ │ │ ├── Hashtag.tsx │ │ │ ├── LightningUri.tsx │ │ │ ├── Url.tsx │ │ │ ├── apple │ │ │ │ ├── AppleMusic.tsx │ │ │ │ ├── AppleMusicComponent.tsx │ │ │ │ ├── ApplePodcast.tsx │ │ │ │ └── ApplePodcastComponent.tsx │ │ │ ├── index.ts │ │ │ ├── instagram │ │ │ │ ├── Instagram.tsx │ │ │ │ └── InstagramComponent.tsx │ │ │ ├── media │ │ │ │ ├── Carousel.tsx │ │ │ │ ├── ImageComponent.tsx │ │ │ │ ├── MediaEmbed.tsx │ │ │ │ ├── SmallImage.tsx │ │ │ │ ├── SmallImageComponent.tsx │ │ │ │ ├── SmallThumbnail.tsx │ │ │ │ ├── SmallThumbnailComponent.tsx │ │ │ │ ├── VideoComponent.tsx │ │ │ │ └── mediaUtils.ts │ │ │ ├── nostr │ │ │ │ ├── CustomEmoji.tsx │ │ │ │ ├── CustomEmojiComponent.tsx │ │ │ │ ├── InlineMention.tsx │ │ │ │ ├── Nip19.tsx │ │ │ │ ├── NostrNote.tsx │ │ │ │ └── NostrNpub.tsx │ │ │ ├── soundcloud │ │ │ │ ├── SoundCloud.tsx │ │ │ │ └── SoundCloudComponent.tsx │ │ │ ├── spotify │ │ │ │ ├── SpotifyAlbum.tsx │ │ │ │ ├── SpotifyAlbumComponent.tsx │ │ │ │ ├── SpotifyPlaylist.tsx │ │ │ │ ├── SpotifyPlaylistComponent.tsx │ │ │ │ ├── SpotifyPodcast.tsx │ │ │ │ ├── SpotifyPodcastComponent.tsx │ │ │ │ ├── SpotifyTrack.tsx │ │ │ │ └── SpotifyTrackComponent.tsx │ │ │ ├── tidal │ │ │ │ ├── TidalPlaylist.tsx │ │ │ │ ├── TidalPlaylistComponent.tsx │ │ │ │ ├── TidalTrack.tsx │ │ │ │ └── TidalTrackComponent.tsx │ │ │ ├── tiktok │ │ │ │ ├── TikTok.tsx │ │ │ │ └── TikTokComponent.tsx │ │ │ ├── twitch │ │ │ │ ├── Twitch.tsx │ │ │ │ ├── TwitchChannel.tsx │ │ │ │ ├── TwitchChannelComponent.tsx │ │ │ │ └── TwitchComponent.tsx │ │ │ ├── wavlake │ │ │ │ ├── WavLake.tsx │ │ │ │ └── WavLakeComponent.tsx │ │ │ └── youtube │ │ │ │ ├── YouTube.tsx │ │ │ │ └── YoutubeComponent.tsx │ │ ├── emoji │ │ │ ├── EmojiButton.tsx │ │ │ └── FloatingEmojiPicker.tsx │ │ ├── event │ │ │ ├── ChannelCreation.tsx │ │ │ ├── EventBorderless.tsx │ │ │ ├── FeedItem │ │ │ │ ├── FeedItem.tsx │ │ │ │ ├── FeedItemContent.tsx │ │ │ │ ├── FeedItemHeader.tsx │ │ │ │ ├── FeedItemPlaceholder.tsx │ │ │ │ ├── FeedItemTitle.tsx │ │ │ │ └── utils.ts │ │ │ ├── Highlight.tsx │ │ │ ├── LikeHeader.tsx │ │ │ ├── LongForm.tsx │ │ │ ├── MuteUser.tsx │ │ │ ├── RawJSON.tsx │ │ │ ├── RelativeTime.tsx │ │ │ ├── ReportReasonForm.tsx │ │ │ ├── ReportUser.tsx │ │ │ ├── RepostHeader.tsx │ │ │ ├── SimpleFeedItemDropdown.tsx │ │ │ ├── TextNote.tsx │ │ │ ├── ZapModal.tsx │ │ │ ├── ZapReceipt.tsx │ │ │ ├── Zapraiser.tsx │ │ │ ├── reactions │ │ │ │ ├── FeedItemActions.tsx │ │ │ │ ├── FeedItemComment.tsx │ │ │ │ ├── FeedItemDropdown.tsx │ │ │ │ ├── FeedItemLike.tsx │ │ │ │ ├── FeedItemRepost.tsx │ │ │ │ ├── FeedItemShare.tsx │ │ │ │ ├── FeedItemZap.tsx │ │ │ │ ├── Likes.tsx │ │ │ │ ├── ReactionContent.tsx │ │ │ │ ├── Reactions.tsx │ │ │ │ ├── Reposts.tsx │ │ │ │ └── Zaps.tsx │ │ │ └── utils.ts │ │ ├── feed │ │ │ ├── DisplayAsSelector.tsx │ │ │ ├── Feed.tsx │ │ │ ├── ImageGridItem.tsx │ │ │ ├── MediaFeed.tsx │ │ │ ├── NewEventsButton.tsx │ │ │ ├── NotificationsFeed.tsx │ │ │ ├── Trending.tsx │ │ │ ├── UnknownUserEvents.tsx │ │ │ └── utils.ts │ │ ├── header │ │ │ ├── Header.tsx │ │ │ ├── NotificationButton.tsx │ │ │ ├── UnseenNotificationsBadge.tsx │ │ │ └── WalletButton.tsx │ │ ├── market │ │ │ ├── MarketDetails.tsx │ │ │ ├── MarketGridItem.tsx │ │ │ ├── MarketImage.tsx │ │ │ └── MarketListing.tsx │ │ ├── media │ │ │ ├── MediaModal.tsx │ │ │ └── PreloadImages.tsx │ │ ├── messages │ │ │ └── UnseenMessagesBadge.tsx │ │ ├── nav │ │ │ ├── MessagesNavItem.tsx │ │ │ ├── NavItem.tsx │ │ │ ├── NavLink.tsx │ │ │ ├── NavSideBar.tsx │ │ │ ├── NotificationNavItem.tsx │ │ │ ├── SubscriptionNavItem.tsx │ │ │ └── navConfig.ts │ │ ├── ui │ │ │ ├── Dropdown.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── Hovercard.tsx │ │ │ ├── InfiniteScroll.tsx │ │ │ ├── Modal.tsx │ │ │ ├── PublishButton.tsx │ │ │ ├── SearchBox.tsx │ │ │ └── Widget.tsx │ │ ├── user │ │ │ ├── Avatar.tsx │ │ │ ├── AvatarGroup.tsx │ │ │ ├── Badge.tsx │ │ │ ├── FollowedBy.tsx │ │ │ ├── LoginDialog.tsx │ │ │ ├── MinidenticonImg.tsx │ │ │ ├── MutedBy.tsx │ │ │ ├── Name.tsx │ │ │ ├── ProfileAbout.tsx │ │ │ ├── ProfileCard.tsx │ │ │ ├── PublicKeyQRCodeButton.tsx │ │ │ ├── QRCodeButton.tsx │ │ │ ├── SignIn.tsx │ │ │ ├── SignUp.tsx │ │ │ ├── SubscriberBadge.tsx │ │ │ ├── UserRow.tsx │ │ │ ├── const.ts │ │ │ └── useHoverCard.ts │ │ └── ux │ │ │ └── Timeout.tsx │ ├── hooks │ │ ├── useAutosizeTextarea.ts │ │ ├── useCachedFetch.ts │ │ ├── useFeedEvents.ts │ │ ├── useFollows.ts │ │ ├── useHistoryState.ts │ │ ├── useInviteFromUrl.ts │ │ ├── useIsMobile.ts │ │ ├── useMutes.ts │ │ ├── useNip05Validation.ts │ │ ├── useOnlineStatus.ts │ │ ├── useProfile.ts │ │ ├── useSearchParam.ts │ │ ├── useSubscriptionStatus.ts │ │ ├── useWalletBalance.ts │ │ └── useWebLNProvider.ts │ ├── services │ │ └── Mute.tsx │ ├── upload.ts │ └── utils │ │ ├── Hex.ts │ │ ├── PublicKey.ts │ │ ├── imgproxy.ts │ │ ├── isTouchDevice.ts │ │ ├── marketUtils.ts │ │ └── subscriptionIcons.tsx ├── stores │ ├── draft.ts │ ├── events.ts │ ├── feed.ts │ ├── notifications.ts │ ├── search.ts │ ├── sessions.ts │ ├── settings.ts │ ├── ui.ts │ ├── user.ts │ ├── wallet.ts │ └── zap.ts ├── types │ ├── dom-types.d.ts │ ├── emoji.ts │ └── global.d.ts ├── utils │ ├── AnimalName.ts │ ├── IrisAPI.ts │ ├── SortedMap │ │ ├── SortedMap.test.ts │ │ └── SortedMap.tsx │ ├── chat │ │ └── webrtc │ │ │ └── PeerConnection.ts │ ├── cloudflare_banned_users.ts │ ├── memcache.ts │ ├── messageRepository.ts │ ├── migration.ts │ ├── ndk.ts │ ├── nostr.ts │ ├── notifications.ts │ ├── profileSearch.ts │ ├── socialGraph.ts │ ├── utils.ts │ └── visibility.ts └── vite-env.d.ts ├── tailwind.config.js ├── tests ├── auth.setup.ts ├── chat-invite.spec.ts ├── draft-persistence.spec.ts ├── follow-user.spec.ts ├── hashtag.spec.ts ├── home.spec.ts ├── like-post.spec.ts ├── message-form.spec.ts ├── navigation.spec.ts ├── new-post.spec.ts ├── notification.spec.ts ├── profile-qr.spec.ts ├── reply-post.spec.ts ├── repost.spec.ts ├── search.spec.ts ├── session-persistence.spec.ts ├── settings.spec.ts ├── signup.spec.ts ├── thread-view.spec.ts └── view-profile.spec.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock /.codesandbox/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // These tasks will run in order when initializing your CodeSandbox project. 3 | "setupTasks": [ 4 | { 5 | "name": "Install Dependencies", 6 | "command": "npm install" 7 | } 8 | ], 9 | 10 | // These tasks can be run from CodeSandbox. Running one will open a log in the app. 11 | "tasks": { 12 | "dev": { 13 | "name": "dev", 14 | "command": "npm run dev", 15 | "runAtStart": true 16 | }, 17 | "start": { 18 | "name": "start", 19 | "command": "npm run start", 20 | "runAtStart": false 21 | }, 22 | "build": { 23 | "name": "build", 24 | "command": "npm run build", 25 | "runAtStart": false 26 | }, 27 | "lint": { 28 | "name": "lint", 29 | "command": "npm run lint", 30 | "runAtStart": false 31 | }, 32 | "preview": { 33 | "name": "preview", 34 | "command": "npm run preview", 35 | "runAtStart": false 36 | }, 37 | "test": { 38 | "name": "test", 39 | "command": "npm run test", 40 | "runAtStart": false 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node.js & TypeScript", 3 | "dockerComposeFile": "../docker-compose.yml", 4 | "service": "app", 5 | "workspaceFolder": "/app", 6 | "forwardPorts": [5173], 7 | "shutdownAction": "stopCompose" 8 | } 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: '20' 20 | cache: 'yarn' 21 | 22 | - name: Install dependencies 23 | run: yarn install --frozen-lockfile 24 | 25 | - name: Install Playwright browsers 26 | run: npx playwright install chromium 27 | 28 | - name: Run tests 29 | run: yarn test 30 | 31 | - name: Build 32 | run: yarn build 33 | 34 | - name: Upload test results 35 | uses: actions/upload-artifact@v4 36 | if: always() 37 | with: 38 | name: playwright-report 39 | path: playwright-report/ 40 | retention-days: 30 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | build 15 | dev-dist 16 | 17 | # Test results 18 | playwright-report/ 19 | test-results/ 20 | /test-results/ 21 | /playwright-report/ 22 | /blob-report/ 23 | /playwright/.cache/ 24 | 25 | # Editor directories and files 26 | .vscode/* 27 | !.vscode/extensions.json 28 | .idea 29 | .DS_Store 30 | *.suo 31 | *.ntvs* 32 | *.njsproj 33 | *.sln 34 | *.sw? 35 | -------------------------------------------------------------------------------- /.lighthouserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ci: { 3 | collect: { 4 | startServerCommand: 'yarn build && yarn preview', 5 | url: ['http://localhost:3000'], 6 | numberOfRuns: 3, 7 | settings: { 8 | onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'], 9 | throttling: { 10 | rttMs: 150, 11 | throughputKbps: 1638.4, 12 | cpuSlowdownMultiplier: 2, 13 | }, 14 | }, 15 | }, 16 | assert: { 17 | assertions: { 18 | 'categories:performance': ['error', { minScore: 0.9 }], 19 | 'categories:accessibility': ['error', { minScore: 0.9 }], 20 | 'categories:best-practices': ['error', { minScore: 0.9 }], 21 | 'categories:seo': ['error', { minScore: 0.9 }], 22 | }, 23 | }, 24 | }, 25 | }; -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | dist 4 | public 5 | node_modules 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-sort-imports"], 3 | "printWidth": 90, 4 | "bracketSpacing": false, 5 | "trailingComma": "es5", 6 | "arrowParens": "always", 7 | "semi": false, 8 | "tabWidth": 2, 9 | "singleQuote": false 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the updated VS Code dev container image for JavaScript/Node.js 2 | FROM mcr.microsoft.com/devcontainers/javascript-node:0-18 3 | 4 | # Set the working directory 5 | WORKDIR /app 6 | 7 | # Copy the package.json and yarn.lock files to the container 8 | COPY package*.json ./ 9 | COPY yarn.lock ./ 10 | 11 | # Install Yarn 12 | RUN npm install -g yarn 13 | 14 | # Install dependencies using Yarn 15 | RUN yarn install 16 | 17 | # Copy the rest of the application code to the container 18 | COPY . . 19 | 20 | # Expose the port your application runs on 21 | EXPOSE 5173 22 | 23 | # Start the application using Yarn 24 | CMD ["yarn", "run", "preview", "--", "--host"] 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Martti Malmi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iris 2 | 3 | Source code for [iris.to](https://iris.to) 4 | 5 | [![CI](https://github.com/irislib/iris-client/actions/workflows/ci.yml/badge.svg)](https://github.com/irislib/iris-client/actions/workflows/ci.yml) 6 | [![Build Status](https://github.com/irislib/iris-client/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/irislib/iris-client/actions/workflows/ci.yml) 7 | [![Tests](https://github.com/irislib/iris-client/actions/workflows/ci.yml/badge.svg?branch=main&event=push&label=tests)](https://github.com/irislib/iris-client/actions/workflows/ci.yml) 8 | 9 | ## Development 10 | 11 | ```bash 12 | # Install dependencies 13 | yarn 14 | 15 | # Start development server 16 | yarn dev 17 | 18 | # Build for production 19 | yarn build 20 | 21 | # Run tests 22 | yarn test # Run all tests 23 | yarn test:ui # E2E tests with UI mode 24 | ``` 25 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | Choose config with NODE_CONFIG_ENV: `NODE_CONFIG_ENV=iris yarn start` 2 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "iris", 3 | "appNameCapitalized": "Iris", 4 | "hostname": "iris.to", 5 | "nip05Domain": "iris.to", 6 | "icon": "/img/icon128.png", 7 | "navLogo": "/img/icon128.png", 8 | "defaultTheme": "iris", 9 | "aboutText": "Iris - Connecting People", 10 | "repository": "https://github.com/irislib/iris-client", 11 | "features": { 12 | "analytics": false, 13 | "showSubscriptionSettings": true 14 | }, 15 | "defaultSettings": { 16 | "notificationServer": "https://notifications.iris.to", 17 | "irisApiUrl": "https://api.iris.to" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "iris", 3 | "appNameCapitalized": "Iris", 4 | "hostname": "iris.to", 5 | "nip05Domain": "iris.to", 6 | "icon": "/img/icon128.png", 7 | "navLogo": "/img/icon128.png", 8 | "defaultTheme": "iris", 9 | "aboutText": "Iris - Connecting People", 10 | "repository": "https://github.com/irislib/iris-client", 11 | "features": { 12 | "analytics": false, 13 | "showSubscriptionSettings": true 14 | }, 15 | "defaultSettings": { 16 | "notificationServer": "https://notifications.iris.to", 17 | "irisApiUrl": "" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/nestr.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": "Nestr", 3 | "appNameCapitalized": "Nestr", 4 | "hostname": "nestr.iris.to", 5 | "nip05Domain": "iris.to", 6 | "icon": "/img/icon128.png", 7 | "navLogo": "/nestr-logo-no-name.png", 8 | "defaultTheme": "dark", 9 | "navItems": [ 10 | "home", 11 | "search", 12 | "notifications", 13 | "organizations", 14 | "repositories", 15 | "settings", 16 | "about" 17 | ], 18 | "aboutText": "Nestr aims to facilitate truly sovereign and censorship-resistant code development with social network features provided by the Nostr protocol.", 19 | "repository": "", 20 | "features": { 21 | "git": true, 22 | "cashu": false, 23 | "analytics": false 24 | }, 25 | "defaultSettings": { 26 | "youtubePrivacyMode": false, 27 | "notificationServer": "https://notifications.iris.to" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /custom.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare const CONFIG: { 4 | appName: string 5 | appNameCapitalized: string 6 | appTitle: string 7 | hostname: string 8 | nip05Domain: string 9 | icon: string 10 | navLogo: string 11 | defaultTheme: string 12 | navItems: string[] 13 | aboutText: string 14 | repository: string 15 | features: { 16 | analytics: boolean 17 | showSubscriptionSettings: boolean 18 | } 19 | defaultSettings: { 20 | notificationServer: string 21 | irisApiUrl: string 22 | } 23 | } 24 | 25 | interface Performance { 26 | memory?: { 27 | jsHeapSizeLimit: number 28 | totalJSHeapSize: number 29 | usedJSHeapSize: number 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | app: 5 | build: . 6 | ports: 7 | - "5173:5173" 8 | volumes: 9 | - .:/app 10 | - /app/node_modules 11 | environment: 12 | NODE_ENV: development 13 | command: ["yarn", "start", "--", "--host"] 14 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig, devices} from "@playwright/test" 2 | 3 | export default defineConfig({ 4 | testDir: "./tests", 5 | fullyParallel: true, 6 | forbidOnly: !!process.env.CI, 7 | retries: process.env.CI ? 2 : 0, 8 | workers: process.env.CI ? 1 : undefined, 9 | reporter: "html", 10 | use: { 11 | baseURL: "http://localhost:5173", 12 | trace: "on-first-retry", 13 | video: "on-first-retry", 14 | }, 15 | projects: [ 16 | { 17 | name: "chromium", 18 | use: {...devices["Desktop Chrome"]}, 19 | }, 20 | ], 21 | webServer: { 22 | command: "yarn dev", 23 | url: "http://localhost:5173", 24 | reuseExistingServer: !process.env.CI, 25 | }, 26 | }) 27 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/.well-known/assetlinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "relation": ["delegate_permission/common.handle_all_urls"], 4 | "target": { 5 | "namespace": "android_app", 6 | "package_name": "to.iris.twa", 7 | "sha256_cert_fingerprints": [ 8 | "63:B5:70:E8:F1:75:7E:D6:EF:81:11:66:F4:9D:47:AB:49:3C:2E:00:B9:67:92:40:89:A5:03:0B:96:B9:40:09" 9 | ] 10 | } 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /cashu /cashu 200 2 | /* /index.html 200 -------------------------------------------------------------------------------- /public/cashu/assets/AlreadyRunning.3bb200a1.js: -------------------------------------------------------------------------------- 1 | import{K as t,j as a,L as n,W as s,a5 as e,Q as o,X as r}from"./index.916cb52e.js";const c=a({name:"AlreadyRunning"}),l={class:"fullscreen bg-dark text-white text-center q-pa-md flex flex-center"},d=e("div",{class:"text-h3"},"Nope.",-1),i=e("div",{class:"text-h5 q-ma-lg text-grey"}," Another tab is already running. Close this tab and try again. ",-1);function _(p,u,x,f,h,m){return n(),s("div",l,[e("div",null,[d,i,o(r,{rounded:"",class:"q-mt-md",color:"white","text-color":"black",unelevated:"",to:"/",label:"Retry"})])])}var v=t(c,[["render",_]]);export{v as default}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/AndroidPWAPrompt.eb57d776.css: -------------------------------------------------------------------------------- 1 | @keyframes moveUpDown-371778e1{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}.pwa-prompt[data-v-371778e1]{position:fixed;bottom:0;left:0;right:0;margin:0 auto;z-index:9999;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center;animation:moveUpDown-371778e1 1s infinite}.pwa-prompt-content[data-v-371778e1]{display:inline-flex;align-items:center;background-color:#000;padding:10px;border:1px solid #ccc;border-radius:8px}.pwa-prompt-content q-icon[data-v-371778e1]{margin-right:5px}.pwa-prompt-arrow[data-v-371778e1]{width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:10px solid white;margin:2px auto;text-align:center}@keyframes moveUpDown-06af58bd{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}.pwa-prompt[data-v-06af58bd]{position:fixed;top:20px;right:20px;margin:0 auto;z-index:9999;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center;animation:moveUpDown-06af58bd 1s infinite}.pwa-prompt-content[data-v-06af58bd]{display:inline-flex;align-items:center;background-color:#000;padding:10px;border:1px solid #ccc;border-radius:8px}.pwa-prompt-content q-icon[data-v-06af58bd]{margin-right:5px}.pwa-prompt-arrow[data-v-06af58bd]{position:relative;width:0;height:0;bottom:60px;left:45%;border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid white;text-align:center;margin:0 auto} 2 | -------------------------------------------------------------------------------- /public/cashu/assets/BlankLayout.41d1327b.js: -------------------------------------------------------------------------------- 1 | import{Q as a,a as n}from"./QLayout.098b1f25.js";import{K as r,j as s,V as i,L as p,M as c,O as e,Q as o}from"./index.916cb52e.js";import"./scroll.b2adc88e.js";import"./QResizeObserver.d45b2ee0.js";const m=s({name:"BlankLayout",mixins:[windowMixin],components:{}});function _(l,u,f,d,w,x){const t=i("router-view");return p(),c(a,{view:"lHh Lpr lFf"},{default:e(()=>[o(n,null,{default:e(()=>[o(t)]),_:1})]),_:1})}var C=r(m,[["render",_]]);export{C as default}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/ErrorNotFound.0a671c4a.js: -------------------------------------------------------------------------------- 1 | import{K as t,j as o,L as s,W as a,a5 as e,Q as r,X as n}from"./index.916cb52e.js";const c=o({name:"ErrorNotFound"}),l={class:"fullscreen bg-dark text-white text-center q-pa-md flex flex-center"},d=e("div",{style:{"font-size":"30vh"}},"404",-1),i=e("div",{class:"text-h3 q-pb-lg",style:{opacity:"0.8"}}," Oops. Nothing here... ",-1);function _(p,f,h,u,x,m){return s(),a("div",l,[e("div",null,[d,i,r(n,{rounded:"",size:"lg",class:"q-mt-xl",color:"white","text-color":"black",unelevated:"",to:"/",label:"Go back home"})])])}var b=t(c,[["render",_]]);export{b as default}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/FullscreenLayout.02ec423b.js: -------------------------------------------------------------------------------- 1 | import{Q as f,a as w}from"./QLayout.098b1f25.js";import{j as n,K as r,L as t,M as s,O as e,Q as o,X as x,a5 as $,V as a}from"./index.916cb52e.js";import{Q}from"./QToolbar.4ef97f48.js";import{Q as F}from"./QHeader.76ba7108.js";import"./scroll.b2adc88e.js";import"./QResizeObserver.d45b2ee0.js";const b=n({name:"FullscreenHeader",mixins:[windowMixin],props:{},components:{},setup(){return{}}}),h=$("span",{class:"q-mx-md text-weight-bold"},"Wallet",-1);function v(c,l,i,p,u,_){return t(),s(F,{class:"bg-marginal-bg"},{default:e(()=>[o(Q,null,{default:e(()=>[o(x,{flat:"",dense:"",rounded:"",icon:"arrow_back_ios_new",to:"/",color:"primary","aria-label":"Menu","no-caps":""},{default:e(()=>[h]),_:1})]),_:1})]),_:1})}var H=r(b,[["render",v]]);const g=n({name:"FullscreenLayout",mixins:[windowMixin],components:{FullscreenHeader:H}});function L(c,l,i,p,u,_){const d=a("FullscreenHeader"),m=a("router-view");return t(),s(f,{view:"lHh Lpr lFf"},{default:e(()=>[o(d),o(w,null,{default:e(()=>[o(m)]),_:1})]),_:1})}var N=r(g,[["render",L]]);export{N as default}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/KFOkCnqEu92Fr1MmgVxIIzQ.34e9582c.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/KFOkCnqEu92Fr1MmgVxIIzQ.34e9582c.woff -------------------------------------------------------------------------------- /public/cashu/assets/KFOlCnqEu92Fr1MmEU9fBBc-.9ce7f3ac.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/KFOlCnqEu92Fr1MmEU9fBBc-.9ce7f3ac.woff -------------------------------------------------------------------------------- /public/cashu/assets/KFOlCnqEu92Fr1MmSU5fBBc-.bf14c7d7.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/KFOlCnqEu92Fr1MmSU5fBBc-.bf14c7d7.woff -------------------------------------------------------------------------------- /public/cashu/assets/KFOlCnqEu92Fr1MmWUlfBBc-.e0fd57c0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/KFOlCnqEu92Fr1MmWUlfBBc-.e0fd57c0.woff -------------------------------------------------------------------------------- /public/cashu/assets/KFOlCnqEu92Fr1MmYUtfBBc-.f6537e32.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/KFOlCnqEu92Fr1MmYUtfBBc-.f6537e32.woff -------------------------------------------------------------------------------- /public/cashu/assets/KFOmCnqEu92Fr1Mu4mxM.f2abf7fb.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/KFOmCnqEu92Fr1Mu4mxM.f2abf7fb.woff -------------------------------------------------------------------------------- /public/cashu/assets/MainLayout.aee306ae.css: -------------------------------------------------------------------------------- 1 | .q-header[data-v-9bc794ce]{position:relative;z-index:auto;overflow-x:hidden}.q-toolbar[data-v-9bc794ce]{flex-wrap:nowrap;min-height:50px}.q-toolbar-title[data-v-9bc794ce]{flex:0 1 auto}.q-toolbar>.q-badge[data-v-9bc794ce]{flex-shrink:0} 2 | -------------------------------------------------------------------------------- /public/cashu/assets/QList.1f08d45c.js: -------------------------------------------------------------------------------- 1 | import{B as r,E as o,k as i,ac as d,I as u,f as s}from"./index.916cb52e.js";import{u as g,a as c}from"./QItem.18ca9374.js";const b=["top","middle","bottom"];var q=r({name:"QBadge",props:{color:String,textColor:String,floating:Boolean,transparent:Boolean,multiLine:Boolean,outline:Boolean,rounded:Boolean,label:[Number,String],align:{type:String,validator:e=>b.includes(e)}},setup(e,{slots:a}){const l=o(()=>e.align!==void 0?{verticalAlign:e.align}:null),n=o(()=>{const t=e.outline===!0&&e.color||e.textColor;return`q-badge flex inline items-center no-wrap q-badge--${e.multiLine===!0?"multi":"single"}-line`+(e.outline===!0?" q-badge--outline":e.color!==void 0?` bg-${e.color}`:"")+(t!==void 0?` text-${t}`:"")+(e.floating===!0?" q-badge--floating":"")+(e.rounded===!0?" q-badge--rounded":"")+(e.transparent===!0?" q-badge--transparent":"")});return()=>i("div",{class:n.value,style:l.value,role:"status","aria-label":e.label},d(a.default,e.label!==void 0?[e.label]:[]))}}),B=r({name:"QList",props:{...g,bordered:Boolean,dense:Boolean,separator:Boolean,padding:Boolean,tag:{type:String,default:"div"}},setup(e,{slots:a}){const l=s(),n=c(e,l.proxy.$q),t=o(()=>"q-list"+(e.bordered===!0?" q-list--bordered":"")+(e.dense===!0?" q-list--dense":"")+(e.separator===!0?" q-list--separator":"")+(n.value===!0?" q-list--dark":"")+(e.padding===!0?" q-list--padding":""));return()=>i(e.tag,{class:t.value},u(a.default))}});export{q as Q,B as a}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/QResizeObserver.d45b2ee0.js: -------------------------------------------------------------------------------- 1 | import{r as g,a9 as y,o as d,B as z,F as f,m as w,n as v,k as O,f as x,a8 as b}from"./index.916cb52e.js";function E(){const r=g(!y.value);return r.value===!1&&d(()=>{r.value=!0}),{isHydrated:r}}const h=typeof ResizeObserver<"u",m=h===!0?{}:{style:"display:block;position:absolute;top:0;left:0;right:0;bottom:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1;",url:"about:blank"};var L=z({name:"QResizeObserver",props:{debounce:{type:[String,Number],default:100}},emits:["resize"],setup(r,{emit:p}){let n=null,t,o={width:-1,height:-1};function s(e){e===!0||r.debounce===0||r.debounce==="0"?u():n===null&&(n=setTimeout(u,r.debounce))}function u(){if(n!==null&&(clearTimeout(n),n=null),t){const{offsetWidth:e,offsetHeight:i}=t;(e!==o.width||i!==o.height)&&(o={width:e,height:i},p("resize",o))}}const{proxy:l}=x();if(l.trigger=s,h===!0){let e;const i=a=>{t=l.$el.parentNode,t?(e=new ResizeObserver(s),e.observe(t),u()):a!==!0&&v(()=>{i(!0)})};return d(()=>{i()}),f(()=>{n!==null&&clearTimeout(n),e!==void 0&&(e.disconnect!==void 0?e.disconnect():t&&e.unobserve(t))}),w}else{let a=function(){n!==null&&(clearTimeout(n),n=null),i!==void 0&&(i.removeEventListener!==void 0&&i.removeEventListener("resize",s,b.passive),i=void 0)},c=function(){a(),t&&t.contentDocument&&(i=t.contentDocument.defaultView,i.addEventListener("resize",s,b.passive),u())};const{isHydrated:e}=E();let i;return d(()=>{v(()=>{t=l.$el,t&&c()})}),f(a),()=>{if(e.value===!0)return O("object",{class:"q--avoid-card-border",style:m.style,tabindex:-1,type:"text/html",data:m.url,"aria-hidden":"true",onLoad:c})}}}});export{L as Q}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/QSpinnerHourglass.12e6d1e7.js: -------------------------------------------------------------------------------- 1 | import{k as e,B as a,aO as s,aP as o}from"./index.916cb52e.js";const n=[e("g",[e("path",{fill:"none",stroke:"currentColor","stroke-width":"5","stroke-miterlimit":"10",d:"M58.4,51.7c-0.9-0.9-1.4-2-1.4-2.3s0.5-0.4,1.4-1.4 C70.8,43.8,79.8,30.5,80,15.5H70H30H20c0.2,15,9.2,28.1,21.6,32.3c0.9,0.9,1.4,1.2,1.4,1.5s-0.5,1.6-1.4,2.5 C29.2,56.1,20.2,69.5,20,85.5h10h40h10C79.8,69.5,70.8,55.9,58.4,51.7z"}),e("clipPath",{id:"uil-hourglass-clip1"},[e("rect",{x:"15",y:"20",width:"70",height:"25"},[e("animate",{attributeName:"height",from:"25",to:"0",dur:"1s",repeatCount:"indefinite",values:"25;0;0",keyTimes:"0;0.5;1"}),e("animate",{attributeName:"y",from:"20",to:"45",dur:"1s",repeatCount:"indefinite",values:"20;45;45",keyTimes:"0;0.5;1"})])]),e("clipPath",{id:"uil-hourglass-clip2"},[e("rect",{x:"15",y:"55",width:"70",height:"25"},[e("animate",{attributeName:"height",from:"0",to:"25",dur:"1s",repeatCount:"indefinite",values:"0;25;25",keyTimes:"0;0.5;1"}),e("animate",{attributeName:"y",from:"80",to:"55",dur:"1s",repeatCount:"indefinite",values:"80;55;55",keyTimes:"0;0.5;1"})])]),e("path",{d:"M29,23c3.1,11.4,11.3,19.5,21,19.5S67.9,34.4,71,23H29z","clip-path":"url(#uil-hourglass-clip1)",fill:"currentColor"}),e("path",{d:"M71.6,78c-3-11.6-11.5-20-21.5-20s-18.5,8.4-21.5,20H71.6z","clip-path":"url(#uil-hourglass-clip2)",fill:"currentColor"}),e("animateTransform",{attributeName:"transform",type:"rotate",from:"0 50 50",to:"180 50 50",repeatCount:"indefinite",dur:"1s",values:"0 50 50;0 50 50;180 50 50",keyTimes:"0;0.7;1"})])];var l=a({name:"QSpinnerHourglass",props:s,setup(i){const{cSize:t,classes:r}=o(i);return()=>e("svg",{class:r.value,width:t.value,height:t.value,viewBox:"0 0 100 100",preserveAspectRatio:"xMidYMid",xmlns:"http://www.w3.org/2000/svg"},n)}});export{l as Q}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/QToolbar.4ef97f48.js: -------------------------------------------------------------------------------- 1 | import{B as t,E as r,k as s,I as l}from"./index.916cb52e.js";var p=t({name:"QToolbar",props:{inset:Boolean},setup(o,{slots:e}){const a=r(()=>"q-toolbar row no-wrap items-center"+(o.inset===!0?" q-toolbar--inset":""));return()=>s("div",{class:a.value,role:"toolbar"},l(e.default))}});export{p as Q}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/QToolbarTitle.7349d924.js: -------------------------------------------------------------------------------- 1 | import{B as a,E as t,k as l,I as r}from"./index.916cb52e.js";var n=a({name:"QToolbarTitle",props:{shrink:Boolean},setup(o,{slots:s}){const e=t(()=>"q-toolbar__title ellipsis"+(o.shrink===!0?" col-shrink":""));return()=>l("div",{class:e.value},r(s.default))}});export{n as Q}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/Settings.f9af94b6.css: -------------------------------------------------------------------------------- 1 | .section-divider{display:flex;align-items:center;width:100%;margin-bottom:24px}.divider-line{flex:1;height:1px;background-color:#48484a}.divider-text{padding:0 10px;font-size:14px;font-weight:600;color:#fff} 2 | -------------------------------------------------------------------------------- /public/cashu/assets/TermsPage.8329ea15.css: -------------------------------------------------------------------------------- 1 | .q-dialog__inner[data-v-7395ac9f]{height:100%;width:100%;margin:0}.q-card[data-v-7395ac9f]{display:flex;flex-direction:column;height:100%}.q-carousel[data-v-7395ac9f]{flex:1}.custom-navigation[data-v-7395ac9f]{display:flex;justify-content:space-between;padding:16px} 2 | -------------------------------------------------------------------------------- /public/cashu/assets/TermsPage.ae1dd3e0.js: -------------------------------------------------------------------------------- 1 | import{f as o,Q as a}from"./welcome.62644ccc.js";import{W as r}from"./WelcomeSlide4.4f3ce9ed.js";import{K as s,o as n,V as i,L as p,W as m,Q as e,O as c,a0 as _}from"./index.916cb52e.js";import"./QItem.18ca9374.js";import"./private.use-form.be065f06.js";import"./use-timeout.c52bf43b.js";import"./scroll.b2adc88e.js";import"./focusout.be8db32e.js";import"./index.42f06193.js";import"./use-checkbox.bd46c6a9.js";const l={name:"TermsPage",components:{WelcomeSlide4:r},setup(){return n(()=>{}),{}}};function d(u,f,g,h,v,x){const t=i("WelcomeSlide4");return p(),m(_,null,[e(o,{class:"bg-dark q-pa-none",style:{height:"100%"}},{default:c(()=>[e(t)]),_:1}),e(a,{persistent:"","transition-show":"slide-up","transition-hide":"fadeOut"})],64)}var B=s(l,[["render",d],["__scopeId","data-v-7395ac9f"]]);export{B as default}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/WelcomePage.b07b1606.css: -------------------------------------------------------------------------------- 1 | h2[data-v-55e7479b]{font-weight:700}p[data-v-55e7479b]{font-size:large}.relative-position[data-v-60a57b40]{position:relative}.instruction[data-v-60a57b40]{display:flex;align-items:center;justify-content:center;margin-bottom:1rem}.instruction span[data-v-60a57b40]{margin-left:.5rem;font-size:1rem}h2[data-v-60a57b40]{font-weight:700}h6[data-v-60a57b40]{font-weight:700;margin-top:1rem;margin-bottom:.5rem}p[data-v-60a57b40]{font-size:large}.sub-instruction[data-v-60a57b40]{margin-left:.5rem}h2[data-v-338b79e4]{font-weight:700}p[data-v-338b79e4]{font-size:large}.seed-phrase[data-v-338b79e4] .q-field__control{padding:12px!important}.seed-phrase[data-v-338b79e4]{font-size:.9rem;font-family:monospace;padding:12px!important}.q-dialog__inner[data-v-ca15906a]{height:100%;width:100%;margin:0}.q-card[data-v-ca15906a]{display:flex;flex-direction:column;height:100%}.q-carousel[data-v-ca15906a]{flex:1}.custom-navigation[data-v-ca15906a]{display:flex;justify-content:space-between;padding:16px} 2 | -------------------------------------------------------------------------------- /public/cashu/assets/WelcomeSlide4.8505cd1c.css: -------------------------------------------------------------------------------- 1 | h2[data-v-304b4918]{font-weight:700}p[data-v-304b4918]{font-size:.82rem;color:#c6c6c6} 2 | -------------------------------------------------------------------------------- /public/cashu/assets/cashu.5d75870c.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/cashu/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.fd84f88b.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.fd84f88b.woff -------------------------------------------------------------------------------- /public/cashu/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.4a4dbc62.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.4a4dbc62.woff2 -------------------------------------------------------------------------------- /public/cashu/assets/focusout.be8db32e.js: -------------------------------------------------------------------------------- 1 | import{c as i}from"./index.916cb52e.js";const e=[];function o(n){e[e.length-1](n)}function c(n){i.is.desktop===!0&&(e.push(n),e.length===1&&document.body.addEventListener("focusin",o))}function u(n){const t=e.indexOf(n);t!==-1&&(e.splice(t,1),e.length===0&&document.body.removeEventListener("focusin",o))}export{c as a,u as r}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/global-components.f92d7ae1.js: -------------------------------------------------------------------------------- 1 | import{h as m}from"./index.916cb52e.js";import{i as o}from"./vue-qrcode.esm.c5e039a4.js";var e=m(async({app:a})=>{a.component(o.name,o)});export{e as default}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/material-icons-v50.fbba257d.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/assets/material-icons-v50.fbba257d.woff2 -------------------------------------------------------------------------------- /public/cashu/assets/scroll.b2adc88e.js: -------------------------------------------------------------------------------- 1 | import{ad as s,ae as l}from"./index.916cb52e.js";const d=[Element,String],r=[null,document,document.body,document.scrollingElement,document.documentElement];function a(o,t){let e=s(t);if(e===void 0){if(o==null)return window;e=o.closest(".scroll,.scroll-y,.overflow-auto")}return r.includes(e)?window:e}function u(o){return o===window?window.pageYOffset||window.scrollY||document.body.scrollTop||0:o.scrollTop}function f(o){return o===window?window.pageXOffset||window.scrollX||document.body.scrollLeft||0:o.scrollLeft}let n;function w(){if(n!==void 0)return n;const o=document.createElement("p"),t=document.createElement("div");l(o,{width:"100%",height:"200px"}),l(t,{position:"absolute",top:"0px",left:"0px",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),t.appendChild(o),document.body.appendChild(t);const e=o.offsetWidth;t.style.overflow="scroll";let i=o.offsetWidth;return e===i&&(i=t.clientWidth),t.remove(),n=e-i,n}function p(o,t=!0){return!o||o.nodeType!==Node.ELEMENT_NODE?!1:t?o.scrollHeight>o.clientHeight&&(o.classList.contains("scroll")||o.classList.contains("overflow-auto")||["auto","scroll"].includes(window.getComputedStyle(o)["overflow-y"])):o.scrollWidth>o.clientWidth&&(o.classList.contains("scroll")||o.classList.contains("overflow-auto")||["auto","scroll"].includes(window.getComputedStyle(o)["overflow-x"]))}export{u as a,f as b,w as c,a as g,p as h,d as s}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/selection.7b8f2356.js: -------------------------------------------------------------------------------- 1 | import{ao as o}from"./index.916cb52e.js";function i(){if(window.getSelection!==void 0){const e=window.getSelection();e.empty!==void 0?e.empty():e.removeAllRanges!==void 0&&(e.removeAllRanges(),o.is.mobile!==!0&&e.addRange(document.createRange()))}else document.selection!==void 0&&document.selection.empty()}export{i as c}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/touch.9135741d.js: -------------------------------------------------------------------------------- 1 | const r={left:!0,right:!0,up:!0,down:!0,horizontal:!0,vertical:!0},o=Object.keys(r);r.all=!0;function n(t){const e={};for(const i of o)t[i]===!0&&(e[i]=!0);return Object.keys(e).length===0?r:(e.horizontal===!0?e.left=e.right=!0:e.left===!0&&e.right===!0&&(e.horizontal=!0),e.vertical===!0?e.up=e.down=!0:e.up===!0&&e.down===!0&&(e.vertical=!0),e.horizontal===!0&&e.vertical===!0&&(e.all=!0),e)}const u=["INPUT","TEXTAREA"];function l(t,e){return e.event===void 0&&t.target!==void 0&&t.target.draggable!==!0&&typeof e.handler=="function"&&u.includes(t.target.nodeName.toUpperCase())===!1&&(t.qClonedBy===void 0||t.qClonedBy.indexOf(e.uid)===-1)}export{n as g,l as s}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/web.050b05b0.js: -------------------------------------------------------------------------------- 1 | import{Z as t}from"./ui.b6121134.js";import"./index.916cb52e.js";import"./index.42f06193.js";class o extends t{async getSafeAreaInsets(){return{insets:{top:0,left:0,right:0,bottom:0}}}async getStatusBarHeight(){return{statusBarHeight:0}}setImmersiveNavigationBar(){throw this.unimplemented("Method not supported on Web.")}}export{o as SafeAreaWeb}; 2 | -------------------------------------------------------------------------------- /public/cashu/assets/web.e3bae036.js: -------------------------------------------------------------------------------- 1 | import{Z as a,_ as i,$ as r}from"./ui.b6121134.js";import"./index.916cb52e.js";import"./index.42f06193.js";class l extends a{constructor(){super(...arguments),this.selectionStarted=!1}async impact(t){const e=this.patternForImpact(t?.style);this.vibrateWithPattern(e)}async notification(t){const e=this.patternForNotification(t?.type);this.vibrateWithPattern(e)}async vibrate(t){const e=t?.duration||300;this.vibrateWithPattern([e])}async selectionStart(){this.selectionStarted=!0}async selectionChanged(){this.selectionStarted&&this.vibrateWithPattern([70])}async selectionEnd(){this.selectionStarted=!1}patternForImpact(t=i.Heavy){return t===i.Medium?[43]:t===i.Light?[20]:[61]}patternForNotification(t=r.Success){return t===r.Warning?[30,40,30,50,60]:t===r.Error?[27,45,50]:[35,65,21]}vibrateWithPattern(t){if(navigator.vibrate)navigator.vibrate(t);else throw this.unavailable("Browser does not support the vibrate API")}}export{l as HapticsWeb}; 2 | -------------------------------------------------------------------------------- /public/cashu/clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/clean.png -------------------------------------------------------------------------------- /public/cashu/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/favicon.ico -------------------------------------------------------------------------------- /public/cashu/icons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-icon-167x167.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1080x2340.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1080x2340.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1125x2436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1125x2436.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1170x2532.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1170x2532.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1179x2556.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1179x2556.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1242x2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1242x2208.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1242x2688.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1242x2688.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1284x2778.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1284x2778.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1290x2796.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1290x2796.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1536x2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1536x2048.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1620x2160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1620x2160.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1668x2224.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1668x2224.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-1668x2388.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-1668x2388.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-2048x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-2048x2732.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-750x1334.png -------------------------------------------------------------------------------- /public/cashu/icons/apple-launch-828x1792.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/apple-launch-828x1792.png -------------------------------------------------------------------------------- /public/cashu/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/favicon-128x128.png -------------------------------------------------------------------------------- /public/cashu/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/cashu/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/cashu/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/favicon-96x96.png -------------------------------------------------------------------------------- /public/cashu/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/icon-128x128.png -------------------------------------------------------------------------------- /public/cashu/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/cashu/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/icon-256x256.png -------------------------------------------------------------------------------- /public/cashu/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/icon-384x384.png -------------------------------------------------------------------------------- /public/cashu/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/icon-512x512.png -------------------------------------------------------------------------------- /public/cashu/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/cashu/index.html: -------------------------------------------------------------------------------- 1 | Cashu.me 2 | 3 |
-------------------------------------------------------------------------------- /public/cashu/nostr-icon.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/cashu/screenshots/narrow-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/screenshots/narrow-1.png -------------------------------------------------------------------------------- /public/cashu/screenshots/narrow-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/screenshots/narrow-2.png -------------------------------------------------------------------------------- /public/cashu/screenshots/wide-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/screenshots/wide-1.png -------------------------------------------------------------------------------- /public/cashu/screenshots/wide-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/cashu/screenshots/wide-2.png -------------------------------------------------------------------------------- /public/cashu/x-logo.svg: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/clean.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/favicon.png -------------------------------------------------------------------------------- /public/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/icon128.png -------------------------------------------------------------------------------- /public/img/irisconnects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/irisconnects.png -------------------------------------------------------------------------------- /public/img/maskable_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/maskable_icon.png -------------------------------------------------------------------------------- /public/img/maskable_icon_x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/maskable_icon_x192.png -------------------------------------------------------------------------------- /public/img/zap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/public/img/zap.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Iris", 3 | "name": "Iris", 4 | "description": "Connecting People", 5 | "id": "/", 6 | "icons": [ 7 | { 8 | "src": "/img/android-chrome-192x192.png", 9 | "sizes": "192x192", 10 | "type": "image/png" 11 | }, 12 | { 13 | "src": "/img/android-chrome-512x512.png", 14 | "sizes": "512x512", 15 | "type": "image/png", 16 | "purpose": "any" 17 | }, 18 | { 19 | "src": "/img/maskable_icon.png", 20 | "sizes": "640x640", 21 | "type": "image/png", 22 | "purpose": "maskable" 23 | }, 24 | { 25 | "src": "/img/maskable_icon_x192.png", 26 | "sizes": "192x192", 27 | "type": "image/png", 28 | "purpose": "maskable" 29 | } 30 | ], 31 | "start_url": "/", 32 | "display": "standalone", 33 | "theme_color": "#000000", 34 | "background_color": "#000000", 35 | "protocol_handlers": [ 36 | { 37 | "protocol": "web+nostr", 38 | "url": "/%s" 39 | } 40 | ], 41 | "screenshots": [], 42 | "display_override": ["fullscreen"] 43 | } 44 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/assets/Bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/Bitcoin.png -------------------------------------------------------------------------------- /src/assets/alby-logo.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/alby-logo.avif -------------------------------------------------------------------------------- /src/assets/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/banner.jpg -------------------------------------------------------------------------------- /src/assets/chrome-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/chrome-logo.png -------------------------------------------------------------------------------- /src/assets/default-repo-pic.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/default-repo-pic.webp -------------------------------------------------------------------------------- /src/assets/default_profile_pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/default_profile_pic.jpg -------------------------------------------------------------------------------- /src/assets/feed-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/feed-icon.png -------------------------------------------------------------------------------- /src/assets/firefox-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/firefox-logo.png -------------------------------------------------------------------------------- /src/assets/git-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/git-logo.png -------------------------------------------------------------------------------- /src/assets/highlights-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/highlights-icon.png -------------------------------------------------------------------------------- /src/assets/hornet-storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/hornet-storage.png -------------------------------------------------------------------------------- /src/assets/landing-page-img-coder.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/landing-page-img-coder.avif -------------------------------------------------------------------------------- /src/assets/landing-page-img-nostrich.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/landing-page-img-nostrich.avif -------------------------------------------------------------------------------- /src/assets/long-form-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/long-form-icon.png -------------------------------------------------------------------------------- /src/assets/nestr-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/nestr-logo.png -------------------------------------------------------------------------------- /src/assets/note-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/note-icon.png -------------------------------------------------------------------------------- /src/assets/quotes-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/quotes-icon.png -------------------------------------------------------------------------------- /src/assets/reels-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/reels-icon.png -------------------------------------------------------------------------------- /src/assets/running-ostrich.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/running-ostrich.gif -------------------------------------------------------------------------------- /src/assets/satoshi-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/satoshi-white.png -------------------------------------------------------------------------------- /src/assets/satoshi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/wallet-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irislib/iris-client/90cad78bca32db028d063af64008212d027d4d8a/src/assets/wallet-icon.png -------------------------------------------------------------------------------- /src/pages/Page404.tsx: -------------------------------------------------------------------------------- 1 | import {useNavigate} from "react-router" 2 | 3 | export const Page404 = () => { 4 | const navigate = useNavigate() 5 | 6 | const handleHomeClick = () => { 7 | navigate("/") 8 | } 9 | 10 | return ( 11 |
12 |
13 |
14 |

404

15 |

The page you are looking for could not be found.

16 | 19 |
20 |
21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/chats/NewChat.tsx: -------------------------------------------------------------------------------- 1 | import NotificationPrompt from "@/shared/components/NotificationPrompt" 2 | import InstallPWAPrompt from "@/shared/components/InstallPWAPrompt" 3 | import PrivateChatCreation from "./private/PrivateChatCreation" 4 | import {Link, Routes, Route, useLocation} from "react-router" 5 | import PublicChatCreation from "./public/PublicChatCreation" 6 | import Header from "@/shared/components/header/Header" 7 | 8 | const TabSelector = () => { 9 | const location = useLocation() 10 | const isPublic = location.pathname === "/chats/new/public" 11 | 12 | const getClasses = (isActive: boolean) => { 13 | const baseClasses = "border-highlight cursor-pointer flex justify-center flex-1 p-3" 14 | return isActive 15 | ? `${baseClasses} border-b border-1` 16 | : `${baseClasses} text-base-content/70 hover:text-base-content border-b border-1 border-transparent` 17 | } 18 | 19 | return ( 20 |
21 | 22 | Private 23 | 24 | 25 | Public 26 | 27 |
28 | ) 29 | } 30 | 31 | const NewChat = () => { 32 | return ( 33 | <> 34 |
35 | 36 | 37 | 38 | } /> 39 | } /> 40 | 41 | 42 | 43 | ) 44 | } 45 | 46 | export default NewChat 47 | -------------------------------------------------------------------------------- /src/pages/chats/index.tsx: -------------------------------------------------------------------------------- 1 | import PublicChatDetails from "./public/PublicChatDetails" 2 | import {useLocation, Routes, Route} from "react-router" 3 | import PrivateChat from "./private/PrivateChat" 4 | import PublicChat from "./public/PublicChat" 5 | import ChatList from "./list/ChatList" 6 | import {Helmet} from "react-helmet" 7 | import classNames from "classnames" 8 | import NewChat from "./NewChat" 9 | 10 | function Messages() { 11 | const location = useLocation() 12 | const isMessagesRoot = location.pathname === "/chats" 13 | 14 | return ( 15 |
16 | 25 |
31 | 32 | } /> 33 | } 36 | /> 37 | } /> 38 | } /> 39 | } /> 40 | 41 |
42 | 43 | Messages 44 | 45 |
46 | ) 47 | } 48 | 49 | export default Messages 50 | -------------------------------------------------------------------------------- /src/pages/chats/public/PublicChatContext.tsx: -------------------------------------------------------------------------------- 1 | import {createContext, Dispatch, SetStateAction} from "react" 2 | 3 | export const PublicChatContext = createContext<{ 4 | setPublicChatTimestamps: Dispatch>> | null 5 | }>({setPublicChatTimestamps: null}) 6 | -------------------------------------------------------------------------------- /src/pages/chats/utils/channelSearch.ts: -------------------------------------------------------------------------------- 1 | import {ChannelMetadata} from "./channelMetadata" 2 | import {LRUCache} from "typescript-lru-cache" 3 | import Fuse from "fuse.js" 4 | 5 | // LRU cache for channel metadata 6 | const channelCache = new LRUCache({maxSize: 200}) 7 | 8 | // Fuse.js search index 9 | let fuse: Fuse | null = null 10 | 11 | // Update the search index with new channel metadata 12 | export const updateChannelSearchIndex = ( 13 | channelId: string, 14 | metadata: ChannelMetadata 15 | ) => { 16 | // Update the cache 17 | channelCache.set(channelId, metadata) 18 | 19 | // Recreate the Fuse.js index 20 | const channels = Array.from(channelCache.values()) 21 | fuse = new Fuse(channels, { 22 | keys: ["name", "about"], 23 | threshold: 0.3, 24 | includeScore: true, 25 | }) 26 | } 27 | 28 | // Search for channels using the Fuse.js index 29 | export const searchChannels = (query: string): ChannelMetadata[] => { 30 | if (!fuse || !query.trim()) { 31 | return [] 32 | } 33 | 34 | const results = fuse.search(query) 35 | // Deduplicate results by channel ID 36 | const seenIds = new Set() 37 | return results 38 | .map((result) => result.item) 39 | .filter((metadata) => { 40 | if (seenIds.has(metadata.founderPubkey)) { 41 | return false 42 | } 43 | seenIds.add(metadata.founderPubkey) 44 | return true 45 | }) 46 | } 47 | 48 | // Get a channel from the cache 49 | export const getCachedChannel = (channelId: string): ChannelMetadata | undefined => { 50 | return channelCache.get(channelId) ?? undefined 51 | } 52 | -------------------------------------------------------------------------------- /src/pages/chats/utils/constants.ts: -------------------------------------------------------------------------------- 1 | // NIP-28 event kinds 2 | export const CHANNEL_CREATE = 40 3 | export const CHANNEL_MESSAGE = 42 4 | export const CHANNEL_HIDE_MESSAGE = 43 5 | export const CHANNEL_MUTE_USER = 44 6 | 7 | // NIP-25 reactions 8 | export const REACTION_KIND = 7 9 | -------------------------------------------------------------------------------- /src/pages/home/feed/components/WalletFeedItem.tsx: -------------------------------------------------------------------------------- 1 | import {RiFlashlightLine} from "@remixicon/react" 2 | 3 | import {Avatar} from "@/shared/components/user/Avatar.tsx" 4 | import {Name} from "@/shared/components/user/Name.tsx" 5 | 6 | const WalletFeedItem = () => { 7 | return ( 8 |
9 |
10 | 11 | -1 12 |
13 |
14 | 15 |
16 | 17 | 18 | Zapped you for a total of -1 sats. 19 | 20 | Zap message here. 21 |
22 |
23 |
24 | ) 25 | } 26 | 27 | export default WalletFeedItem 28 | -------------------------------------------------------------------------------- /src/pages/home/index.tsx: -------------------------------------------------------------------------------- 1 | import HomeFeedEvents from "@/pages/home/feed/components/HomeFeedEvents.tsx" 2 | import RightColumn from "@/shared/components/RightColumn.tsx" 3 | import Trending from "@/shared/components/feed/Trending.tsx" 4 | import Widget from "@/shared/components/ui/Widget.tsx" 5 | 6 | function Index() { 7 | return ( 8 |
9 |
10 | 11 |
12 | 13 | {() => ( 14 | <> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | )} 23 | 24 |
25 | ) 26 | } 27 | 28 | export default Index 29 | -------------------------------------------------------------------------------- /src/pages/notifications/Notifications.tsx: -------------------------------------------------------------------------------- 1 | import NotificationsFeed from "@/shared/components/feed/NotificationsFeed.tsx" 2 | import RightColumn from "@/shared/components/RightColumn" 3 | import Trending from "@/shared/components/feed/Trending" 4 | import Header from "@/shared/components/header/Header" 5 | import Widget from "@/shared/components/ui/Widget" 6 | 7 | import {subscribeToNotifications} from "@/utils/notifications" 8 | import {useEffect} from "react" 9 | let subscribed = false 10 | 11 | function Notifications() { 12 | useEffect(() => { 13 | if (subscribed) { 14 | return 15 | } 16 | subscribeToNotifications() 17 | subscribed = true 18 | }) 19 | 20 | return ( 21 |
22 |
23 |
24 | 25 |
26 | 27 | {() => ( 28 | <> 29 | 30 | 31 | 32 | 33 | )} 34 | 35 |
36 | ) 37 | } 38 | 39 | export default Notifications 40 | -------------------------------------------------------------------------------- /src/pages/settings/Appearance.tsx: -------------------------------------------------------------------------------- 1 | import {useSettingsStore} from "@/stores/settings" 2 | import {ChangeEvent} from "react" 3 | 4 | function AppearanceSettings() { 5 | const {appearance, updateAppearance} = useSettingsStore() 6 | 7 | function handleThemeChange(e: ChangeEvent) { 8 | updateAppearance({theme: e.target.value}) 9 | } 10 | 11 | return ( 12 |
13 |

Appearance

14 |
15 |
16 |

Theme

17 |
18 | 28 |
29 |
30 |
31 |
32 | ) 33 | } 34 | 35 | export default AppearanceSettings 36 | -------------------------------------------------------------------------------- /src/pages/settings/Backup.tsx: -------------------------------------------------------------------------------- 1 | import CopyButton from "@/shared/components/button/CopyButton.tsx" 2 | import {hexToBytes} from "@noble/hashes/utils" 3 | import {useUserStore} from "@/stores/user" 4 | import {nip19} from "nostr-tools" 5 | 6 | function Backup() { 7 | const privateKey = useUserStore((state) => state.privateKey) 8 | 9 | return ( 10 |
11 |

Backup

12 |
13 | {privateKey && ( 14 |
15 |

Backup your Nostr key

16 | Copy and securely store your secret key. 17 |
18 | 23 |
24 |
25 | )} 26 |
27 |

Backup your Notes

28 | Export all your notes for safekeeping. 29 |
30 | 33 |
34 |
35 |
36 |
37 | ) 38 | } 39 | 40 | export default Backup 41 | -------------------------------------------------------------------------------- /src/pages/settings/IrisAccount/AccountName.tsx: -------------------------------------------------------------------------------- 1 | import {useNavigate} from "react-router" 2 | 3 | interface AccountNameProps { 4 | name?: string 5 | link?: boolean 6 | } 7 | 8 | export default function AccountName({name = "", link = true}: AccountNameProps) { 9 | const navigate = useNavigate() 10 | return ( 11 | <> 12 |
13 | Username: {name} 14 |
15 |
16 | Short link:{" "} 17 | {link ? ( 18 | { 21 | e.preventDefault() 22 | navigate(`/${name}`) 23 | }} 24 | > 25 | iris.to/{name} 26 | 27 | ) : ( 28 | <>iris.to/{name} 29 | )} 30 |
31 |
32 | Nostr address (nip05): {name}@iris.to 33 |
34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/pages/settings/IrisAccount/ActiveAccount.tsx: -------------------------------------------------------------------------------- 1 | import AccountName from "./AccountName" 2 | import {ndk} from "@/utils/ndk" 3 | 4 | interface ActiveAccountProps { 5 | name?: string 6 | setAsPrimary: () => void 7 | myPub?: string 8 | } 9 | 10 | export default function ActiveAccount({ 11 | name = "", 12 | setAsPrimary = () => {}, 13 | myPub = "", 14 | }: ActiveAccountProps) { 15 | async function saveProfile(nip05: string) { 16 | const user = ndk().getUser({pubkey: myPub}) 17 | user.profile = user.profile || {nip05} 18 | user.publish() 19 | } 20 | 21 | const onClick = async () => { 22 | const profile = ndk().getUser({pubkey: myPub}).profile 23 | const newNip = name + "@iris.to" 24 | const timeout = setTimeout(() => { 25 | saveProfile(newNip) 26 | }, 2000) 27 | if (profile) { 28 | clearTimeout(timeout) 29 | if (profile.nip05 !== newNip) { 30 | saveProfile(newNip) 31 | setAsPrimary() 32 | } 33 | } 34 | } 35 | 36 | return ( 37 |
38 |
39 | You have an active iris.to username: 40 | 41 |
42 |

43 | 46 |

47 |
48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/settings/IrisAccount/ChallengeForm.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect} from "react" 2 | 3 | interface ChallengeFormProps { 4 | onVerify: (token: string) => void 5 | } 6 | 7 | function ChallengeForm({onVerify}: ChallengeFormProps) { 8 | useEffect(() => { 9 | // Setup callback for Cloudflare 10 | window.cf_turnstile_callback = (token: string) => onVerify(token) 11 | 12 | // Load Cloudflare script 13 | if ( 14 | !document.querySelector( 15 | 'script[src="https://challenges.cloudflare.com/turnstile/v0/api.js"]' 16 | ) 17 | ) { 18 | const script = document.createElement("script") 19 | script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js" 20 | script.async = true 21 | script.defer = true 22 | document.body.appendChild(script) 23 | } 24 | 25 | return () => { 26 | delete window.cf_turnstile_callback 27 | } 28 | }, [onVerify]) 29 | 30 | return ( 31 |
40 | ) 41 | } 42 | 43 | export default ChallengeForm 44 | -------------------------------------------------------------------------------- /src/pages/settings/IrisAccount/IrisSettings.tsx: -------------------------------------------------------------------------------- 1 | import IrisAccount from "./IrisAccount" 2 | 3 | function IrisSettings() { 4 | return ( 5 |
6 |

Iris.to username

7 |
8 |
9 | 10 |
11 |
12 |
13 | ) 14 | } 15 | 16 | export default IrisSettings 17 | -------------------------------------------------------------------------------- /src/pages/settings/Privacy.tsx: -------------------------------------------------------------------------------- 1 | import {useSettingsStore} from "@/stores/settings" 2 | import {ChangeEvent} from "react" 3 | 4 | function PrivacySettings() { 5 | const {privacy, updatePrivacy} = useSettingsStore() 6 | 7 | function handleEnableAnalyticsChange(e: ChangeEvent) { 8 | updatePrivacy({enableAnalytics: e.target.checked}) 9 | } 10 | 11 | return ( 12 |
13 |

Privacy

14 |
15 |
16 | 25 |
26 |
27 |
28 | ) 29 | } 30 | 31 | export default PrivacySettings 32 | -------------------------------------------------------------------------------- /src/pages/user/components/FollowList.tsx: -------------------------------------------------------------------------------- 1 | import {useState} from "react" 2 | 3 | import InfiniteScroll from "@/shared/components/ui/InfiniteScroll.tsx" // Make sure to import InfiniteScroll 4 | import ProfileCard from "@/shared/components/user/ProfileCard" 5 | import useFollows from "@/shared/hooks/useFollows" 6 | 7 | interface FollowListProps { 8 | follows?: string[] 9 | pubKey?: string 10 | initialDisplayCount?: number 11 | showAbout?: boolean 12 | } 13 | 14 | function FollowList({ 15 | follows, 16 | pubKey = "", 17 | initialDisplayCount = 10, 18 | showAbout = false, 19 | }: FollowListProps) { 20 | const [displayCount, setDisplayCount] = useState(initialDisplayCount) // Start by displaying 10 items 21 | const f = useFollows(pubKey) 22 | 23 | if (!pubKey && !follows) { 24 | throw new Error("FollowList needs follows or pubKey param") 25 | } 26 | 27 | const localFollows = follows || f 28 | 29 | const loadMoreFollows = () => { 30 | if (displayCount < localFollows.length) { 31 | setDisplayCount((prevCount) => 32 | Math.min(prevCount + initialDisplayCount * 2, localFollows.length) 33 | ) // Load 10 more items at a time 34 | } 35 | } 36 | 37 | return ( 38 | <> 39 | 40 |
41 | {localFollows.slice(0, displayCount).map((pubkey) => ( 42 | 43 | ))} 44 |
45 |
46 | 47 | ) 48 | } 49 | 50 | export default FollowList 51 | -------------------------------------------------------------------------------- /src/pages/user/components/MediaTab.tsx: -------------------------------------------------------------------------------- 1 | function MediaTab() { 2 | return
3 | } 4 | 5 | export default MediaTab 6 | -------------------------------------------------------------------------------- /src/pages/user/components/ProfileAvatar.tsx: -------------------------------------------------------------------------------- 1 | import {NDKUserProfile} from "@nostr-dev-kit/ndk" 2 | import {useNavigate} from "react-router" 3 | 4 | interface ProfileAvatarProps { 5 | profile: NDKUserProfile | null | undefined 6 | pubkey: string 7 | } 8 | 9 | function ProfileAvatar({profile, pubkey}: ProfileAvatarProps) { 10 | const navigate = useNavigate() 11 | 12 | const handleUserNameClick = () => { 13 | navigate(`/${pubkey}`) 14 | } 15 | 16 | // ndk's fetchProfile returns a profile with .image 17 | // but kind 0 events have profiles with .picture 18 | let image = profile?.image 19 | if (!image && typeof profile?.picture === "string") image = profile?.picture 20 | 21 | return ( 22 |
23 | {/* Replace MUI Avatar with a custom Avatar component */} 24 |
25 | ) 26 | } 27 | 28 | export default ProfileAvatar 29 | -------------------------------------------------------------------------------- /src/pages/user/components/ProfileName.tsx: -------------------------------------------------------------------------------- 1 | import {RiVerifiedBadgeLine, RiErrorWarningLine} from "@remixicon/react" 2 | import {useNip05Validation} from "@/shared/hooks/useNip05Validation" 3 | import {NDKUserProfile} from "@nostr-dev-kit/ndk" 4 | import {useNavigate} from "react-router" 5 | import {useCallback} from "react" 6 | 7 | interface ProfileNameProps { 8 | profile?: NDKUserProfile 9 | pubkey: string 10 | } 11 | 12 | function ProfileName({profile, pubkey}: ProfileNameProps) { 13 | const navigate = useNavigate() 14 | const nip05valid = useNip05Validation(pubkey, profile?.nip05) 15 | 16 | const handleClick = useCallback(() => navigate(`/${pubkey}`), [pubkey]) 17 | 18 | return ( 19 |
20 | 21 | {profile?.name && {profile.name}} 22 | {profile?.name && profile?.displayName && ( 23 | {profile?.displayName} 24 | )} 25 | {!profile?.name && profile?.displayName && {profile?.displayName}} 26 | 27 | {!profile?.name && !profile?.displayName && Anonymous Nostrich} 28 | {profile?.nip05 && ( 29 | 30 | {nip05valid ? ( 31 | 32 | ) : ( 33 | 34 | )} 35 | {profile?.nip05} 36 | 37 | )} 38 |
39 | ) 40 | } 41 | 42 | export default ProfileName 43 | -------------------------------------------------------------------------------- /src/pages/wallet/WalletPage.tsx: -------------------------------------------------------------------------------- 1 | import RightColumn from "@/shared/components/RightColumn.tsx" 2 | import Trending from "@/shared/components/feed/Trending.tsx" 3 | import Widget from "@/shared/components/ui/Widget" 4 | import {useUserStore} from "@/stores/user" 5 | import {useNavigate} from "react-router" 6 | import {useEffect} from "react" 7 | 8 | export default function WalletPage() { 9 | const navigate = useNavigate() 10 | const myPubKey = useUserStore((state) => state.publicKey) 11 | const cashuEnabled = useUserStore((state) => state.cashuEnabled) 12 | 13 | useEffect(() => { 14 | if (!cashuEnabled) { 15 | navigate("/settings/wallet", {replace: true}) 16 | } 17 | }, [navigate, cashuEnabled]) 18 | 19 | if (!cashuEnabled) { 20 | return null 21 | } 22 | 23 | return ( 24 |
25 |
26 | {myPubKey && ( 27 |
28 | 39 |
40 | )} 41 |
42 | 43 | {() => ( 44 | <> 45 | 46 | 47 | 48 | 49 | )} 50 | 51 |
52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /src/shared/components/Icons/Icon.tsx: -------------------------------------------------------------------------------- 1 | import {MouseEventHandler} from "react" 2 | 3 | import IconsSvg from "./icons.svg" 4 | 5 | export interface IconProps { 6 | name: string 7 | size?: number 8 | height?: number 9 | className?: string 10 | onClick?: MouseEventHandler 11 | } 12 | 13 | const Icon = (props: IconProps) => { 14 | const size = props.size || 20 15 | const href = `${IconsSvg}#` + props.name 16 | 17 | return ( 18 | 24 | 25 | 26 | ) 27 | } 28 | 29 | export default Icon 30 | -------------------------------------------------------------------------------- /src/shared/components/LoadingFallback.tsx: -------------------------------------------------------------------------------- 1 | export const LoadingFallback = () =>
Loading...
2 | -------------------------------------------------------------------------------- /src/shared/components/NotificationPrompt.tsx: -------------------------------------------------------------------------------- 1 | import {subscribeToDMNotifications, subscribeToNotifications} from "@/utils/notifications" 2 | import {useNotificationsStore} from "@/stores/notifications" 3 | import {useUserStore} from "@/stores/user" 4 | import {useEffect, useState} from "react" 5 | 6 | const NotificationPrompt = () => { 7 | const [showPrompt, setShowPrompt] = useState(false) 8 | const {notificationsDeclined, setNotificationsDeclined} = useNotificationsStore() 9 | const {publicKey: myPubKey} = useUserStore() 10 | 11 | useEffect(() => { 12 | setShowPrompt( 13 | !!myPubKey && 14 | window.Notification?.permission === "default" && 15 | !notificationsDeclined 16 | ) 17 | }, [notificationsDeclined, myPubKey]) 18 | 19 | const handleEnableNotifications = () => { 20 | window.Notification?.requestPermission().then((permission) => { 21 | if (permission === "granted" || permission === "denied") { 22 | setShowPrompt(false) 23 | } 24 | subscribeToNotifications() 25 | subscribeToDMNotifications() 26 | }) 27 | } 28 | 29 | const handleDeclineNotifications = () => { 30 | setNotificationsDeclined(true) 31 | setShowPrompt(false) 32 | } 33 | 34 | if (!showPrompt) return null 35 | 36 | return ( 37 |
38 | Enable push notifications? 39 |
40 | 43 | 46 |
47 |
48 | ) 49 | } 50 | 51 | export default NotificationPrompt 52 | -------------------------------------------------------------------------------- /src/shared/components/RightColumn.tsx: -------------------------------------------------------------------------------- 1 | import SearchBox from "@/shared/components/ui/SearchBox.tsx" 2 | import React, {useState, useEffect} from "react" 3 | import ErrorBoundary from "./ui/ErrorBoundary" 4 | 5 | interface RightColumnProps { 6 | children: () => React.ReactNode 7 | } 8 | 9 | function useWindowWidth() { 10 | const [windowWidth, setWindowWidth] = useState(window.innerWidth) 11 | 12 | useEffect(() => { 13 | const handleResize = () => setWindowWidth(window.innerWidth) 14 | window.addEventListener("resize", handleResize) 15 | return () => window.removeEventListener("resize", handleResize) 16 | }, []) 17 | 18 | return windowWidth 19 | } 20 | 21 | function RightColumn({children}: RightColumnProps) { 22 | const windowWidth = useWindowWidth() 23 | 24 | const isTestEnvironment = 25 | typeof window !== "undefined" && window.location.href.includes("localhost:5173") 26 | 27 | if (windowWidth < 1024 && !isTestEnvironment) { 28 | return null 29 | } 30 | 31 | return ( 32 | 33 |
34 | 35 | {children()} 36 |
37 |
38 | ) 39 | } 40 | 41 | export default RightColumn 42 | -------------------------------------------------------------------------------- /src/shared/components/embed/Audio.tsx: -------------------------------------------------------------------------------- 1 | import Embed from "./index.ts" 2 | 3 | const Audio: Embed = { 4 | regex: /(https?:\/\/\S+\.(?:mp3|wav|ogg|flac)(?:\?\S*)?)\b/gi, 5 | settingsKey: "enableAudio", 6 | component: ({match}) => { 7 | return