├── apps ├── web-app │ ├── .prettierrc │ ├── src │ │ ├── vite-env.d.ts │ │ ├── routes │ │ │ ├── __root.tsx │ │ │ ├── app │ │ │ │ ├── index.tsx │ │ │ │ ├── explore.tsx │ │ │ │ └── profile.tsx │ │ │ ├── status │ │ │ │ └── success.tsx │ │ │ └── index.tsx │ │ ├── user │ │ │ ├── user.service.ts │ │ │ ├── user.controller.ts │ │ │ └── user.module.ts │ │ ├── components │ │ │ ├── pages │ │ │ │ ├── settings │ │ │ │ │ └── update-profile.tsx │ │ │ │ ├── homepage.tsx │ │ │ │ └── explore │ │ │ │ │ ├── potatoe-users.tsx │ │ │ │ │ └── general-users.tsx │ │ │ ├── ui │ │ │ │ ├── skeleton.tsx │ │ │ │ ├── sonner.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── avatar.tsx │ │ │ │ ├── badge.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── tabs.tsx │ │ │ │ ├── tooltip.tsx │ │ │ │ ├── slider.tsx │ │ │ │ ├── button.tsx │ │ │ │ └── table.tsx │ │ │ ├── misc │ │ │ │ ├── ProductHuntCard.tsx │ │ │ │ └── product-hunt-badge.tsx │ │ │ ├── fallbacks │ │ │ │ ├── noDataFound.tsx │ │ │ │ └── wallet-no-connect.tsx │ │ │ ├── wallet │ │ │ │ └── walletProfilePanel..tsx │ │ │ ├── profile │ │ │ │ ├── TipBadge.tsx │ │ │ │ ├── profilePanel.tsx │ │ │ │ └── addressAndBadge.tsx │ │ │ ├── popups │ │ │ │ ├── drawer │ │ │ │ │ └── index.tsx │ │ │ │ └── modals │ │ │ │ │ └── index.tsx │ │ │ ├── github │ │ │ │ ├── GithubUserCardSkeleton.tsx │ │ │ │ └── GithubUserCard.tsx │ │ │ ├── search │ │ │ │ └── GithubUserSearch.tsx │ │ │ ├── typography │ │ │ │ └── index.tsx │ │ │ └── views │ │ │ │ └── settings-drawer-view.tsx │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── auth.service.ts │ │ │ ├── user.service.ts │ │ │ ├── wallet.service.ts │ │ │ └── transaction.service.ts │ │ ├── lib │ │ │ ├── utils.ts │ │ │ └── supabase.ts │ │ ├── data │ │ │ └── dashboardData.ts │ │ ├── enums │ │ │ └── API_ENUM.ts │ │ ├── util │ │ │ ├── extract-wallet-from-user.ts │ │ │ ├── content-utils.ts │ │ │ └── api.ts │ │ ├── pages │ │ │ ├── embed │ │ │ │ └── tip-badge.tsx │ │ │ ├── main │ │ │ │ └── index.tsx │ │ │ ├── explore │ │ │ │ └── ExploreUsers.tsx │ │ │ ├── status │ │ │ │ └── success-page.tsx │ │ │ ├── profile │ │ │ │ └── userProfileCard.tsx │ │ │ └── usersPage.tsx │ │ ├── main.tsx │ │ ├── App.tsx │ │ ├── interface │ │ │ └── users.interface.ts │ │ ├── constant │ │ │ └── index.ts │ │ ├── providers │ │ │ ├── indexProvider.tsx │ │ │ └── walletConnectProvider.tsx │ │ ├── hooks │ │ │ ├── useProfile.ts │ │ │ ├── connect-wallet.ts │ │ │ ├── connect-wallet.tsx │ │ │ ├── extract-user-wallet.ts │ │ │ ├── useSolanaTip.ts │ │ │ └── useAuth.ts │ │ ├── configs │ │ │ └── axios.ts │ │ ├── sections │ │ │ ├── auth.tsx │ │ │ └── hero.tsx │ │ ├── header │ │ │ ├── dashboardHeader.tsx │ │ │ └── nav.tsx │ │ ├── button │ │ │ ├── connectWalletButton.tsx │ │ │ ├── auth.tsx │ │ │ └── index.tsx │ │ ├── store │ │ │ ├── use-fetch-github-data.store.ts │ │ │ └── user.store.ts │ │ ├── misc │ │ │ └── github_users_card.tsx │ │ ├── dashboard │ │ │ └── dashboardBottomTab.tsx │ │ └── layouts │ │ │ └── dashboard.tsx │ ├── vercel.json │ ├── public │ │ ├── logo │ │ │ └── logo.png │ │ ├── favicon_io │ │ │ ├── favicon.ico │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── about.txt │ │ │ └── site.webmanifest │ │ ├── asset │ │ │ └── sounds │ │ │ │ └── magic_sound.mp3 │ │ └── vite.svg │ ├── dev-dist │ │ └── registerSW.js │ ├── tsconfig.json │ ├── .gitignore │ ├── components.json │ ├── tsconfig.node.json │ ├── eslint.config.js │ ├── tsconfig.app.json │ ├── store │ │ └── user.ts │ ├── vite.config.ts │ ├── index.html │ ├── README.md │ └── package.json ├── extension │ ├── src │ │ ├── vite-env.d.ts │ │ ├── constants │ │ │ └── index.ts │ │ ├── images │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ └── 48.png │ │ ├── interface │ │ │ └── index.ts │ │ ├── popup.tsx │ │ ├── popup.html │ │ ├── components │ │ │ └── ui │ │ │ │ └── button.tsx │ │ ├── pages │ │ │ ├── welcome.tsx │ │ │ └── index.tsx │ │ ├── content.tsx │ │ └── manifest.json │ ├── public │ │ ├── icon │ │ │ ├── 16.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ ├── 96.png │ │ │ └── 128.png │ │ └── icon-with-shadow.svg │ ├── postcss.config.js │ ├── tsconfig.node.json │ ├── libs │ │ └── supabase.ts │ ├── .gitignore │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── vite.config.ts │ └── package.json ├── server │ ├── .dockerignore │ ├── bun.lockb │ ├── drizzle │ │ ├── 0001_motionless_loki.sql │ │ ├── 0003_past_kitty_pryde.sql │ │ ├── 0002_easy_shen.sql │ │ ├── meta │ │ │ └── _journal.json │ │ └── 0000_kind_gamora.sql │ ├── tsconfig.json │ ├── .nixpacks.toml │ ├── src │ │ ├── db │ │ │ ├── index.ts │ │ │ └── schema.ts │ │ ├── utils │ │ │ ├── content-format.ts │ │ │ └── telegram-notification.ts │ │ ├── types │ │ │ └── env.d.ts │ │ ├── types.ts │ │ ├── enums │ │ │ └── index.ts │ │ ├── constants │ │ │ └── index.ts │ │ ├── config │ │ │ └── telegraf.ts │ │ ├── middleware │ │ │ └── auth.ts │ │ ├── routes │ │ │ ├── user.ts │ │ │ ├── wallets.ts │ │ │ └── tx-records.ts │ │ └── index.ts │ ├── README.md │ ├── .prettierrc │ ├── drizzle.config.ts │ ├── Dockerfile │ ├── .gitignore │ ├── scripts │ │ └── push-db.ts │ ├── fly.toml │ └── package.json └── github-bot │ ├── .gitignore │ ├── .dockerignore │ ├── Dockerfile │ ├── src │ ├── controllers │ │ ├── index.ts │ │ ├── bounty.controller.ts │ │ ├── issues.controller.ts │ │ └── commands.controller.ts │ ├── enums │ │ └── commands.ts │ └── index.ts │ ├── vitest.config.ts │ ├── .env.example │ ├── test │ ├── fixtures │ │ ├── issues.opened.json │ │ └── mock-cert.pem │ └── index.test.ts │ ├── tsconfig.json │ ├── LICENSE │ ├── README.md │ ├── package.json │ └── CONTRIBUTING.md ├── .DS_Store ├── libs ├── common │ ├── .travis.yml │ ├── src │ │ ├── .eslintrc │ │ ├── index.test.tsx │ │ ├── styles.module.css │ │ ├── index.tsx │ │ └── typings.d.ts │ ├── .eslintignore │ ├── example │ │ ├── src │ │ │ ├── react-app-env.d.ts │ │ │ ├── index.tsx │ │ │ ├── App.tsx │ │ │ ├── setupTests.ts │ │ │ ├── App.test.tsx │ │ │ └── index.css │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── manifest.json │ │ │ └── index.html │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── package.json │ ├── tsconfig.test.json │ ├── .editorconfig │ ├── .prettierrc │ ├── .gitignore │ ├── tsconfig.json │ ├── README.md │ ├── .eslintrc │ └── package.json └── supabase │ └── index.ts ├── pnpm-workspace.yaml ├── constants └── index.ts ├── program ├── programs │ └── program │ │ ├── Xargo.toml │ │ ├── src │ │ └── lib.rs │ │ └── Cargo.toml ├── .gitignore ├── .prettierignore ├── tsconfig.json ├── Cargo.toml ├── Anchor.toml ├── migrations │ └── deploy.ts ├── package.json └── tests │ └── program.ts ├── .gitignore ├── .prettierrc ├── enums └── index.ts ├── interface └── index.interface.ts ├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── custom-issue-template-📝.md │ ├── feature-request-✨.md │ └── bug-report-🐞.md └── workflows │ └── greetings.yml ├── .idea ├── .gitignore ├── modules.xml ├── vcs.xml ├── potato-monorepo.iml └── dataSources.xml ├── config └── axios.ts ├── webpack.config.js ├── README.md ├── package.json └── LICENSE /apps/web-app/.prettierrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /apps/extension/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/.DS_Store -------------------------------------------------------------------------------- /apps/extension/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const GITHUB_HTML_IDENTIFIER = ""; 2 | -------------------------------------------------------------------------------- /libs/common/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | -------------------------------------------------------------------------------- /libs/common/src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /libs/common/.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /libs/common/example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "libs/*" 4 | - "packages/*" 5 | -------------------------------------------------------------------------------- /constants/index.ts: -------------------------------------------------------------------------------- 1 | export const TOKEN = ""; 2 | export const AUTH_TOKEN = "access_token"; 3 | -------------------------------------------------------------------------------- /apps/server/.dockerignore: -------------------------------------------------------------------------------- 1 | fly.toml 2 | Dockerfile 3 | .dockerignore 4 | node_modules 5 | .git 6 | -------------------------------------------------------------------------------- /apps/server/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/server/bun.lockb -------------------------------------------------------------------------------- /apps/web-app/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] 3 | } 4 | -------------------------------------------------------------------------------- /apps/server/drizzle/0001_motionless_loki.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "wallets" DROP CONSTRAINT "wallets_address_unique"; -------------------------------------------------------------------------------- /program/programs/program/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /program/.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | .yarn 8 | -------------------------------------------------------------------------------- /program/.prettierignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | node_modules 5 | dist 6 | build 7 | test-ledger 8 | -------------------------------------------------------------------------------- /apps/server/drizzle/0003_past_kitty_pryde.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "transaction_records" ALTER COLUMN "tx_hash" DROP NOT NULL; -------------------------------------------------------------------------------- /apps/github-bot/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.pem 4 | !mock-cert.pem 5 | .env 6 | coverage 7 | lib 8 | -------------------------------------------------------------------------------- /apps/extension/public/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/public/icon/16.png -------------------------------------------------------------------------------- /apps/extension/public/icon/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/public/icon/32.png -------------------------------------------------------------------------------- /apps/extension/public/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/public/icon/48.png -------------------------------------------------------------------------------- /apps/extension/public/icon/96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/public/icon/96.png -------------------------------------------------------------------------------- /apps/extension/src/images/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/src/images/128.png -------------------------------------------------------------------------------- /apps/extension/src/images/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/src/images/16.png -------------------------------------------------------------------------------- /apps/extension/src/images/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/src/images/48.png -------------------------------------------------------------------------------- /apps/web-app/public/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/logo/logo.png -------------------------------------------------------------------------------- /apps/extension/public/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/extension/public/icon/128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /node_modules 3 | apps/extension/node_modules 4 | apps/*/node_modules 5 | apps/*/.env 6 | .env 7 | */.env 8 | -------------------------------------------------------------------------------- /apps/extension/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /libs/common/example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/libs/common/example/public/favicon.ico -------------------------------------------------------------------------------- /apps/web-app/src/routes/__root.tsx: -------------------------------------------------------------------------------- 1 | import { createRootRoute } from "@tanstack/react-router"; 2 | export const Route = createRootRoute(); 3 | -------------------------------------------------------------------------------- /apps/web-app/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | 3 | @Injectable() 4 | export class UserService {} 5 | -------------------------------------------------------------------------------- /libs/common/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 80, 4 | "arrowParens": "always", 5 | "semi": true, 6 | "bracketSpacing": true 7 | } 8 | -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/favicon_io/favicon.ico -------------------------------------------------------------------------------- /apps/web-app/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "@nestjs/common"; 2 | 3 | @Controller("user") 4 | export class UserController {} 5 | -------------------------------------------------------------------------------- /apps/web-app/public/asset/sounds/magic_sound.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/asset/sounds/magic_sound.mp3 -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/favicon_io/favicon-16x16.png -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/favicon_io/favicon-32x32.png -------------------------------------------------------------------------------- /apps/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "hono/jsx" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/favicon_io/apple-touch-icon.png -------------------------------------------------------------------------------- /apps/web-app/src/components/pages/settings/update-profile.tsx: -------------------------------------------------------------------------------- 1 | const UpdateProfile = () => { 2 | return <>profile; 3 | }; 4 | export default UpdateProfile; 5 | -------------------------------------------------------------------------------- /enums/index.ts: -------------------------------------------------------------------------------- 1 | import { AUTH_TOKEN } from "../constants"; 2 | 3 | export enum Secrete_Enums {} 4 | 5 | export const TOKEN_ENUMS = { 6 | AUTH_TOKEN, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/server/.nixpacks.toml: -------------------------------------------------------------------------------- 1 | [phases.setup] 2 | nixPkgs = ["bun"] 3 | 4 | [phases.build] 5 | cmds = ["bun install"] 6 | 7 | [start] 8 | cmd = "bun run src/index.ts" 9 | -------------------------------------------------------------------------------- /apps/web-app/src/services/index.ts: -------------------------------------------------------------------------------- 1 | import { AuthService } from "./auth.service"; 2 | import UserService from "./user.service"; 3 | export { AuthService, UserService }; 4 | -------------------------------------------------------------------------------- /interface/index.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IGithubUserData { 2 | provider_id: string; 3 | user_name: string; 4 | email: string; 5 | avatar_url: string; 6 | } 7 | -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/favicon_io/android-chrome-192x192.png -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yhoungdev/potatoe-squeezy/HEAD/apps/web-app/public/favicon_io/android-chrome-512x512.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | -------------------------------------------------------------------------------- /apps/server/src/db/index.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { drizzle } from 'drizzle-orm/node-postgres'; 3 | 4 | const db = drizzle(process.env.DATABASE_URL!); 5 | export { db }; 6 | -------------------------------------------------------------------------------- /apps/server/src/utils/content-format.ts: -------------------------------------------------------------------------------- 1 | const escapeMarkdown = (text: string) => { 2 | return text.replace(/[_*[\]()~`>#+\-=|{}.!]/g, '\\$&'); 3 | }; 4 | 5 | export { escapeMarkdown }; 6 | -------------------------------------------------------------------------------- /apps/web-app/dev-dist/registerSW.js: -------------------------------------------------------------------------------- 1 | if ("serviceWorker" in navigator) 2 | navigator.serviceWorker.register("/dev-sw.js?dev-sw", { 3 | scope: "/", 4 | type: "classic", 5 | }); 6 | -------------------------------------------------------------------------------- /apps/server/README.md: -------------------------------------------------------------------------------- 1 | To install dependencies: 2 | 3 | ```sh 4 | bun install 5 | ``` 6 | 7 | To run: 8 | 9 | ```sh 10 | bun run dev 11 | ``` 12 | 13 | open http://localhost:3000 14 | -------------------------------------------------------------------------------- /apps/web-app/src/components/pages/homepage.tsx: -------------------------------------------------------------------------------- 1 | import AuthComponent from "../auth"; 2 | 3 | const Homepage = () => { 4 | return ; 5 | }; 6 | 7 | export default Homepage; 8 | -------------------------------------------------------------------------------- /apps/server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "singleQuote": true 8 | } 9 | -------------------------------------------------------------------------------- /apps/server/src/types/env.d.ts: -------------------------------------------------------------------------------- 1 | type Bindings = { 2 | GITHUB_CLIENT_ID: string; 3 | GITHUB_CLIENT_SECRET: string; 4 | JWT_SECRET: string; 5 | DB: any; 6 | }; 7 | 8 | export type Env = Bindings; 9 | -------------------------------------------------------------------------------- /libs/common/src/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { ExampleComponent } from '.' 2 | 3 | describe('ExampleComponent', () => { 4 | it('is truthy', () => { 5 | expect(ExampleComponent).toBeTruthy() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom-issue-template-📝.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Custom issue template \U0001F4DD" 3 | about: Describe this issue template's purpose here. 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | -------------------------------------------------------------------------------- /libs/common/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /apps/web-app/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /libs/common/example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import App from './App' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /apps/github-bot/.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/.git 3 | **/README.md 4 | **/LICENSE 5 | **/.vscode 6 | **/npm-debug.log 7 | **/coverage 8 | **/.env 9 | **/.editorconfig 10 | **/dist 11 | **/*.pem 12 | Dockerfile 13 | -------------------------------------------------------------------------------- /libs/common/src/styles.module.css: -------------------------------------------------------------------------------- 1 | /* add css module styles here (optional) */ 2 | 3 | .test { 4 | margin: 2em; 5 | padding: 0.5em; 6 | border: 2px solid #000; 7 | font-size: 2em; 8 | text-align: center; 9 | } 10 | -------------------------------------------------------------------------------- /apps/extension/src/interface/index.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | export interface IUser { 3 | email: string; 4 | } 5 | 6 | export interface IButtonProps { 7 | children?: React.ReactNode; 8 | className?: string; 9 | } 10 | -------------------------------------------------------------------------------- /apps/github-bot/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-slim 2 | WORKDIR /usr/src/app 3 | COPY package.json package-lock.json ./ 4 | RUN npm ci --production 5 | RUN npm cache clean --force 6 | ENV NODE_ENV="production" 7 | COPY . . 8 | CMD [ "npm", "start" ] 9 | -------------------------------------------------------------------------------- /apps/web-app/src/routes/app/index.tsx: -------------------------------------------------------------------------------- 1 | import IndexDashboardPage from "@/pages/main"; 2 | import { createFileRoute } from "@tanstack/react-router"; 3 | 4 | export const Route = createFileRoute("/app/")({ 5 | component: IndexDashboardPage, 6 | }); 7 | -------------------------------------------------------------------------------- /apps/extension/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/github-bot/src/controllers/index.ts: -------------------------------------------------------------------------------- 1 | import { handleIssueOpened, handleIssueComment } from "./issues.controller.ts"; 2 | import bountyController from "./bounty.controller.ts"; 3 | 4 | export { handleIssueOpened, handleIssueComment, bountyController }; 5 | -------------------------------------------------------------------------------- /apps/github-bot/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ["test/**/*.test.ts"], 6 | coverage: { 7 | provider: "v8", 8 | }, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /apps/web-app/src/routes/app/explore.tsx: -------------------------------------------------------------------------------- 1 | import ExploreUsers from "@/pages/explore/ExploreUsers"; 2 | import { createFileRoute } from "@tanstack/react-router"; 3 | 4 | export const Route = createFileRoute("/app/explore")({ 5 | component: ExploreUsers, 6 | }); 7 | -------------------------------------------------------------------------------- /apps/web-app/src/routes/status/success.tsx: -------------------------------------------------------------------------------- 1 | import SuccessPage from "@/pages/status/success-page"; 2 | import { createFileRoute } from "@tanstack/react-router"; 3 | 4 | export const Route = createFileRoute("/status/success")({ 5 | component: SuccessPage, 6 | }); 7 | -------------------------------------------------------------------------------- /apps/extension/libs/supabase.ts: -------------------------------------------------------------------------------- 1 | import initializeSupabase from "../../../libs/supabase"; 2 | 3 | // @ts-ignore 4 | const env = import.meta.env; 5 | export const supabaseObject = initializeSupabase( 6 | env.VITE_APP_PROJECT_URL, 7 | env.VITE_APP_PRIVATE_KEY, 8 | ); 9 | -------------------------------------------------------------------------------- /libs/common/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": false, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /apps/web-app/src/data/dashboardData.ts: -------------------------------------------------------------------------------- 1 | export const DASHBOARDNAV = [ 2 | { 3 | title: "Dashboard", 4 | icon: "🛖", 5 | path: "/app", 6 | }, 7 | 8 | { 9 | title: "Explore", 10 | icon: "🌍", 11 | path: "/app/explore", 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /program/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/common/example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ExampleComponent } from 'common' 4 | import 'common/dist/index.css' 5 | 6 | const App = () => { 7 | return 8 | } 9 | 10 | export default App 11 | -------------------------------------------------------------------------------- /config/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | interface IAxiosProps { 4 | baseUrl: string; 5 | } 6 | export const defaultAxios = axios.create({ 7 | baseURL: "http://localhost:5000", 8 | timeout: 10000, 9 | timeoutErrorMessage: "Service timeout please try again later", 10 | }); 11 | -------------------------------------------------------------------------------- /libs/common/example/README.md: -------------------------------------------------------------------------------- 1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | It is linked to the common package in the parent directory for development purposes. 4 | 5 | You can run `yarn install` and then `yarn start` to test your package. 6 | -------------------------------------------------------------------------------- /program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | resolver = "2" 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /apps/github-bot/.env.example: -------------------------------------------------------------------------------- 1 | # The ID of your GitHub App 2 | APP_ID= 3 | WEBHOOK_SECRET=development 4 | 5 | # Use `trace` to get verbose logging or `info` to show less 6 | LOG_LEVEL=debug 7 | 8 | # Go to https://smee.io/new set this to the URL that you are redirected to. 9 | WEBHOOK_PROXY_URL= 10 | -------------------------------------------------------------------------------- /libs/common/example/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect' 6 | -------------------------------------------------------------------------------- /libs/common/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import styles from './styles.module.css' 3 | 4 | interface Props { 5 | text: string 6 | } 7 | 8 | export const ExampleComponent = ({ text }: Props) => { 9 | return
Example Component: {text}
10 | } 11 | -------------------------------------------------------------------------------- /apps/web-app/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { UserService } from "./user.service"; 3 | import { UserController } from "./user.controller"; 4 | 5 | @Module({ 6 | providers: [UserService], 7 | controllers: [UserController], 8 | }) 9 | export class UserModule {} 10 | -------------------------------------------------------------------------------- /libs/common/example/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | ReactDOM.unmountComponentAtNode(div) 9 | }) 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require("webpack"); 2 | 3 | module.exports = { 4 | resolve: { 5 | fallback: { 6 | buffer: require.resolve("buffer"), 7 | }, 8 | }, 9 | plugins: [ 10 | new webpack.ProvidePlugin({ 11 | Buffer: ["buffer", "Buffer"], 12 | }), 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /apps/server/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { defineConfig } from 'drizzle-kit'; 3 | 4 | export default defineConfig({ 5 | out: './drizzle', 6 | schema: './src/db/schema.ts', 7 | dialect: 'postgresql', 8 | dbCredentials: { 9 | url: process.env.DATABASE_URL!, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /apps/server/src/types.ts: -------------------------------------------------------------------------------- 1 | import { InferSelectModel } from 'drizzle-orm'; 2 | import { users, wallets } from './db/schema'; 3 | 4 | export type User = InferSelectModel; 5 | export type Wallet = InferSelectModel; 6 | 7 | export interface UserWithWallets extends User { 8 | wallets: Wallet[]; 9 | } 10 | -------------------------------------------------------------------------------- /apps/extension/src/popup.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import Welcome from "./pages/welcome"; 4 | import SignIn from "./pages"; 5 | 6 | ReactDOM.createRoot(document.body).render( 7 | 8 | {/**/} 9 | 10 | , 11 | ); 12 | -------------------------------------------------------------------------------- /apps/web-app/src/enums/API_ENUM.ts: -------------------------------------------------------------------------------- 1 | enum API_ENDPOINTS { 2 | GITHUB_AUTH = "/auth/login", 3 | GOOGLE_AUTH = "/auth/google", 4 | SIGN_OUT = "/auth/logout", 5 | 6 | USER_PROFILE = "/user/profile", 7 | USER_ALL = "/user/all", 8 | USER_WALLET = "/wallet", 9 | 10 | TRANSACTION_RECORDS = "/tx-records", 11 | } 12 | 13 | export default API_ENDPOINTS; 14 | -------------------------------------------------------------------------------- /apps/web-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | } 10 | ], 11 | "compilerOptions": { 12 | "baseUrl": ".", 13 | "paths": { 14 | "@/*": ["./src/*"] 15 | }, 16 | "jsx": "react-jsx" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/github-bot/test/fixtures/issues.opened.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "issue": { 4 | "number": 1, 5 | "user": { 6 | "login": "hiimbex" 7 | } 8 | }, 9 | "repository": { 10 | "name": "testing-things", 11 | "owner": { 12 | "login": "hiimbex" 13 | } 14 | }, 15 | "installation": { 16 | "id": 2 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/web-app/src/util/extract-wallet-from-user.ts: -------------------------------------------------------------------------------- 1 | import { UserService } from "@/services"; 2 | 3 | const extractWalletFromUser = async (userName: string) => { 4 | try { 5 | const response = await UserService.fetchAllPotatoeUsers(); 6 | return response; 7 | } catch (error) { 8 | return null; 9 | } 10 | }; 11 | 12 | export { extractWalletFromUser }; 13 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /libs/common/example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "common", 3 | "name": "common", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /apps/web-app/src/pages/embed/tip-badge.tsx: -------------------------------------------------------------------------------- 1 | import { TipBadge } from "@/components/profile/TipBadge"; 2 | 3 | export default function TipBadgeEmbed() { 4 | const searchParams = new URLSearchParams(window.location.search); 5 | const username = searchParams.get("user"); 6 | 7 | if (!username) { 8 | return null; 9 | } 10 | 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /apps/web-app/.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 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .vercel 26 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /libs/common/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/about.txt: -------------------------------------------------------------------------------- 1 | This favicon was generated using the following graphics from Twitter Twemoji: 2 | 3 | - Graphics Title: 1f35f.svg 4 | - Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/twitter/twemoji) 5 | - Graphics Source: https://github.com/twitter/twemoji/blob/master/assets/svg/1f35f.svg 6 | - Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) 7 | -------------------------------------------------------------------------------- /program/programs/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | declare_id!("8kfy8YGqzB1EQf87dqhrfuNaagqpm3yVSZG5T7QnQ294"); 4 | 5 | #[program] 6 | pub mod program { 7 | use super::*; 8 | 9 | pub fn initialize(ctx: Context) -> Result<()> { 10 | msg!("Greetings from: {:?}", ctx.program_id); 11 | Ok(()) 12 | } 13 | } 14 | 15 | #[derive(Accounts)] 16 | pub struct Initialize {} 17 | -------------------------------------------------------------------------------- /apps/extension/.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 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # Config files 27 | .webextrc 28 | .webextrc.* 29 | -------------------------------------------------------------------------------- /apps/github-bot/src/controllers/bounty.controller.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "probot"; 2 | const bountyController = async (context: Context) => { 3 | try { 4 | const comment = context.issue({ 5 | body: "💰 This issue has been labeled as a bounty", 6 | }); 7 | 8 | await context.octokit.issues.createComment(comment); 9 | } catch (err) { 10 | console.error(err); 11 | } 12 | }; 13 | 14 | export default bountyController; 15 | -------------------------------------------------------------------------------- /apps/server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | 3 | WORKDIR /app 4 | 5 | RUN apt-get update && apt-get install -y unzip 6 | 7 | RUN curl -fsSL https://bun.sh/install | bash && \ 8 | echo "export PATH=\"/root/.bun/bin:\$PATH\"" >> /root/.bashrc 9 | 10 | ENV PATH="/root/.bun/bin:$PATH" 11 | 12 | COPY package.json ./ 13 | COPY bun.lock* ./ 14 | 15 | RUN bun install --frozen-lockfile 16 | 17 | COPY . . 18 | 19 | CMD ["bun", "run", "src/index.ts"] 20 | -------------------------------------------------------------------------------- /apps/extension/src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Popup 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /program/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | package_manager = "yarn" 3 | 4 | [features] 5 | resolution = true 6 | skip-lint = false 7 | 8 | [programs.localnet] 9 | program = "8kfy8YGqzB1EQf87dqhrfuNaagqpm3yVSZG5T7QnQ294" 10 | 11 | [registry] 12 | url = "https://api.apr.dev" 13 | 14 | [provider] 15 | cluster = "localnet" 16 | wallet = "~/.config/solana/id.json" 17 | 18 | [scripts] 19 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 20 | -------------------------------------------------------------------------------- /program/programs/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "program" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "program" 10 | 11 | [features] 12 | default = [] 13 | cpi = ["no-entrypoint"] 14 | no-entrypoint = [] 15 | no-idl = [] 16 | no-log-ix-name = [] 17 | idl-build = ["anchor-lang/idl-build"] 18 | 19 | 20 | [dependencies] 21 | anchor-lang = "0.31.0" 22 | 23 | -------------------------------------------------------------------------------- /apps/extension/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IButtonProps } from "../../interface"; 3 | 4 | const Button: React.FC = ({ 5 | children, 6 | className, 7 | }: IButtonProps) => { 8 | const customClass = `py-2 9 | px-2 bg-primary-100 10 | rounded-md text-white font-bold`; 11 | return ; 12 | }; 13 | 14 | export default Button; 15 | -------------------------------------------------------------------------------- /libs/common/example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: 5 | -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 6 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: 13 | source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🚀 Features 2 | 3 | - 🔥 **Zap GitHub Users** – Instantly tip developers with SOL for their contributions. 4 | - ⚡ **Get Zapped** – Receive tips from other users for your open-source work. 5 | - 🔗 **Seamless GitHub Integration** – Connect your GitHub profile and start tipping effortlessly. 6 | - 🛠 **Fast & Low-Cost Transactions** – Built on Solana for lightning-fast and cheap payments. 7 | - 🎉 **Support Open Source** – Encourage innovation by rewarding developers directly. 8 | -------------------------------------------------------------------------------- /apps/web-app/src/routes/app/profile.tsx: -------------------------------------------------------------------------------- 1 | import DefaultDashboard from "@/layouts/dashboard"; 2 | import UserProfileCard from "@/pages/profile/userProfileCard"; 3 | import { createFileRoute } from "@tanstack/react-router"; 4 | 5 | export const Route = createFileRoute("/app/profile")({ 6 | component: RouteComponent, 7 | }); 8 | 9 | function RouteComponent() { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/server/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | .pnp 4 | .pnp.js 5 | 6 | # testing 7 | coverage/ 8 | 9 | # production 10 | build/ 11 | dist/ 12 | .output/ 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | .env 18 | .env.local 19 | .env.* 20 | !.env.example 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # IDE 28 | .idea/ 29 | .vscode/ 30 | *.swp 31 | *.swo 32 | 33 | # Bun 34 | bun.lockb 35 | 36 | # Logs 37 | logs 38 | *.log 39 | -------------------------------------------------------------------------------- /apps/web-app/public/favicon_io/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /apps/web-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import IndexProdivder from "./providers/indexProvider.tsx"; 5 | import { Toaster } from "sonner"; 6 | 7 | createRoot(document.getElementById("root")!).render( 8 | 9 | 10 | 11 | 12 | 13 | , 14 | ); 15 | -------------------------------------------------------------------------------- /apps/web-app/src/pages/main/index.tsx: -------------------------------------------------------------------------------- 1 | import WalletTransactionTable from "@/components/tables/transactionTable.tsx"; 2 | import DefaultDashboard from "@/layouts/dashboard.tsx"; 3 | import WalletProfilePanel from "@/components/wallet/walletProfilePanel."; 4 | function IndexDashboardPage() { 5 | return ( 6 | 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default IndexDashboardPage; 14 | -------------------------------------------------------------------------------- /program/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | import * as anchor from "@coral-xyz/anchor"; 6 | 7 | module.exports = async function (provider: anchor.AnchorProvider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /apps/github-bot/src/enums/commands.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 3 | * Yoo, so i Probot does'nt support Ts enums ye 4 | * I am not stupid for using object, it is just for the main time. 5 | * I will change it to enum if Probot supports it. 6 | * For now lets be squashing some potatoes 🍟 7 | */ 8 | const COMMANDS = { 9 | BOUNTY: "/bounty", 10 | CLAIM: "/claim", 11 | CANCEL_BOUNTY: "/cancel-bounty", 12 | EDIT_BOUNTY: "/edit-bounty", 13 | HELP: "/help", 14 | } as const; 15 | 16 | export default COMMANDS; 17 | -------------------------------------------------------------------------------- /.idea/potato-monorepo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/server/scripts/push-db.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from 'drizzle-orm/neon-http'; 2 | import { neon } from '@neondatabase/serverless'; 3 | import * as dotenv from 'dotenv'; 4 | 5 | dotenv.config(); 6 | 7 | async function push() { 8 | try { 9 | const sql = neon(process.env.DATABASE_URL!); 10 | const db = drizzle(sql); 11 | 12 | console.log('Connected to database'); 13 | console.log('Schema push completed'); 14 | } catch (error) { 15 | console.error('Failed to connect:', error); 16 | } 17 | } 18 | 19 | push(); 20 | -------------------------------------------------------------------------------- /apps/web-app/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/App.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /apps/github-bot/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Probot } from "probot"; 2 | import { handleIssueOpened, handleIssueComment } from "./controllers/index.ts"; 3 | import { bountyController } from "./controllers/index.ts"; 4 | 5 | export default (app: Probot) => { 6 | app.log.info("App started 🎉"); 7 | app.on("issues.opened", handleIssueOpened); 8 | app.on("issue_comment.created", async (context) => { 9 | if (context.isBot) return; 10 | await handleIssueComment(context); 11 | }); 12 | app.on("issues.labeled", bountyController); 13 | }; 14 | -------------------------------------------------------------------------------- /apps/web-app/src/components/misc/ProductHuntCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ProductHuntCard: React.FC = () => { 4 | return ( 5 |
6 | 15 |
16 | ); 17 | }; 18 | 19 | export default ProductHuntCard; 20 | -------------------------------------------------------------------------------- /apps/server/src/enums/index.ts: -------------------------------------------------------------------------------- 1 | enum NOTIFICATION_TYPE { 2 | NEW_USER = '🎉 A new user has signed up.', 3 | NEW_MESSAGE = '📩 You have received a new message.', 4 | TRANSACTION_NOTIFICATION = '💳 Your transaction has been processed successfully.', 5 | TIP_NOTIFICATION = '💰 You have received a new tip.', 6 | REMINDER = '⏰ This is a reminder for your upcoming event.', 7 | } 8 | 9 | enum NOTIFICATION_STATUS { 10 | SENT = 'sent', 11 | FAILED = 'failed', 12 | PENDING = 'pending', 13 | } 14 | 15 | export { NOTIFICATION_TYPE, NOTIFICATION_STATUS }; 16 | -------------------------------------------------------------------------------- /program/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "ISC", 3 | "scripts": { 4 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 5 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 6 | }, 7 | "dependencies": { 8 | "@coral-xyz/anchor": "^0.31.0" 9 | }, 10 | "devDependencies": { 11 | "chai": "^4.3.4", 12 | "mocha": "^9.0.3", 13 | "ts-mocha": "^10.0.0", 14 | "@types/bn.js": "^5.1.0", 15 | "@types/chai": "^4.3.0", 16 | "@types/mocha": "^9.0.0", 17 | "typescript": "^5.7.3", 18 | "prettier": "^2.6.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/web-app/src/lib/supabase.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@supabase/supabase-js"; 2 | 3 | const supabaseUrl = import.meta.env.VITE_PROJECT_URL; 4 | const supabaseKey = import.meta.env.VITE_APP_PRIVATE_KEY; 5 | 6 | if (!supabaseUrl || !supabaseKey) { 7 | throw new Error("Missing Supabase environment variables"); 8 | } 9 | 10 | export const supabase = createClient(supabaseUrl, supabaseKey, { 11 | auth: { 12 | autoRefreshToken: true, 13 | persistSession: true, 14 | detectSessionInUrl: true, 15 | }, 16 | }); 17 | 18 | export default supabase; 19 | -------------------------------------------------------------------------------- /libs/common/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default CSS definition for typescript, 3 | * will be overridden with file-specific definitions by rollup 4 | */ 5 | declare module '*.css' { 6 | const content: { [className: string]: string } 7 | export default content 8 | } 9 | 10 | interface SvgrComponent 11 | extends React.StatelessComponent> {} 12 | 13 | declare module '*.svg' { 14 | const svgUrl: string 15 | const svgComponent: SvgrComponent 16 | export default svgUrl 17 | export { svgComponent as ReactComponent } 18 | } 19 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postgresql 6 | true 7 | org.postgresql.Driver 8 | jdbc:postgresql:///potatoe 9 | $ProjectFileDir$ 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/extension/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: { 6 | colors: { 7 | primary: { 8 | 100: "#f4a929", 9 | }, 10 | }, 11 | container: { 12 | padding: { 13 | DEFAULT: "1rem", 14 | sm: "2rem", 15 | lg: "4rem", 16 | xl: "5rem", 17 | "2xl": "6rem", 18 | }, 19 | }, 20 | }, 21 | }, 22 | plugins: [require("daisyui")], 23 | }; 24 | -------------------------------------------------------------------------------- /apps/web-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { RouterProvider, createRouter } from "@tanstack/react-router"; 3 | import { routeTree } from "./routeTree.gen"; 4 | import { Analytics } from "@vercel/analytics/react"; 5 | 6 | const router = createRouter({ routeTree }); 7 | 8 | declare module "@tanstack/react-router" { 9 | interface Register { 10 | router: typeof router; 11 | } 12 | } 13 | function App() { 14 | return ( 15 | <> 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "potato-monorepo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "workspaces": [], 7 | "scripts": { 8 | "format": "prettier --write ./", 9 | "start": "concurrently \"cd apps/web-app && pnpm dev\" \"cd apps/server && pnpm dev\"" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@radix-ui/react-alert-dialog": "^1.1.6", 14 | "dotenv": "^16.4.7", 15 | "lucid": "^2.21.0", 16 | "prettier": "^3.4.2" 17 | }, 18 | "devDependencies": { 19 | "concurrently": "^9.1.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/web-app/src/interface/users.interface.ts: -------------------------------------------------------------------------------- 1 | interface IPotatoeUser { 2 | id: number; 3 | githubId: string; 4 | username: string; 5 | email: string | null; 6 | name: string; 7 | avatarUrl: string; 8 | createdAt: string; 9 | updatedAt: string; 10 | } 11 | 12 | interface IWallet { 13 | id: number; 14 | userId: number; 15 | address: string; 16 | createdAt: string; 17 | updatedAt: string; 18 | } 19 | 20 | interface IPotatoeUserData { 21 | users: IPotatoeUser; 22 | wallets: IWallet; 23 | } 24 | 25 | export type { IPotatoeUser, IWallet, IPotatoeUserData }; 26 | -------------------------------------------------------------------------------- /program/tests/program.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program } from "@coral-xyz/anchor"; 3 | import { Program } from "../target/types/program"; 4 | 5 | describe("program", () => { 6 | // Configure the client to use the local cluster. 7 | anchor.setProvider(anchor.AnchorProvider.env()); 8 | 9 | const program = anchor.workspace.program as Program; 10 | 11 | it("Is initialized!", async () => { 12 | // Add your test here. 13 | const tx = await program.methods.initialize().rpc(); 14 | console.log("Your transaction signature", tx); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/web-app/src/constant/index.ts: -------------------------------------------------------------------------------- 1 | export const BASE_API_URL = import.meta.env.VITE_API_BASE_URL; 2 | export const TAB_STATE = `cursor-pointer !rounded-none 3 | data-[state=active]:bg-transparent 4 | data-[state=active]:border-b-2 5 | data-[state=active]:text-red-400`; 6 | 7 | export const RPC_KEY = import.meta.env.VITE_RPC_KEY; 8 | const RPC_ENDPOINT = `https://mainnet.helius-rpc.com/?api-key=${RPC_KEY}`; 9 | export const RPC_URL = 10 | import.meta.env.MODE === "development" 11 | ? "https://api.devnet.solana.com" 12 | : RPC_ENDPOINT; 13 | -------------------------------------------------------------------------------- /apps/web-app/src/providers/indexProvider.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import WalletConnectProvider from "./walletConnectProvider"; 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | 5 | const IndexProdivder = ({ children }: { children: ReactNode }) => { 6 | const queryClient = new QueryClient(); 7 | return ( 8 | <> 9 | 10 | 11 | {children} 12 | 13 | 14 | 15 | ); 16 | }; 17 | export default IndexProdivder; 18 | -------------------------------------------------------------------------------- /apps/server/fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for potatoe-squeezy on 2025-03-16T01:06:35+01:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = 'potatoe-squeezy' 7 | primary_region = 'jnb' 8 | 9 | [build] 10 | 11 | [env] 12 | PORT = '8080' 13 | 14 | [http_service] 15 | internal_port = 8080 16 | force_https = true 17 | auto_stop_machines = 'stop' 18 | auto_start_machines = true 19 | min_machines_running = 0 20 | processes = ['app'] 21 | 22 | [[vm]] 23 | memory = '1gb' 24 | cpu_kind = 'shared' 25 | cpus = 1 26 | -------------------------------------------------------------------------------- /apps/web-app/src/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import API_ENDPOINTS from "@/enums/API_ENUM"; 2 | import ApiClient from "@/util/api"; 3 | 4 | class AuthService { 5 | static async signInWithGithub(): Promise { 6 | const response = await ApiClient.get<{ url: string }>( 7 | API_ENDPOINTS.GITHUB_AUTH, 8 | ); 9 | return response; 10 | } 11 | 12 | static async signInWithGoogle(): Promise { 13 | return await ApiClient.get(API_ENDPOINTS.GOOGLE_AUTH); 14 | } 15 | 16 | static async signOut(): Promise { 17 | await ApiClient.post(API_ENDPOINTS.SIGN_OUT); 18 | } 19 | } 20 | 21 | export { AuthService }; 22 | -------------------------------------------------------------------------------- /apps/web-app/src/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import API_ENDPOINTS from "@/enums/API_ENUM"; 2 | import ApiClient from "@/util/api"; 3 | 4 | class UserService { 5 | static async fetchUserProfile() { 6 | const response = await ApiClient.get(API_ENDPOINTS.USER_PROFILE); 7 | return response; 8 | } 9 | 10 | static async fetchUserWallets() { 11 | const response = await ApiClient.get(API_ENDPOINTS.USER_WALLET); 12 | return response; 13 | } 14 | 15 | static async fetchAllPotatoeUsers() { 16 | const response = await ApiClient.get(API_ENDPOINTS.USER_ALL); 17 | return response; 18 | } 19 | } 20 | export default UserService; 21 | -------------------------------------------------------------------------------- /apps/web-app/src/components/fallbacks/noDataFound.tsx: -------------------------------------------------------------------------------- 1 | import { WalletSvg } from "@/assets/svg"; 2 | 3 | interface NoDataFoundProps { 4 | message?: string; 5 | submessage?: string; 6 | } 7 | 8 | export const NoDataFound = ({ 9 | message = "No transactions yet", 10 | submessage = "Your transaction history will appear here", 11 | }: NoDataFoundProps) => { 12 | return ( 13 |
14 | 15 |

{message}

16 |

{submessage}

17 |
18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /apps/extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: actions/first-interaction@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-message: "🎉 Thank you for opening your first issue! We appreciate your contribution and will review it soon. Stay tuned for updates! 🚀" 16 | pr-message: "👏 Amazing! You've submitted your first pull request! Our team will review it shortly. Thanks for helping improve the project! 🎯" 17 | -------------------------------------------------------------------------------- /apps/extension/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import webExtension, { readJsonFile } from "vite-plugin-web-extension"; 4 | 5 | function generateManifest() { 6 | const manifest = readJsonFile("src/manifest.json"); 7 | const pkg = readJsonFile("package.json"); 8 | return { 9 | name: pkg.name, 10 | description: pkg.description, 11 | version: pkg.version, 12 | ...manifest, 13 | }; 14 | } 15 | 16 | // https://vitejs.dev/config/ 17 | export default defineConfig({ 18 | plugins: [ 19 | react(), 20 | webExtension({ 21 | manifest: generateManifest, 22 | }), 23 | ], 24 | }); 25 | -------------------------------------------------------------------------------- /libs/common/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "build"] 22 | } 23 | -------------------------------------------------------------------------------- /libs/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": ["src"], 21 | "exclude": ["node_modules", "dist", "example"] 22 | } 23 | -------------------------------------------------------------------------------- /apps/server/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID!; 2 | export const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET!; 3 | export const JWT_SECRET = process.env.JWT_SECRET!; 4 | export const DATABASE_URL = process.env.DATABASE_URL!; 5 | export const GITHUB_CALLBACK_URL = 6 | process.env.NODE_ENV === 'production' 7 | ? 'https://potatoe-app-production.up.railway.app/auth/callback' 8 | : 'http://localhost:3000/auth/callback'; 9 | export const FRONTEND_APP_URL = process.env.FRONTEND_APP_URL!; 10 | export const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!; 11 | export const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID!; 12 | -------------------------------------------------------------------------------- /libs/common/README.md: -------------------------------------------------------------------------------- 1 | # common 2 | 3 | > french fries 4 | 5 | [![NPM](https://img.shields.io/npm/v/common.svg)](https://www.npmjs.com/package/common) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save common 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```tsx 16 | import React, { Component } from 'react' 17 | 18 | import MyComponent from 'common' 19 | import 'common/dist/index.css' 20 | 21 | class Example extends Component { 22 | render() { 23 | return 24 | } 25 | } 26 | ``` 27 | 28 | ## License 29 | 30 | MIT © [yhoungdev](https://github.com/yhoungdev) 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request-✨.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request ✨ 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /libs/supabase/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient, SupabaseClient } from "@supabase/supabase-js"; 2 | 3 | /** 4 | * @param {string} supabaseUrl - The Supabase project URL. 5 | * @param {string} supabaseAnonKey - The Supabase public anonymous key. 6 | * @returns {SupabaseClient} The initialized Supabase client. 7 | */ 8 | const initializeSupabase = ( 9 | supabaseUrl: string, 10 | supabaseAnonKey: string, 11 | ): SupabaseClient => { 12 | if (!supabaseUrl || !supabaseAnonKey) { 13 | throw new Error( 14 | "Supabase URL and Anon Key are required to initialize the client.", 15 | ); 16 | } 17 | return createClient(supabaseUrl, supabaseAnonKey); 18 | }; 19 | 20 | export default initializeSupabase; 21 | -------------------------------------------------------------------------------- /apps/web-app/src/components/wallet/walletProfilePanel..tsx: -------------------------------------------------------------------------------- 1 | import ProfilePanel from "../profile/profilePanel"; 2 | import { useUserStore } from "@/store/user.store"; 3 | 4 | function WalletProfilePanel() { 5 | const userStore = useUserStore(); 6 | const users = userStore?.user?.users ?? {}; 7 | const address = userStore?.user?.wallets?.address ?? ""; 8 | 9 | const { avatarUrl, username, name } = users; 10 | 11 | return ( 12 |
13 | 20 |
21 | ); 22 | } 23 | 24 | export default WalletProfilePanel; 25 | -------------------------------------------------------------------------------- /apps/web-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /apps/github-bot/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2022", 5 | "module": "Node16", 6 | "declaration": true, 7 | "sourceMap": true, 8 | "outDir": "./lib", 9 | 10 | /* Strict Type-Checking Options */ 11 | "strict": true, 12 | 13 | /* Additional Checks */ 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "allowImportingTsExtensions": true, 19 | 20 | "moduleResolution": "Node16", 21 | "esModuleInterop": true, 22 | "forceConsistentCasingInFileNames": true 23 | }, 24 | "include": ["src/"], 25 | "compileOnSave": false 26 | } 27 | -------------------------------------------------------------------------------- /apps/server/src/config/telegraf.ts: -------------------------------------------------------------------------------- 1 | import { Telegraf } from 'telegraf'; 2 | import { TELEGRAM_BOT_TOKEN } from '../constants'; 3 | 4 | const telegram_bot = new Telegraf(TELEGRAM_BOT_TOKEN); 5 | 6 | let isBotLaunched = false; 7 | 8 | const launchBot = async () => { 9 | if (!isBotLaunched) { 10 | try { 11 | await telegram_bot.launch(); 12 | isBotLaunched = true; 13 | console.log('✅ Telegram bot launched successfully!'); 14 | } catch (error) { 15 | console.error('🚫 Failed to launch Telegram bot:', error); 16 | } 17 | 18 | process.once('SIGINT', () => telegram_bot.stop('SIGINT')); 19 | process.once('SIGTERM', () => telegram_bot.stop('SIGTERM')); 20 | } 21 | }; 22 | 23 | export { telegram_bot, launchBot }; 24 | -------------------------------------------------------------------------------- /apps/github-bot/src/controllers/issues.controller.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "probot"; 2 | 3 | export async function handleIssueOpened(context: Context) { 4 | try { 5 | const issueComment = context.issue({ 6 | body: "Thanks for opening this issue!", 7 | }); 8 | await context.octokit.issues.createComment(issueComment); 9 | } catch (error) { 10 | console.error("Error handling opened issue:", error); 11 | } 12 | } 13 | 14 | export async function handleIssueComment(context: Context) { 15 | try { 16 | // Add your issue comment handling logic here 17 | const comment = context.payload.comment; 18 | // Process the comment as needed 19 | } catch (error) { 20 | console.error("Error handling issue comment:", error); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/server/src/utils/telegram-notification.ts: -------------------------------------------------------------------------------- 1 | import { telegram_bot } from '../config/telegraf'; 2 | 3 | const sendTelegramMessage = async (chatId: string, message: string) => { 4 | try { 5 | await telegram_bot.telegram.sendMessage(chatId, message); 6 | } catch (error) { 7 | console.error(`Failed to send message to chat ${chatId}:`, error); 8 | } 9 | }; 10 | 11 | const sendTelegramNotification = async (chatId: string, message: string) => { 12 | try { 13 | await telegram_bot.telegram.sendMessage(chatId, message, { 14 | parse_mode: 'MarkdownV2', 15 | }); 16 | } catch (error) { 17 | console.error(`Failed to send notification to chat ${chatId}:`, error); 18 | } 19 | }; 20 | 21 | export { sendTelegramNotification, sendTelegramMessage }; 22 | -------------------------------------------------------------------------------- /apps/github-bot/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2025, obiabo 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /apps/server/drizzle/0002_easy_shen.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "transaction_records" ( 2 | "id" serial PRIMARY KEY NOT NULL, 3 | "amount" numeric NOT NULL, 4 | "sender_address" text NOT NULL, 5 | "sender_id" integer, 6 | "recipient_address" text NOT NULL, 7 | "recipient_id" integer, 8 | "tx_hash" text NOT NULL, 9 | "note" text, 10 | "created_at" timestamp DEFAULT now() 11 | ); 12 | --> statement-breakpoint 13 | ALTER TABLE "transaction_records" ADD CONSTRAINT "transaction_records_sender_id_users_id_fk" FOREIGN KEY ("sender_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint 14 | ALTER TABLE "transaction_records" ADD CONSTRAINT "transaction_records_recipient_id_users_id_fk" FOREIGN KEY ("recipient_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -------------------------------------------------------------------------------- /apps/server/drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "postgresql", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "7", 8 | "when": 1742018780546, 9 | "tag": "0000_kind_gamora", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "7", 15 | "when": 1744089206973, 16 | "tag": "0001_motionless_loki", 17 | "breakpoints": true 18 | }, 19 | { 20 | "idx": 2, 21 | "version": "7", 22 | "when": 1745093521013, 23 | "tag": "0002_easy_shen", 24 | "breakpoints": true 25 | }, 26 | { 27 | "idx": 3, 28 | "version": "7", 29 | "when": 1745093718119, 30 | "tag": "0003_past_kitty_pryde", 31 | "breakpoints": true 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /apps/github-bot/README.md: -------------------------------------------------------------------------------- 1 | # github-bot 2 | 3 | > A GitHub App built with [Probot](https://github.com/probot/probot) that Reward contributors from PR and comment with 🍟 potatoe bot 4 | 5 | ## Setup 6 | 7 | ```sh 8 | # Install dependencies 9 | npm install 10 | 11 | # Run the bot 12 | npm start 13 | ``` 14 | 15 | ## Docker 16 | 17 | ```sh 18 | # 1. Build container 19 | docker build -t github-bot . 20 | 21 | # 2. Start container 22 | docker run -e APP_ID= -e PRIVATE_KEY= github-bot 23 | ``` 24 | 25 | ## Contributing 26 | 27 | If you have suggestions for how github-bot could be improved, or want to report a bug, open an issue! We'd love all and any contributions. 28 | 29 | For more, check out the [Contributing Guide](CONTRIBUTING.md). 30 | 31 | ## License 32 | 33 | [ISC](LICENSE) © 2025 obiabo 34 | -------------------------------------------------------------------------------- /apps/web-app/src/components/profile/TipBadge.tsx: -------------------------------------------------------------------------------- 1 | interface TipBadgeProps { 2 | username: string; 3 | } 4 | 5 | export function TipBadge({ username }: TipBadgeProps) { 6 | const currentUrl = 7 | typeof window !== "undefined" ? window.location.origin : ""; 8 | 9 | return ( 10 | 16 | Potatoe Squeezy - Support GitHub contributors with crypto | Product Hunt 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /apps/extension/src/pages/welcome.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Button from "../components/ui/button"; 3 | 4 | function Welcome() { 5 | return ( 6 |
12 |
13 |

🥔

14 |

15 | Your GitHub just got crispy. When life hands you potatoes, squeeze out 16 | Bitcoin instead! 🎉🥔. 17 |

18 |
19 | 20 |
21 | ); 22 | } 23 | 24 | export default Welcome; 25 | -------------------------------------------------------------------------------- /libs/common/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react", 9 | "plugin:@typescript-eslint/eslint-recommended" 10 | ], 11 | "env": { 12 | "node": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "ecmaFeatures": { 17 | "legacyDecorators": true, 18 | "jsx": true 19 | } 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "16" 24 | } 25 | }, 26 | "rules": { 27 | "space-before-function-paren": 0, 28 | "react/prop-types": 0, 29 | "react/jsx-handler-names": 0, 30 | "react/jsx-fragments": 0, 31 | "react/no-unused-prop-types": 0, 32 | "import/export": 0 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/web-app/src/components/popups/drawer/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Sheet, 3 | SheetContent, 4 | SheetHeader, 5 | SheetTitle, 6 | SheetTrigger, 7 | } from "@/components/ui/sheet"; 8 | 9 | interface IDrawer { 10 | trigger: React.ReactNode; 11 | children: React.ReactNode; 12 | title: string; 13 | side?: "top" | "right" | "bottom" | "left"; 14 | } 15 | 16 | const Drawer = ({ trigger, children, title, side = "right" }: IDrawer) => { 17 | return ( 18 | 19 | {trigger} 20 | 21 | 22 | {title} 23 | 24 |
{children}
25 |
26 |
27 | ); 28 | }; 29 | 30 | export default Drawer; 31 | -------------------------------------------------------------------------------- /apps/web-app/src/components/github/GithubUserCardSkeleton.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton"; 2 | 3 | export function GithubUserCardSkeleton() { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/server/drizzle/0000_kind_gamora.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "users" ( 2 | "id" serial PRIMARY KEY NOT NULL, 3 | "github_id" text NOT NULL, 4 | "username" text NOT NULL, 5 | "email" text, 6 | "name" text, 7 | "avatar_url" text, 8 | "created_at" timestamp DEFAULT now(), 9 | "updated_at" timestamp DEFAULT now(), 10 | CONSTRAINT "users_github_id_unique" UNIQUE("github_id") 11 | ); 12 | --> statement-breakpoint 13 | CREATE TABLE "wallets" ( 14 | "id" serial PRIMARY KEY NOT NULL, 15 | "user_id" integer NOT NULL, 16 | "address" text NOT NULL, 17 | "created_at" timestamp DEFAULT now(), 18 | "updated_at" timestamp DEFAULT now(), 19 | CONSTRAINT "wallets_address_unique" UNIQUE("address") 20 | ); 21 | --> statement-breakpoint 22 | ALTER TABLE "wallets" ADD CONSTRAINT "wallets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action; -------------------------------------------------------------------------------- /apps/web-app/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | "react-refresh/only-export-components": [ 23 | "warn", 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ); 29 | -------------------------------------------------------------------------------- /apps/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "scripts": { 4 | "dev": "bun run --hot src/index.ts", 5 | "start": "bun run --hot src/index.ts", 6 | "db:generate": "drizzle-kit generate", 7 | "push": "bun run scripts/push-db.ts", 8 | "db:studio": "drizzle-kit studio", 9 | "db:push": "npx drizzle-kit push", 10 | "format": "prettier --write ." 11 | }, 12 | "dependencies": { 13 | "@hono-dev/auth-github": "^0.0.1", 14 | "@neondatabase/serverless": "^0.10.4", 15 | "dotenv": "^16.4.7", 16 | "drizzle-orm": "^0.40.0", 17 | "hono": "^4.7.2", 18 | "pg": "^8.14.0", 19 | "prettier": "^3.5.3", 20 | "telegraf": "^4.16.3", 21 | "telegramify-markdown": "^1.3.0" 22 | }, 23 | "devDependencies": { 24 | "@types/bun": "latest", 25 | "@types/pg": "^8.11.11", 26 | "drizzle-kit": "^0.30.5", 27 | "tsx": "^4.19.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/web-app/src/hooks/useProfile.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { UserService } from "@/services"; 3 | import { useUserStore } from "@/store/user.store"; 4 | import Cookies from "js-cookie"; 5 | 6 | export function useProfile() { 7 | const setUser = useUserStore((state) => state.setUser); 8 | const { setWallet } = useUserStore(); 9 | const token = Cookies.get("auth-token"); 10 | 11 | const { data: profile, isLoading } = useQuery({ 12 | queryKey: ["userProfile", token], 13 | queryFn: async () => { 14 | const response = await UserService.fetchUserProfile(); 15 | setUser(response); 16 | setWallet(response?.wallets?.address); 17 | 18 | return response; 19 | }, 20 | enabled: !!token, 21 | staleTime: 1000 * 60 * 5, 22 | retry: 1, 23 | }); 24 | 25 | return { 26 | profile, 27 | isLoading, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /apps/web-app/src/services/wallet.service.ts: -------------------------------------------------------------------------------- 1 | import API_ENDPOINTS from "@/enums/API_ENUM"; 2 | import ApiClient from "@/util/api"; 3 | 4 | interface UpdateWalletPayload { 5 | address: string; 6 | } 7 | 8 | class WalletService { 9 | static async getWalletAddress() { 10 | const response = await ApiClient.get<{ address: string }>( 11 | API_ENDPOINTS.USER_WALLET, 12 | ); 13 | return response; 14 | } 15 | 16 | static async addWallet(payload: any) { 17 | const response = await ApiClient.post<{ address: string }>( 18 | API_ENDPOINTS.USER_WALLET, 19 | payload, 20 | ); 21 | return response; 22 | } 23 | 24 | static async updateWallet(payload: any) { 25 | const response = await ApiClient.put<{ address: string }>( 26 | API_ENDPOINTS.USER_WALLET, 27 | payload, 28 | ); 29 | return response; 30 | } 31 | } 32 | 33 | export default WalletService; 34 | -------------------------------------------------------------------------------- /apps/web-app/src/hooks/connect-wallet.ts: -------------------------------------------------------------------------------- 1 | import { useWallet } from "@solana/wallet-adapter-react"; 2 | import { useWalletModal } from "@solana/wallet-adapter-react-ui"; 3 | 4 | export const useWalletConnection = () => { 5 | const { setVisible } = useWalletModal(); 6 | const { connected, connecting } = useWallet(); 7 | 8 | const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); 9 | 10 | const connectWallet = async () => { 11 | if (isMobile) { 12 | const isSolanaAvailable = 13 | !!(window as any).solana || !!(window as any).solflare; 14 | 15 | if (!isSolanaAvailable) { 16 | const universalLink = `https://solana.com/portal`; 17 | window.location.href = universalLink; 18 | return; 19 | } 20 | } 21 | 22 | setVisible(true); 23 | }; 24 | 25 | return { 26 | connectWallet, 27 | connected, 28 | connecting, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /apps/web-app/src/hooks/connect-wallet.tsx: -------------------------------------------------------------------------------- 1 | import { useWallet } from "@solana/wallet-adapter-react"; 2 | import { useWalletModal } from "@solana/wallet-adapter-react-ui"; 3 | 4 | export const useWalletConnection = () => { 5 | const { setVisible } = useWalletModal(); 6 | const { connected, connecting } = useWallet(); 7 | 8 | const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); 9 | 10 | const connectWallet = async () => { 11 | if (isMobile) { 12 | const isSolanaAvailable = 13 | !!(window as any).solana || !!(window as any).solflare; 14 | 15 | if (!isSolanaAvailable) { 16 | const universalLink = `https://solana.com/portal`; 17 | window.location.href = universalLink; 18 | return; 19 | } 20 | } 21 | 22 | setVisible(true); 23 | }; 24 | 25 | return { 26 | connectWallet, 27 | connected, 28 | connecting, 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /apps/web-app/src/configs/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import Cookies from "js-cookie"; 3 | 4 | const DEFAULT_AXIOS = axios.create({ 5 | baseURL: import.meta.env.VITE_API_BASE_URL, 6 | timeout: 10000, 7 | timeoutErrorMessage: "There was a timeout, try again", 8 | }); 9 | 10 | DEFAULT_AXIOS.interceptors.request.use( 11 | (config) => { 12 | const token = Cookies.get("auth-token"); 13 | if (token) { 14 | config.headers.Authorization = `Bearer ${token}`; 15 | } 16 | return config; 17 | }, 18 | (error) => { 19 | return Promise.reject(error); 20 | }, 21 | ); 22 | 23 | DEFAULT_AXIOS.interceptors.response.use( 24 | (response) => response, 25 | (error) => { 26 | if (error.response?.status === 401) { 27 | Cookies.remove("auth-token"); 28 | window.location.href = "/"; 29 | } 30 | return Promise.reject(error); 31 | }, 32 | ); 33 | 34 | export default DEFAULT_AXIOS; 35 | -------------------------------------------------------------------------------- /apps/web-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "ESNext", 6 | "useDefineForClassFields": true, 7 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 8 | "module": "ESNext", 9 | "skipLibCheck": true, 10 | "moduleResolution": "Node", 11 | "esModuleInterop": true, 12 | 13 | "allowImportingTsExtensions": true, 14 | "isolatedModules": true, 15 | "moduleDetection": "force", 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true, 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": ["./src/*"] 27 | } 28 | }, 29 | "include": ["src"], 30 | "exclude": ["node_modules"] 31 | } 32 | -------------------------------------------------------------------------------- /apps/web-app/src/util/content-utils.ts: -------------------------------------------------------------------------------- 1 | const truncateContext = (text: string, maxLength: number): string => { 2 | if (text.length <= maxLength) { 3 | return text; 4 | } 5 | return text.slice(0, maxLength) + "..."; 6 | }; 7 | 8 | const truncateText = (text: string, maxLength: number): string => { 9 | if (text.length <= maxLength) { 10 | return text; 11 | } 12 | const truncated = text.slice(0, maxLength); 13 | const lastSpaceIndex = truncated.lastIndexOf(" "); 14 | return truncated.slice(0, lastSpaceIndex) + "..."; 15 | }; 16 | const truncateTextWithEllipsis = (text: string, maxLength: number): string => { 17 | if (text.length <= maxLength) { 18 | return text; 19 | } 20 | const truncated = text.slice(0, maxLength); 21 | const lastSpaceIndex = truncated.lastIndexOf(" "); 22 | return truncated.slice(0, lastSpaceIndex) + "..."; 23 | }; 24 | 25 | export { truncateContext, truncateText, truncateTextWithEllipsis }; 26 | -------------------------------------------------------------------------------- /apps/extension/src/content.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { Toaster, toast } from "sonner"; 4 | import TipGithubUser from "./components/Views/tipGithubUser"; 5 | 6 | const ID = ".user-profile-bio"; 7 | 8 | console.log("Potatoe squeezed communicating successfully with Github"); 9 | if (document.location.hostname === "github.com") { 10 | const formElement = document.querySelector(ID); 11 | if (formElement) { 12 | const App = () => { 13 | return ; 14 | }; 15 | 16 | const container = document.createElement("div"); 17 | formElement.appendChild(container); 18 | 19 | const root = ReactDOM.createRoot(container); 20 | root.render( 21 | <> 22 | 23 | 24 | , 25 | ); 26 | } else { 27 | console.error(`Element with ID "${ID}" not found.`); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/server/src/middleware/auth.ts: -------------------------------------------------------------------------------- 1 | import { Context, Next } from 'hono'; 2 | import { verify } from 'hono/jwt'; 3 | import type { Env } from '../types/env'; 4 | 5 | export const auth = async (c: Context<{ Bindings: Env }>, next: Next) => { 6 | const authHeader = c.req.header('Authorization'); 7 | 8 | if (!authHeader || !authHeader.startsWith('Bearer ')) { 9 | return c.json({ error: 'Unauthorized' }, 401); 10 | } 11 | 12 | const token = authHeader.split(' ')[1]; 13 | console.log('Received Token:', token); 14 | 15 | try { 16 | const payload = await verify(token, c.env.JWT_SECRET); 17 | console.log('Decoded Payload:', payload); 18 | c.set('user', payload); 19 | c.set('userId', payload.userId || payload.sub); // Ensure userId is set correctly 20 | await next(); 21 | } catch (error) { 22 | console.error('Token Verification Error:', error); 23 | return c.json({ error: 'Invalid token', details: error.message }, 401); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /apps/github-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-bot", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Reward contributors from PR and comment with 🍟 potatoe bot", 6 | "author": "obiabo", 7 | "license": "ISC", 8 | "homepage": "https://github.com//", 9 | "keywords": [ 10 | "probot", 11 | "github", 12 | "probot-app" 13 | ], 14 | "scripts": { 15 | "build": "tsc", 16 | "start": "probot run ./src/index.ts", 17 | "start:dev": "ts-node-esm --loader ts-node/esm ./src/index.ts", 18 | "dev": "nodemon", 19 | "test": "vitest" 20 | }, 21 | "dependencies": { 22 | "nodemon": "^3.1.9", 23 | "prettier": "^3.5.3", 24 | "probot": "^13.0.1" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^20.0.0", 28 | "nock": "^14.0.0-beta.5", 29 | "smee-client": "^2.0.0", 30 | "typescript": "^5.3.3", 31 | "vitest": "^1.3.1" 32 | }, 33 | "engines": { 34 | "node": ">= 18" 35 | }, 36 | "type": "module" 37 | } 38 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | import { Toaster as Sonner, ToasterProps } from "sonner"; 3 | 4 | const Toaster = ({ ...props }: ToasterProps) => { 5 | const { theme = "system" } = useTheme(); 6 | 7 | return ( 8 | 24 | ); 25 | }; 26 | 27 | export { Toaster }; 28 | -------------------------------------------------------------------------------- /apps/web-app/src/sections/auth.tsx: -------------------------------------------------------------------------------- 1 | import Nav from "@/header/nav.astro"; 2 | import AuthButton from "../button/auth"; 3 | 4 | const Auth = () => { 5 | return ( 6 |
7 |
26 | ); 27 | }; 28 | 29 | export default Auth; 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report-🐞.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Bug report \U0001F41E" 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ); 19 | } 20 | 21 | export { Input }; 22 | -------------------------------------------------------------------------------- /apps/web-app/src/header/dashboardHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Settings } from "lucide-react"; 2 | import Drawer from "@/components/popups/drawer"; 3 | import SettingsDrawerView from "@/components/views/settings-drawer-view.tsx"; 4 | 5 | function DashboardHeader() { 6 | return ( 7 |
8 |
13 |
14 | 15 | 16 | 17 |
18 | 19 |
20 | 24 | 25 |
26 | } 27 | > 28 | 29 | 30 |
31 |
32 |
33 | ); 34 | } 35 | 36 | export default DashboardHeader; 37 | -------------------------------------------------------------------------------- /apps/web-app/src/services/transaction.service.ts: -------------------------------------------------------------------------------- 1 | import API_ENDPOINTS from "@/enums/API_ENUM"; 2 | import ApiClient from "@/util/api"; 3 | 4 | interface TransactionRecord { 5 | id: number; 6 | amount: number; 7 | senderAddress: string; 8 | senderId: number | null; 9 | recipientAddress: string; 10 | recipientId: number | null; 11 | txHash: string; 12 | note: string | null; 13 | createdAt: string; 14 | } 15 | 16 | class TransactionService { 17 | static async getTransactionRecords(): Promise { 18 | const response = await ApiClient.get( 19 | API_ENDPOINTS.TRANSACTION_RECORDS, 20 | ); 21 | return response; 22 | } 23 | 24 | static async createTransactionRecord( 25 | data: Omit, 26 | ) { 27 | const response = await ApiClient.post( 28 | API_ENDPOINTS.TRANSACTION_RECORDS, 29 | data as any, 30 | ); 31 | return response; 32 | } 33 | } 34 | 35 | export default TransactionService; 36 | export type { TransactionRecord }; 37 | -------------------------------------------------------------------------------- /apps/web-app/src/hooks/extract-user-wallet.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { UserService } from "@/services"; 3 | 4 | const useExtractUserWallet = (userName: string) => { 5 | const [wallet, setWallet] = useState(null); 6 | const [isLoading, setIsLoading] = useState(true); 7 | const [error, setError] = useState(null); 8 | 9 | useEffect(() => { 10 | const fetchUserWallet = async () => { 11 | try { 12 | const users = await UserService.fetchAllPotatoeUsers(); 13 | const user = users?.find((data: any) => { 14 | const userData = data?.users; 15 | 16 | return userData?.username === userName; 17 | }); 18 | setWallet(user?.wallets || null); 19 | } catch (err) { 20 | setError("Failed to fetch user wallet"); 21 | } finally { 22 | setIsLoading(false); 23 | } 24 | }; 25 | 26 | fetchUserWallet(); 27 | }, [userName]); 28 | 29 | return { wallet, isLoading, error }; 30 | }; 31 | 32 | export default useExtractUserWallet; 33 | -------------------------------------------------------------------------------- /apps/extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "potato-squeezy", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build" 9 | }, 10 | "dependencies": { 11 | "@headlessui/react": "^2.2.0", 12 | "@solana/pay": "^0.2.5", 13 | "@supabase/supabase-js": "^2.47.10", 14 | "axios": "^1.7.9", 15 | "canvas-confetti": "^1.9.3", 16 | "lucid": "^2.21.0", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-icons": "^5.4.0", 20 | "sonner": "^1.7.1", 21 | "supabase": "^2.1.1" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^18.0.26", 25 | "@types/react-dom": "^18.0.9", 26 | "@types/webextension-polyfill": "^0.10.0", 27 | "@vitejs/plugin-react": "^4.2.1", 28 | "autoprefixer": "^10.4.20", 29 | "daisyui": "^4.12.23", 30 | "postcss": "^8.4.49", 31 | "tailwindcss": "^3.4.17", 32 | "typescript": "~5.6.3", 33 | "vite": "^5.0.0", 34 | "vite-plugin-web-extension": "^4.0.0", 35 | "webextension-polyfill": "^0.10.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 O514B0 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 | -------------------------------------------------------------------------------- /apps/web-app/src/button/connectWalletButton.tsx: -------------------------------------------------------------------------------- 1 | import { WalletMultiButton } from "@solana/wallet-adapter-react-ui"; 2 | import { Wallet2 } from "lucide-react"; 3 | import { ReactNode } from "react"; 4 | import { useWallet } from "@solana/wallet-adapter-react"; 5 | 6 | const ConnectWalletButton = ({ children }: { children: ReactNode }) => { 7 | const { connected } = useWallet(); 8 | return ( 9 | 25 | {!connected ? ( 26 | <> 27 | Connect Wallet 28 | 29 | ) : ( 30 | "Connected" 31 | )} 32 | 33 | ); 34 | }; 35 | 36 | export default ConnectWalletButton; 37 | -------------------------------------------------------------------------------- /apps/web-app/src/store/use-fetch-github-data.store.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { persist, devtools } from "zustand/middleware"; 3 | 4 | interface GithubUser { 5 | id: number; 6 | login: string; 7 | avatar_url: string; 8 | html_url: string; 9 | name: string | null; 10 | email: string | null; 11 | bio: string | null; 12 | } 13 | 14 | interface GithubDataState { 15 | githubUser: GithubUser | null; 16 | setGithubUser: (user: GithubUser) => void; 17 | clearGithubUser: () => void; 18 | } 19 | 20 | export const useFetchGithubDataStore = create()( 21 | devtools( 22 | persist( 23 | (set) => ({ 24 | githubUser: null, 25 | setGithubUser: (user) => 26 | set({ githubUser: user }, false, "setGithubUser"), 27 | clearGithubUser: () => 28 | set({ githubUser: null }, false, "clearGithubUser"), 29 | }), 30 | { 31 | name: "github-user-storage", 32 | partialize: (state) => ({ 33 | githubUser: state.githubUser, 34 | }), 35 | }, 36 | ), 37 | { 38 | name: "FetchGithubDataStore", 39 | enabled: process.env.NODE_ENV === "development", 40 | }, 41 | ), 42 | ); 43 | -------------------------------------------------------------------------------- /apps/extension/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "{{chrome}}.manifest_version": 3, 3 | "{{firefox}}.manifest_version": 2, 4 | "description": "Turn potatoes into Bitcoin! Zap anyone on GitHub as sponsorship.", 5 | "icons": { 6 | "16": "icon/16.png", 7 | "32": "icon/32.png", 8 | "48": "icon/48.png", 9 | "96": "icon/96.png", 10 | "128": "icon/128.png" 11 | }, 12 | "permissions": [ 13 | "sidePanel", 14 | "identity", 15 | "cookies", 16 | "activeTab", 17 | "storage", 18 | "tabs" 19 | ], 20 | "{{chrome}}.action": { 21 | "default_popup": "src/popup.html" 22 | }, 23 | "{{firefox}}.browser_action": { 24 | "default_popup": "src/popup.html" 25 | }, 26 | "background": { 27 | "{{chrome}}.service_worker": "src/background.ts", 28 | "{{firefox}}.scripts": ["src/background.ts"] 29 | }, 30 | "content_scripts": [ 31 | { 32 | "matches": ["https://github.com/*"], 33 | "js": ["src/content.tsx"] 34 | } 35 | ], 36 | "{{chrome}}.side_panel": { 37 | "default_path": "src/popup.html" 38 | }, 39 | "host_permissions": [ 40 | "https://www.potatoesqueezy.xyz/*", 41 | "http://localhost:4321/*" 42 | ], 43 | "oauth2": { 44 | "client_id": "Ov23liATCckJVguF3n2G", 45 | "scopes": ["read:user"] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | function Avatar({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | ); 20 | } 21 | 22 | function AvatarImage({ 23 | className, 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 32 | ); 33 | } 34 | 35 | function AvatarFallback({ 36 | className, 37 | ...props 38 | }: React.ComponentProps) { 39 | return ( 40 | 48 | ); 49 | } 50 | 51 | export { Avatar, AvatarImage, AvatarFallback }; 52 | -------------------------------------------------------------------------------- /apps/web-app/src/button/auth.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import supabase from "@/lib/supabase"; 3 | import useAuth from "@/hooks/useAuth"; 4 | function AuthButton() { 5 | const webSupabase = supabase; 6 | const { session, loading, error } = useAuth(); 7 | 8 | useEffect(() => { 9 | if (session && typeof window !== "undefined") { 10 | window.location.href = "/app"; 11 | } 12 | }, [session]); 13 | 14 | const signInWithGithub = async () => { 15 | try { 16 | const { error } = await webSupabase.auth.signInWithOAuth({ 17 | provider: "github", 18 | }); 19 | 20 | if (error) { 21 | console.error("Error signing in:", error.message); 22 | alert("Failed to sign in. Please try again."); 23 | } 24 | } catch (err) { 25 | console.error("Unexpected error during sign-in:", err); 26 | alert("An unexpected error occurred. Please try again."); 27 | } 28 | }; 29 | 30 | if (loading) return

Loading...

; 31 | if (error) return

Error: {error.message}

; 32 | 33 | return ( 34 |
35 | 41 |
42 | ); 43 | } 44 | 45 | export default AuthButton; 46 | -------------------------------------------------------------------------------- /apps/web-app/src/button/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type TVariant = 4 | | "primary" 5 | | "secondary" 6 | | "danger" 7 | | "default" 8 | | "success" 9 | | "warning"; 10 | 11 | interface IProps { 12 | children: React.ReactNode; 13 | className?: string; 14 | variant?: TVariant; 15 | onClick?: () => void; 16 | type?: "button" | "submit" | "reset"; 17 | disabled?: boolean; 18 | } 19 | 20 | const DefaultButton: React.FC = ({ 21 | children, 22 | className = "", 23 | variant = "primary", 24 | onClick, 25 | type = "button", 26 | disabled = false, 27 | }): React.ReactNode => { 28 | const variantStyles: Record = { 29 | primary: "bg-orange-500 text-white hover:bg-orange-600", 30 | secondary: "bg-gray-500 text-white hover:bg-gray-600", 31 | danger: "bg-red-500 text-white hover:bg-red-600", 32 | default: 33 | "bg-[#212830] border-[#80808054] border text-white hover:bg-red-600", 34 | }; 35 | 36 | return ( 37 | 47 | ); 48 | }; 49 | 50 | export default DefaultButton; 51 | -------------------------------------------------------------------------------- /apps/web-app/src/components/fallbacks/wallet-no-connect.tsx: -------------------------------------------------------------------------------- 1 | import { WalletSvg } from "@/assets/svg"; 2 | import ConnectWalletButton from "@/button/connectWalletButton"; 3 | import React from "react"; 4 | import { motion } from "framer-motion"; 5 | 6 | const WalletNotConnected = () => { 7 | return ( 8 | 14 | 19 | 20 | 21 | 27 | Wallet Not Connected 28 | 29 | 35 | Connect Wallet 36 | 37 | 38 | ); 39 | }; 40 | 41 | export default WalletNotConnected; 42 | -------------------------------------------------------------------------------- /apps/web-app/store/user.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { persist } from "zustand/middleware"; 3 | 4 | interface UserData { 5 | id: number; 6 | githubId: string; 7 | username: string; 8 | email: string | null; 9 | name: string; 10 | avatarUrl: string; 11 | createdAt: string; 12 | updatedAt: string; 13 | } 14 | 15 | interface WalletData { 16 | id: number; 17 | userId: number; 18 | address: string; 19 | createdAt: string; 20 | updatedAt: string; 21 | } 22 | 23 | interface UserState { 24 | users: UserData | null; 25 | wallets: WalletData | null; 26 | isAuthenticated: boolean; 27 | setUserData: (data: { users: UserData; wallets: WalletData }) => void; 28 | updateWallet: (wallet: WalletData) => void; 29 | clearUserData: () => void; 30 | } 31 | 32 | const initialState = { 33 | users: null, 34 | wallets: null, 35 | isAuthenticated: false, 36 | }; 37 | 38 | export const useUserStore = create()( 39 | persist( 40 | (set) => ({ 41 | ...initialState, 42 | 43 | setUserData: (data) => 44 | set({ 45 | users: data.users, 46 | wallets: data.wallets, 47 | isAuthenticated: true, 48 | }), 49 | 50 | updateWallet: (wallet) => set((state) => ({ ...state, wallets: wallet })), 51 | 52 | clearUserData: () => set(initialState), 53 | }), 54 | { 55 | name: "user-storage", 56 | }, 57 | ), 58 | ); 59 | -------------------------------------------------------------------------------- /apps/web-app/src/header/nav.tsx: -------------------------------------------------------------------------------- 1 | const Nav = () => { 2 | return ( 3 | 34 | ); 35 | }; 36 | 37 | export default Nav; 38 | -------------------------------------------------------------------------------- /apps/web-app/src/providers/walletConnectProvider.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { clusterApiUrl } from "@solana/web3.js"; 3 | import { 4 | ConnectionProvider, 5 | WalletProvider, 6 | } from "@solana/wallet-adapter-react"; 7 | import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"; 8 | import { 9 | CoinbaseWalletAdapter, 10 | PhantomWalletAdapter, 11 | SolflareWalletAdapter, 12 | TorusWalletAdapter, 13 | } from "@solana/wallet-adapter-wallets"; 14 | import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"; 15 | import "@solana/wallet-adapter-react-ui/styles.css"; 16 | import { ReactNode } from "@tanstack/react-router"; 17 | 18 | const WalletAdapterProvider = ({ children }: { children: ReactNode }) => { 19 | const network = WalletAdapterNetwork.Mainnet; 20 | 21 | const endpoint = useMemo(() => clusterApiUrl(network), [network]); 22 | 23 | const wallets = useMemo( 24 | () => [ 25 | new PhantomWalletAdapter(), 26 | new CoinbaseWalletAdapter(), 27 | new SolflareWalletAdapter({ network }), 28 | new TorusWalletAdapter(), 29 | ], 30 | [network], 31 | ); 32 | 33 | return ( 34 | 35 | 36 | {children} 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default WalletAdapterProvider; 43 | -------------------------------------------------------------------------------- /apps/web-app/src/util/api.ts: -------------------------------------------------------------------------------- 1 | import DEFAULT_AXIOS from "@/configs/axios"; 2 | import { BASE_API_URL } from "@/constant"; 3 | 4 | class ApiClient { 5 | private static readonly baseUrl = BASE_API_URL; 6 | private static readonly axiosInstance = DEFAULT_AXIOS; 7 | 8 | static async get( 9 | path: string, 10 | params?: Record, 11 | ): Promise { 12 | const response = await this.axiosInstance.get(`${this.baseUrl}${path}`, { 13 | params, 14 | }); 15 | return response.data; 16 | } 17 | 18 | static async post( 19 | path: string, 20 | data?: Record, 21 | ): Promise { 22 | const response = await this.axiosInstance.post( 23 | `${this.baseUrl}${path}`, 24 | data, 25 | ); 26 | return response.data; 27 | } 28 | 29 | static async put(path: string, data?: Record): Promise { 30 | const response = await this.axiosInstance.put( 31 | `${this.baseUrl}${path}`, 32 | data, 33 | ); 34 | return response.data; 35 | } 36 | 37 | static async delete(path: string): Promise { 38 | const response = await this.axiosInstance.delete(`${this.baseUrl}${path}`); 39 | return response.data; 40 | } 41 | 42 | static async patch(path: string): Promise { 43 | const response = await this.axiosInstance.patch(`${this.baseUrl}${path}`); 44 | return response.data; 45 | } 46 | } 47 | 48 | export default ApiClient; 49 | -------------------------------------------------------------------------------- /apps/web-app/src/misc/github_users_card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import DefaultButton from "../button"; 3 | import Button from "../button"; 4 | 5 | interface IGithubUserCardProps { 6 | user_avatar_url: string; 7 | user_name: string; 8 | github_username: string; 9 | html_url?: string; 10 | } 11 | 12 | function GithubUsersCard({ 13 | user_name, 14 | user_avatar_url, 15 | github_username, 16 | html_url, 17 | }: IGithubUserCardProps) { 18 | const goToProfile = () => { 19 | if (typeof window !== "undefined") { 20 | window.location.href = `/app/profile?user=${github_username}`; 21 | } 22 | }; 23 | 24 | return ( 25 |
32 | {user_name} 37 |
38 |

{user_name}

39 |

@{github_username}

40 |
41 | 44 |
45 | ); 46 | } 47 | 48 | export default GithubUsersCard; 49 | -------------------------------------------------------------------------------- /apps/web-app/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/web-app/src/sections/hero.tsx: -------------------------------------------------------------------------------- 1 | import Nav from "@/header/nav.astro"; 2 | 3 | const Hero = () => { 4 | return ( 5 |
6 |
38 | ); 39 | }; 40 | 41 | export default Hero; 42 | -------------------------------------------------------------------------------- /apps/web-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import tailwindcss from "@tailwindcss/vite"; 3 | import react from "@vitejs/plugin-react"; 4 | import { defineConfig } from "vite"; 5 | import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; 6 | import { nodePolyfills } from "vite-plugin-node-polyfills"; 7 | import { VitePWA } from "vite-plugin-pwa"; 8 | 9 | // https://vite.dev/config/ 10 | export default defineConfig({ 11 | root: ".", 12 | plugins: [ 13 | TanStackRouterVite({ target: "react", autoCodeSplitting: true }), 14 | react({ 15 | babel: { 16 | plugins: [ 17 | [ 18 | "@locator/babel-jsx/dist", 19 | { 20 | env: "development", 21 | }, 22 | ], 23 | ], 24 | }, 25 | }), 26 | VitePWA({ 27 | registerType: "autoUpdate", 28 | injectRegister: "auto", 29 | 30 | devOptions: { 31 | enabled: true, 32 | }, 33 | manifest: { 34 | name: "Sui Bulma", 35 | short_name: "Sui Bulma", 36 | description: 37 | "SUI Bulma - A Bitcoin-centric DeFi platform on Sui, enabling lending, stablecoins, perps, and synthetic assets to enhance BTC utility", 38 | theme_color: "#0B0B14", 39 | }, 40 | }), 41 | nodePolyfills(), 42 | tailwindcss(), 43 | ], 44 | resolve: { 45 | alias: { 46 | "@": path.resolve(__dirname, "./src"), 47 | }, 48 | }, 49 | define: { 50 | global: "globalThis", 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /apps/web-app/src/hooks/useSolanaTip.ts: -------------------------------------------------------------------------------- 1 | import { useWallet } from "@solana/wallet-adapter-react"; 2 | import { 3 | LAMPORTS_PER_SOL, 4 | PublicKey, 5 | Transaction, 6 | SystemProgram, 7 | } from "@solana/web3.js"; 8 | import { useState } from "react"; 9 | import { toast } from "sonner"; 10 | 11 | export function useSolanaTip() { 12 | const { publicKey, sendTransaction } = useWallet(); 13 | const [isSending, setIsSending] = useState(false); 14 | 15 | const sendTip = async (recipientWalletAddress: string) => { 16 | if (!publicKey || !recipientWalletAddress) { 17 | toast.error("Please connect your wallet first"); 18 | return; 19 | } 20 | 21 | try { 22 | setIsSending(true); 23 | const recipientAddress = new PublicKey(recipientWalletAddress); 24 | const transaction = new Transaction().add( 25 | SystemProgram.transfer({ 26 | fromPubkey: publicKey, 27 | toPubkey: recipientAddress, 28 | lamports: 0.1 * LAMPORTS_PER_SOL, 29 | }), 30 | ); 31 | 32 | const signature = await sendTransaction(transaction, connection); 33 | await connection.confirmTransaction(signature, "confirmed"); 34 | 35 | toast.success("Tip sent successfully! 🎉"); 36 | } catch (error) { 37 | console.error("Error sending tip:", error); 38 | toast.error("Failed to send tip"); 39 | } finally { 40 | setIsSending(false); 41 | } 42 | }; 43 | 44 | return { 45 | sendTip, 46 | isSending, 47 | isWalletConnected: !!publicKey, 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /apps/web-app/src/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import Cookies from "js-cookie"; 3 | import { useUserStore } from "@/store/user.store"; 4 | import { useNavigate } from "@tanstack/react-router"; 5 | 6 | function useAuth() { 7 | const [isAuthenticated, setIsAuthenticated] = useState(() => { 8 | const token = Cookies.get("auth-token"); 9 | return !!token; 10 | }); 11 | 12 | const { user, clearUser } = useUserStore(); 13 | const navigate = useNavigate(); 14 | 15 | useEffect(() => { 16 | const token = Cookies.get("auth-token"); 17 | if (!token && isAuthenticated) { 18 | handleLogout(); 19 | } 20 | }, [isAuthenticated]); 21 | 22 | const handleLogin = (token: string) => { 23 | Cookies.set("auth-token", token, { 24 | secure: true, 25 | sameSite: "lax", 26 | expires: 7, 27 | }); 28 | setIsAuthenticated(true); 29 | }; 30 | 31 | const handleLogout = () => { 32 | Cookies.remove("auth-token"); 33 | clearUser(); 34 | setIsAuthenticated(false); 35 | navigate({ to: "/" }); 36 | }; 37 | 38 | const checkAuthStatus = () => { 39 | const token = Cookies.get("auth-token"); 40 | const isValid = !!token; 41 | setIsAuthenticated(isValid); 42 | 43 | if (!isValid) { 44 | handleLogout(); 45 | return false; 46 | } 47 | return true; 48 | }; 49 | 50 | return { 51 | isAuthenticated, 52 | user, 53 | login: handleLogin, 54 | logout: handleLogout, 55 | checkAuthStatus, 56 | }; 57 | } 58 | 59 | export default useAuth; 60 | -------------------------------------------------------------------------------- /apps/web-app/src/components/profile/profilePanel.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; 3 | import Typography from "../typography"; 4 | interface ProfilePanelProps { 5 | name: string; 6 | username: string; 7 | withAction?: boolean; 8 | onUpdateAddress?: () => void; 9 | avatar: string; 10 | userBio?: string; 11 | walletAddress?: string; 12 | } 13 | 14 | function ProfilePanel({ 15 | name, 16 | username, 17 | withAction = false, 18 | avatar, 19 | userBio = "", 20 | }: ProfilePanelProps) { 21 | return ( 22 |
23 | 28 |
29 |
30 | 31 | 32 | 33 | {username?.slice(0, 2).toUpperCase()} 34 | 35 | 36 | {name} 37 | 38 | @{username} 39 | 40 | 41 | {userBio} 42 |
43 |
44 |
45 |
46 | ); 47 | } 48 | 49 | export default ProfilePanel; 50 | -------------------------------------------------------------------------------- /apps/server/src/routes/user.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { db } from '../db'; 3 | import { UserWithWallets } from '../types'; 4 | import { auth } from '../middleware/auth'; 5 | import type { Env } from '../types/env'; 6 | import { eq } from 'drizzle-orm'; 7 | import { users, wallets } from '../db/schema'; 8 | const userRoute = new Hono<{ Bindings: Env }>(); 9 | 10 | userRoute.get('/profile', auth, async (c) => { 11 | try { 12 | const userId = c.get('userId'); 13 | 14 | const user = await db 15 | .select() 16 | .from(users) 17 | .where(eq(users.id, parseInt(userId))) 18 | .leftJoin(wallets, eq(users.id, wallets.userId)) 19 | .execute(); 20 | 21 | if (!user || user.length === 0) { 22 | return c.json({ error: 'User not found' }, 404); 23 | } 24 | 25 | return c.json(user[0]); 26 | } catch (error) { 27 | console.error('Error fetching user profile:', error); 28 | return c.json({ error: 'Internal server error' }, 500); 29 | } 30 | }); 31 | 32 | userRoute.get('/all', async (c) => { 33 | try { 34 | const usersList = await db 35 | .select() 36 | .from(users) 37 | .leftJoin(wallets, eq(users.id, wallets.userId)) 38 | .execute(); 39 | 40 | if (!usersList || usersList.length === 0) { 41 | return c.json({ error: 'No users found' }, 404); 42 | } 43 | 44 | return c.json(usersList); 45 | } catch (error) { 46 | console.error('Error fetching all users:', error); 47 | return c.json({ error: 'Internal server error' }, 500); 48 | } 49 | }); 50 | 51 | export { userRoute }; 52 | -------------------------------------------------------------------------------- /apps/web-app/src/components/popups/modals/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AlertDialog, 3 | AlertDialogAction, 4 | AlertDialogCancel, 5 | AlertDialogContent, 6 | AlertDialogFooter, 7 | AlertDialogHeader, 8 | AlertDialogTitle, 9 | AlertDialogTrigger, 10 | } from "@/components/ui/alert-dialog"; 11 | import { X } from "lucide-react"; 12 | 13 | interface IModalLayout { 14 | trigger?: React.ReactNode; 15 | children: React.ReactNode; 16 | title: string; 17 | onClose?: () => void; 18 | open?: boolean; 19 | closeOnOverlayClick?: boolean; 20 | } 21 | 22 | const ModalLayout = ({ 23 | trigger, 24 | children, 25 | title, 26 | onClose, 27 | open, 28 | closeOnOverlayClick = true, 29 | }: IModalLayout) => { 30 | return ( 31 | 32 | {trigger && {trigger}} 33 | { 35 | if (!closeOnOverlayClick) { 36 | e.preventDefault(); 37 | } 38 | }} 39 | > 40 | 41 |
42 | {title} 43 | {(closeOnOverlayClick || !open) && ( 44 | 45 | 46 | 47 | )} 48 |
49 |
{children}
50 |
51 |
52 |
53 | ); 54 | }; 55 | 56 | export default ModalLayout; 57 | -------------------------------------------------------------------------------- /apps/web-app/src/components/misc/product-hunt-badge.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ProductHuntBadge: React.FC = () => { 4 | return ( 5 | 33 | ); 34 | }; 35 | 36 | export default ProductHuntBadge; 37 | -------------------------------------------------------------------------------- /apps/web-app/src/components/search/GithubUserSearch.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { SearchIcon, Loader2 } from "lucide-react"; 3 | import { Button } from "../ui/button"; 4 | import { Input } from "../ui/input"; 5 | 6 | interface GithubUserSearchProps { 7 | searchQuery: string; 8 | onSearchChange: (value: string) => void; 9 | onSearch: () => void; 10 | loading: boolean; 11 | } 12 | 13 | export function GithubUserSearch({ 14 | searchQuery, 15 | onSearchChange, 16 | onSearch, 17 | loading, 18 | }: GithubUserSearchProps) { 19 | const handleKeyPress = (e: React.KeyboardEvent) => { 20 | if (e.key === "Enter") { 21 | onSearch(); 22 | } 23 | }; 24 | 25 | return ( 26 |
27 |
28 |
29 | onSearchChange(e.target.value)} 33 | onKeyPress={handleKeyPress} 34 | placeholder="Search GitHub username..." 35 | className="w-full !p-5 bg-white/5 rounded-xl pl-11 border border-white/10 36 | focus:outline-none focus:ring-2 transition-all" 37 | /> 38 |
39 | 49 |
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /libs/common/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common-example", 3 | "homepage": ".", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start", 8 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build", 9 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test", 10 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject" 11 | }, 12 | "dependencies": { 13 | "@testing-library/jest-dom": "link:../node_modules/@testing-library/jest-dom", 14 | "@testing-library/react": "link:../node_modules/@testing-library/react", 15 | "@testing-library/user-event": "link:../node_modules/@testing-library/user-event", 16 | "@types/jest": "link:../node_modules/@types/jest", 17 | "@types/node": "link:../node_modules/@types/node", 18 | "@types/react": "link:../node_modules/@types/react", 19 | "@types/react-dom": "link:../node_modules/@types/react-dom", 20 | "react": "link:../node_modules/react", 21 | "react-dom": "link:../node_modules/react-dom", 22 | "react-scripts": "link:../node_modules/react-scripts", 23 | "typescript": "link:../node_modules/typescript", 24 | "common": "link:.." 25 | }, 26 | "devDependencies": { 27 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3" 28 | }, 29 | "eslintConfig": { 30 | "extends": "react-app" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/github-bot/test/fixtures/mock-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAli7V49NdZe+XYC1pLaHM0te8kiDmZBJ1u2HJHN8GdbROB6NO 3 | VpC3xK7NxQn6xpvZ9ux20NvcDvGle+DOptZztBH+np6h2jZQ1/kD1yG1eQvVH4th 4 | /9oqHuIjmIfO8lIe4Hyd5Fw5xHkGqVETTGR+0c7kdZIlHmkOregUGtMYZRUi4YG+ 5 | q0w+uFemiHpGKXbeCIAvkq7aIkisEzvPWfSyYdA6WJHpxFk7tD7D8VkzABLVRHCq 6 | AuyqPG39BhGZcGLXx5rGK56kDBJkyTR1t3DkHpwX+JKNG5UYNwOG4LcQj1fteeta 7 | TdkYUMjIyWbanlMYyC+dq7B5fe7el99jXQ1gXwIDAQABAoIBADKfiPOpzKLOtzzx 8 | MbHzB0LO+75aHq7+1faayJrVxqyoYWELuB1P3NIMhknzyjdmU3t7S7WtVqkm5Twz 9 | lBUC1q+NHUHEgRQ4GNokExpSP4SU63sdlaQTmv0cBxmkNarS6ZuMBgDy4XoLvaYX 10 | MSUf/uukDLhg0ehFS3BteVFtdJyllhDdTenF1Nb1rAeN4egt8XLsE5NQDr1szFEG 11 | xH5lb+8EDtzgsGpeIddWR64xP0lDIKSZWst/toYKWiwjaY9uZCfAhvYQ1RsO7L/t 12 | sERmpYgh+rAZUh/Lr98EI8BPSPhzFcSHmtqzzejvC5zrZPHcUimz0CGA3YBiLoJX 13 | V1OrxmECgYEAxkd8gpmVP+LEWB3lqpSvJaXcGkbzcDb9m0OPzHUAJDZtiIIf0UmO 14 | nvL68/mzbCHSj+yFjZeG1rsrAVrOzrfDCuXjAv+JkEtEx0DIevU1u60lGnevOeky 15 | r8Be7pmymFB9/gzQAd5ezIlTv/COgoO986a3h1yfhzrrzbqSiivw308CgYEAwecI 16 | aZZwqH3GifR+0+Z1B48cezA5tC8LZt5yObGzUfxKTWy30d7lxe9N59t0KUVt/QL5 17 | qVkd7mqGzsUMyxUN2U2HVnFTWfUFMhkn/OnCnayhILs8UlCTD2Xxoy1KbQH/9FIr 18 | xf0pbMNJLXeGfyRt/8H+BzSZKBw9opJBWE4gqfECgYBp9FdvvryHuBkt8UQCRJPX 19 | rWsRy6pY47nf11mnazpZH5Cmqspv3zvMapF6AIxFk0leyYiQolFWvAv+HFV5F6+t 20 | Si1mM8GCDwbA5zh6pEBDewHhw+UqMBh63HSeUhmi1RiOwrAA36CO8i+D2Pt+eQHv 21 | ir52IiPJcs4BUNrv5Q1BdwKBgBHgVNw3LGe8QMOTMOYkRwHNZdjNl2RPOgPf2jQL 22 | d/bFBayhq0jD/fcDmvEXQFxVtFAxKAc+2g2S8J67d/R5Gm/AQAvuIrsWZcY6n38n 23 | pfOXaLt1x5fnKcevpFlg4Y2vM4O416RHNLx8PJDehh3Oo/2CSwMrDDuwbtZAGZok 24 | icphAoGBAI74Tisfn+aeCZMrO8KxaWS5r2CD1KVzddEMRKlJvSKTY+dOCtJ+XKj1 25 | OsZdcDvDC5GtgcywHsYeOWHldgDWY1S8Z/PUo4eK9qBXYBXp3JEZQ1dqzFdz+Txi 26 | rBn2WsFLsxV9j2/ugm0PqWVBcU2bPUCwvaRu3SOms2teaLwGCkhr 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /libs/common/example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | 17 | 18 | 27 | common 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const badgeVariants = cva( 8 | "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", 14 | secondary: 15 | "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", 16 | destructive: 17 | "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", 18 | outline: 19 | "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", 20 | }, 21 | }, 22 | defaultVariants: { 23 | variant: "default", 24 | }, 25 | }, 26 | ); 27 | 28 | function Badge({ 29 | className, 30 | variant, 31 | asChild = false, 32 | ...props 33 | }: React.ComponentProps<"span"> & 34 | VariantProps & { asChild?: boolean }) { 35 | const Comp = asChild ? Slot : "span"; 36 | 37 | return ( 38 | 43 | ); 44 | } 45 | 46 | export { Badge, badgeVariants }; 47 | -------------------------------------------------------------------------------- /apps/web-app/src/dashboard/dashboardBottomTab.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { DASHBOARDNAV } from "@/data/dashboardData.ts"; 3 | import { Link, useRouter } from "@tanstack/react-router"; 4 | 5 | function DashboardBottomTab() { 6 | const router = useRouter(); 7 | const [currentPath, setCurrentPath] = useState(""); 8 | 9 | useEffect(() => { 10 | setCurrentPath(window.location.pathname); 11 | }, []); 12 | 13 | return ( 14 | 48 | ); 49 | } 50 | 51 | export default DashboardBottomTab; 52 | -------------------------------------------------------------------------------- /apps/web-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | Potatoe - Support GitHub Contributors with Crypto 12 | 16 | 20 | 24 | 28 | 29 | 30 | 31 | 36 | 37 | 41 | 50 | 51 | 52 |
53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ); 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
25 | ); 26 | } 27 | 28 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 29 | return ( 30 |
35 | ); 36 | } 37 | 38 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 39 | return ( 40 |
45 | ); 46 | } 47 | 48 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 49 | return ( 50 |
55 | ); 56 | } 57 | 58 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 59 | return ( 60 |
65 | ); 66 | } 67 | 68 | export { 69 | Card, 70 | CardHeader, 71 | CardFooter, 72 | CardTitle, 73 | CardDescription, 74 | CardContent, 75 | }; 76 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as TabsPrimitive from "@radix-ui/react-tabs"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | function Tabs({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 16 | ); 17 | } 18 | 19 | function TabsList({ 20 | className, 21 | ...props 22 | }: React.ComponentProps) { 23 | return ( 24 | 32 | ); 33 | } 34 | 35 | function TabsTrigger({ 36 | className, 37 | ...props 38 | }: React.ComponentProps) { 39 | return ( 40 | 48 | ); 49 | } 50 | 51 | function TabsContent({ 52 | className, 53 | ...props 54 | }: React.ComponentProps) { 55 | return ( 56 | 61 | ); 62 | } 63 | 64 | export { Tabs, TabsList, TabsTrigger, TabsContent }; 65 | -------------------------------------------------------------------------------- /apps/web-app/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import Homepage from "@/components/pages/homepage"; 3 | import { useEffect } from "react"; 4 | import Cookies from "js-cookie"; 5 | import { useProfile } from "@/hooks/useProfile"; 6 | import ProductHuntBadge from "@/components/misc/product-hunt-badge"; 7 | 8 | export const Route = createFileRoute("/")({ 9 | validateSearch: (search: Record) => { 10 | return { 11 | token: search.token as string | undefined, 12 | }; 13 | }, 14 | component: IndexRoute, 15 | }); 16 | 17 | function IndexRoute() { 18 | const { token } = Route.useSearch(); 19 | useProfile(); 20 | 21 | useEffect(() => { 22 | if (token) { 23 | Cookies.set("auth-token", token, { 24 | secure: true, 25 | sameSite: "lax", 26 | expires: 7, 27 | }); 28 | } 29 | }, [token]); 30 | 31 | return ( 32 | <> 33 | 34 | 35 | 39 | {/* Potatoe Squeezy is for sale at @SideProjectors */} 44 | 45 |
50 | I would love to keep Potatoe in good hands, so I am offering to sell it.{" "} 51 | 57 | @obiabo_immanuel 58 | 59 |
60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /apps/server/src/routes/wallets.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { eq } from 'drizzle-orm'; 3 | import { db } from '../db'; 4 | import { wallets } from '../db/schema'; 5 | 6 | const walletsRoute = new Hono(); 7 | 8 | walletsRoute.post('/', async (c) => { 9 | try { 10 | const { userId, address } = await c.req.json(); 11 | 12 | if (!userId || !address) { 13 | return c.json({ error: 'User ID and wallet address are required' }, 400); 14 | } 15 | 16 | const newWallet = await db 17 | .insert(wallets) 18 | .values({ 19 | userId, 20 | address, 21 | updatedAt: new Date(), 22 | }) 23 | .returning(); 24 | 25 | return c.json(newWallet[0]); 26 | } catch (error) { 27 | console.error('Error adding wallet:', error); 28 | return c.json({ error: 'Internal server error' }, 500); 29 | } 30 | }); 31 | 32 | walletsRoute.put('/', async (c) => { 33 | try { 34 | const { walletId, address } = await c.req.json(); 35 | if (!walletId || !address) { 36 | return c.json({ error: 'Wallet ID and address are required' }, 400); 37 | } 38 | const updatedWallet = await db 39 | .update(wallets) 40 | .set({ 41 | address, 42 | updatedAt: new Date(), 43 | }) 44 | .where(eq(wallets.id, walletId)) 45 | .returning(); 46 | return c.json(updatedWallet[0]); 47 | } catch (error) { 48 | console.error('Error updating wallet:', error); 49 | return c.json({ error: 'Internal server error' }, 500); 50 | } 51 | }); 52 | 53 | walletsRoute.get('/user/:userId', async (c) => { 54 | try { 55 | const userId = parseInt(c.req.param('userId')); 56 | const userWallets = await db.query.wallets.findMany({ 57 | where: eq(wallets.userId, userId), 58 | }); 59 | 60 | return c.json(userWallets); 61 | } catch (error) { 62 | console.error('Error fetching wallets:', error); 63 | return c.json({ error: 'Internal server error' }, 500); 64 | } 65 | }); 66 | 67 | export default walletsRoute; 68 | -------------------------------------------------------------------------------- /apps/server/src/db/schema.ts: -------------------------------------------------------------------------------- 1 | import { 2 | pgTable, 3 | serial, 4 | text, 5 | timestamp, 6 | integer, 7 | numeric, 8 | } from 'drizzle-orm/pg-core'; 9 | import { relations } from 'drizzle-orm'; 10 | 11 | export const users = pgTable('users', { 12 | id: serial('id').primaryKey(), 13 | githubId: text('github_id').notNull().unique(), 14 | username: text('username').notNull(), 15 | email: text('email'), 16 | name: text('name'), 17 | avatarUrl: text('avatar_url'), 18 | createdAt: timestamp('created_at').defaultNow(), 19 | updatedAt: timestamp('updated_at').defaultNow(), 20 | }); 21 | 22 | export const wallets = pgTable('wallets', { 23 | id: serial('id').primaryKey(), 24 | userId: integer('user_id') 25 | .notNull() 26 | .references(() => users.id), 27 | address: text('address').notNull(), 28 | createdAt: timestamp('created_at').defaultNow(), 29 | updatedAt: timestamp('updated_at').defaultNow(), 30 | }); 31 | 32 | export const transactionRecords = pgTable('transaction_records', { 33 | id: serial('id').primaryKey(), 34 | amount: numeric('amount').notNull(), 35 | senderAddress: text('sender_address').notNull(), 36 | senderId: integer('sender_id').references(() => users.id), 37 | recipientAddress: text('recipient_address').notNull(), 38 | recipientId: integer('recipient_id').references(() => users.id), 39 | txHash: text('tx_hash'), 40 | note: text('note'), 41 | createdAt: timestamp('created_at').defaultNow(), 42 | }); 43 | 44 | export const walletsRelations = relations(wallets, ({ one }) => ({ 45 | user: one(users, { 46 | fields: [wallets.userId], 47 | references: [users.id], 48 | }), 49 | })); 50 | 51 | export const transactionRecordsRelations = relations( 52 | transactionRecords, 53 | ({ one }) => ({ 54 | sender: one(users, { 55 | fields: [transactionRecords.senderId], 56 | references: [users.id], 57 | }), 58 | recipient: one(users, { 59 | fields: [transactionRecords.recipientId], 60 | references: [users.id], 61 | }), 62 | }), 63 | ); 64 | -------------------------------------------------------------------------------- /apps/server/src/routes/tx-records.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { db } from '../db'; 3 | import { transactionRecords, users } from '../db/schema'; 4 | import { desc, eq } from 'drizzle-orm'; 5 | 6 | const txRecordsRoute = new Hono(); 7 | 8 | txRecordsRoute.get('/', async (c) => { 9 | try { 10 | const records = await db 11 | .select({ 12 | id: transactionRecords.id, 13 | amount: transactionRecords.amount, 14 | senderAddress: transactionRecords.senderAddress, 15 | recipientAddress: transactionRecords.recipientAddress, 16 | createdAt: transactionRecords.createdAt, 17 | sender: { 18 | username: users.username, 19 | avatarUrl: users.avatarUrl, 20 | }, 21 | }) 22 | .from(transactionRecords) 23 | .leftJoin(users, eq(users.id, transactionRecords.senderId)) 24 | .orderBy(desc(transactionRecords.createdAt)) 25 | .limit(50); 26 | 27 | return c.json(records); 28 | } catch (error) { 29 | console.error('Error fetching transaction records:', error); 30 | return c.json({ error: 'Internal server error' }, 500); 31 | } 32 | }); 33 | 34 | txRecordsRoute.post('/', async (c) => { 35 | try { 36 | const { amount, senderAddress, senderId, recipientAddress, recipientId } = 37 | await c.req.json(); 38 | 39 | if (!amount || !senderAddress || !recipientAddress) { 40 | return c.json( 41 | { 42 | error: 'Amount, sender address, and recipient address are required', 43 | }, 44 | 400, 45 | ); 46 | } 47 | 48 | const newRecord = await db 49 | .insert(transactionRecords) 50 | .values({ 51 | amount, 52 | senderAddress, 53 | senderId, 54 | recipientAddress, 55 | recipientId, 56 | }) 57 | .returning(); 58 | 59 | return c.json(newRecord[0]); 60 | } catch (error) { 61 | console.error('Error creating transaction record:', error); 62 | return c.json({ error: 'Internal server error' }, 500); 63 | } 64 | }); 65 | 66 | export default txRecordsRoute; 67 | -------------------------------------------------------------------------------- /apps/web-app/src/pages/explore/ExploreUsers.tsx: -------------------------------------------------------------------------------- 1 | import DefaultDashboard from "@/layouts/dashboard"; 2 | import { motion } from "framer-motion"; 3 | import { TAB_STATE } from "@/constant/index"; 4 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 5 | import GeneralGithubUsers from "@/components/pages/explore/general-users"; 6 | import PotatoeUsers from "@/components/pages/explore/potatoe-users"; 7 | 8 | const tabs = [ 9 | { value: "account", label: "Potatoe Users 🍟", content: }, 10 | { value: "general", label: "General", content: }, 11 | ]; 12 | 13 | export default function ExploreUsers() { 14 | return ( 15 | 16 | 21 |
22 |
23 |

24 | Find GitHub Users 25 |

26 |

27 | Search and tip your favorite GitHub contributors 28 |

29 |
30 | 31 | 32 | 33 | {tabs.map((tab) => ( 34 | 39 | {tab.label} 40 | 41 | ))} 42 | 43 | {tabs.map((tab) => ( 44 | 45 | {tab.content} 46 | 47 | ))} 48 | 49 |
50 |
51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /apps/web-app/src/store/user.store.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { persist, devtools } from "zustand/middleware"; 3 | 4 | interface Wallet { 5 | id: number; 6 | userId: number; 7 | address: string; 8 | createdAt: string; 9 | updatedAt: string; 10 | } 11 | 12 | interface User { 13 | id: number; 14 | githubId: string; 15 | username: string; 16 | email: string | null; 17 | name: string | null; 18 | avatarUrl: string; 19 | createdAt: string; 20 | updatedAt: string; 21 | } 22 | 23 | interface UserState { 24 | user: User | null; 25 | wallet: Wallet | null; 26 | isLoading: boolean; 27 | setUser: (user: User) => void; 28 | setWallet: (wallet: Wallet) => void; 29 | clearUser: () => void; 30 | setLoading: (status: boolean) => void; 31 | updateWalletAddress: (address: string) => void; 32 | } 33 | 34 | export const useUserStore = create()( 35 | devtools( 36 | persist( 37 | (set, get) => ({ 38 | user: null, 39 | wallet: null, 40 | isLoading: false, 41 | setUser: (user) => set({ user }, false, "setUser"), 42 | setWallet: (wallet) => set({ wallet }, false, "setWallet"), 43 | clearUser: () => set({ user: null, wallet: null }, false, "clearUser"), 44 | setLoading: (status) => set({ isLoading: status }, false, "setLoading"), 45 | updateWalletAddress: (address: string) => 46 | set( 47 | (state) => ({ 48 | wallet: state.wallet 49 | ? { 50 | ...state.wallet, 51 | address, 52 | updatedAt: new Date().toISOString(), 53 | } 54 | : null, 55 | }), 56 | false, 57 | "updateWalletAddress", 58 | ), 59 | }), 60 | { 61 | name: "user-storage", 62 | partialize: (state) => ({ 63 | user: state.user, 64 | wallet: state.wallet, 65 | }), 66 | }, 67 | ), 68 | { 69 | name: "UserStore", 70 | enabled: process.env.NODE_ENV === "development", 71 | }, 72 | ), 73 | ); 74 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function TooltipProvider({ 9 | delayDuration = 0, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 18 | ); 19 | } 20 | 21 | function Tooltip({ 22 | ...props 23 | }: React.ComponentProps) { 24 | return ( 25 | 26 | 27 | 28 | ); 29 | } 30 | 31 | function TooltipTrigger({ 32 | ...props 33 | }: React.ComponentProps) { 34 | return ; 35 | } 36 | 37 | function TooltipContent({ 38 | className, 39 | sideOffset = 0, 40 | children, 41 | ...props 42 | }: React.ComponentProps) { 43 | return ( 44 | 45 | 54 | {children} 55 | 56 | 57 | 58 | ); 59 | } 60 | 61 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; 62 | -------------------------------------------------------------------------------- /apps/web-app/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: 13 | 14 | ```js 15 | export default tseslint.config({ 16 | extends: [ 17 | // Remove ...tseslint.configs.recommended and replace with this 18 | ...tseslint.configs.recommendedTypeChecked, 19 | // Alternatively, use this for stricter rules 20 | ...tseslint.configs.strictTypeChecked, 21 | // Optionally, add this for stylistic rules 22 | ...tseslint.configs.stylisticTypeChecked, 23 | ], 24 | languageOptions: { 25 | // other options... 26 | parserOptions: { 27 | project: ["./tsconfig.node.json", "./tsconfig.app.json"], 28 | tsconfigRootDir: import.meta.dirname, 29 | }, 30 | }, 31 | }); 32 | ``` 33 | 34 | You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: 35 | 36 | ```js 37 | // eslint.config.js 38 | import reactX from "eslint-plugin-react-x"; 39 | import reactDom from "eslint-plugin-react-dom"; 40 | 41 | export default tseslint.config({ 42 | plugins: { 43 | // Add the react-x and react-dom plugins 44 | "react-x": reactX, 45 | "react-dom": reactDom, 46 | }, 47 | rules: { 48 | // other rules... 49 | // Enable its recommended typescript rules 50 | ...reactX.configs["recommended-typescript"].rules, 51 | ...reactDom.configs.recommended.rules, 52 | }, 53 | }); 54 | ``` 55 | -------------------------------------------------------------------------------- /apps/github-bot/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: /fork 4 | [pr]: /compare 5 | [code-of-conduct]: CODE_OF_CONDUCT.md 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 10 | 11 | ## Issues and PRs 12 | 13 | If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. 14 | 15 | We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. 16 | 17 | ## Submitting a pull request 18 | 19 | 1. [Fork][fork] and clone the repository. 20 | 1. Configure and install the dependencies: `npm install`. 21 | 1. Make sure the tests pass on your machine: `npm test`, note: these tests also apply the linter, so there's no need to lint separately. 22 | 1. Create a new branch: `git checkout -b my-branch-name`. 23 | 1. Make your change, add tests, and make sure the tests still pass. 24 | 1. Push to your fork and [submit a pull request][pr]. 25 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 26 | 27 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 28 | 29 | - Write and update tests. 30 | - Keep your changes as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 31 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 32 | 33 | Work in Progress pull requests are also welcome to get feedback early on, or if there is something blocked you. 34 | 35 | ## Resources 36 | 37 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 38 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 39 | - [GitHub Help](https://help.github.com) 40 | -------------------------------------------------------------------------------- /libs/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "1.0.0", 4 | "description": "french fries", 5 | "author": "yhoungdev", 6 | "license": "MIT", 7 | "repository": "yhoungdev/common", 8 | "main": "dist/index.js", 9 | "module": "dist/index.modern.js", 10 | "source": "src/index.tsx", 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "build": "microbundle-crl --no-compress --format modern,cjs", 16 | "start": "microbundle-crl watch --no-compress --format modern,cjs", 17 | "prepare": "run-s build", 18 | "test": "run-s test:unit test:lint test:build", 19 | "test:build": "run-s build", 20 | "test:lint": "eslint .", 21 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom", 22 | "test:watch": "react-scripts test --env=jsdom", 23 | "predeploy": "cd example && yarn install && yarn run build", 24 | "deploy": "gh-pages -d example/build" 25 | }, 26 | "peerDependencies": { 27 | "react": "^16.0.0" 28 | }, 29 | "devDependencies": { 30 | "@testing-library/jest-dom": "^4.2.4", 31 | "@testing-library/react": "^9.5.0", 32 | "@testing-library/user-event": "^7.2.1", 33 | "@types/jest": "^25.1.4", 34 | "@types/node": "^12.12.38", 35 | "@types/react": "^16.9.27", 36 | "@types/react-dom": "^16.9.7", 37 | "@typescript-eslint/eslint-plugin": "^2.26.0", 38 | "@typescript-eslint/parser": "^2.26.0", 39 | "microbundle-crl": "^0.13.10", 40 | "babel-eslint": "^10.0.3", 41 | "cross-env": "^7.0.2", 42 | "eslint": "^6.8.0", 43 | "eslint-config-prettier": "^6.7.0", 44 | "eslint-config-standard": "^14.1.0", 45 | "eslint-config-standard-react": "^9.2.0", 46 | "eslint-plugin-import": "^2.18.2", 47 | "eslint-plugin-node": "^11.0.0", 48 | "eslint-plugin-prettier": "^3.1.1", 49 | "eslint-plugin-promise": "^4.2.1", 50 | "eslint-plugin-react": "^7.17.0", 51 | "eslint-plugin-standard": "^4.0.1", 52 | "gh-pages": "^2.2.0", 53 | "npm-run-all": "^4.1.5", 54 | "prettier": "^2.0.4", 55 | "react": "^16.13.1", 56 | "react-dom": "^16.13.1", 57 | "react-scripts": "^3.4.1", 58 | "typescript": "^3.7.5" 59 | }, 60 | "files": [ 61 | "dist" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /apps/web-app/src/pages/status/success-page.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { Link } from "@tanstack/react-router"; 3 | import Confetti from "react-confetti"; 4 | import { useEffect, useRef } from "react"; 5 | 6 | const SuccessPage = () => { 7 | const audioRef = useRef(null); 8 | 9 | const params = new URLSearchParams(window.location.search); 10 | const txnHash = params.get("txnHash"); 11 | useEffect(() => { 12 | const handleInteraction = () => { 13 | if (audioRef.current) { 14 | audioRef.current 15 | .play() 16 | .then(() => { 17 | console.log("✅ Audio played successfully after user interaction."); 18 | }) 19 | .catch((err) => { 20 | console.error("❌ Still couldn’t play audio:", err); 21 | }); 22 | } 23 | 24 | window.removeEventListener("click", handleInteraction); 25 | }; 26 | 27 | window.addEventListener("click", handleInteraction); 28 | 29 | return () => { 30 | window.removeEventListener("click", handleInteraction); 31 | }; 32 | }, []); 33 | 34 | return ( 35 |
36 | 37 |
59 | ); 60 | }; 61 | 62 | export default SuccessPage; 63 | -------------------------------------------------------------------------------- /apps/web-app/src/layouts/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import DashboardHeader from "@/header/dashboardHeader.tsx"; 2 | import DashboardBottomTab from "@/dashboard/dashboardBottomTab.tsx"; 3 | import useAuth from "@/hooks/useAuth"; 4 | import { useEffect } from "react"; 5 | import { useNavigate } from "@tanstack/react-router"; 6 | import ModalLayout from "@/components/popups/modals"; 7 | import AddOrUpdateAddress from "@/components/pages/settings/add-or-update-address.tsx"; 8 | import { useUserStore } from "../../store/user.ts"; 9 | 10 | interface IDashboardProps { 11 | children: React.ReactNode; 12 | title?: string; 13 | } 14 | 15 | const DefaultDashboard = ({ children }: IDashboardProps): React.JSX.Element => { 16 | const { isAuthenticated, checkAuthStatus } = useAuth(); 17 | const navigate = useNavigate(); 18 | const { wallet } = useUserStore(); 19 | 20 | useEffect(() => { 21 | const isValid = checkAuthStatus(); 22 | if (!isValid) { 23 | navigate({ to: "/" }); 24 | } 25 | }, []); 26 | 27 | console.log(wallet); 28 | 29 | if (!isAuthenticated) { 30 | return null; 31 | } 32 | 33 | return ( 34 |
35 | {wallet === undefined && ( 36 |
43 | 47 | Please add your Solana wallet address.{" "} 48 | 49 | Click here. 50 | 51 |

52 | } 53 | //closeOnOverlayClick={!!address} 54 | > 55 | 56 |
57 |
58 | )} 59 |
60 | 61 |
{children}
62 | 63 |
64 |
65 | ); 66 | }; 67 | 68 | export default DefaultDashboard; 69 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SliderPrimitive from "@radix-ui/react-slider"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | function Slider({ 9 | className, 10 | defaultValue, 11 | value, 12 | min = 0, 13 | max = 100, 14 | ...props 15 | }: React.ComponentProps) { 16 | const _values = React.useMemo( 17 | () => 18 | Array.isArray(value) 19 | ? value 20 | : Array.isArray(defaultValue) 21 | ? defaultValue 22 | : [min, max], 23 | [value, defaultValue, min, max], 24 | ); 25 | 26 | return ( 27 | 39 | 45 | 51 | 52 | {Array.from({ length: _values.length }, (_, index) => ( 53 | 58 | ))} 59 | 60 | ); 61 | } 62 | 63 | export { Slider }; 64 | -------------------------------------------------------------------------------- /apps/server/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { cors } from 'hono/cors'; 3 | import { authRouter } from './routes/auth'; 4 | import walletsRoute from './routes/wallets'; 5 | import txRecordsRoute from './routes/tx-records'; 6 | import { db } from './db'; 7 | import { sql } from 'drizzle-orm'; 8 | import type { Env } from './types/env'; 9 | import { logger } from 'hono/logger'; 10 | import { prettyJSON } from 'hono/pretty-json'; 11 | import { userRoute } from './routes/user'; 12 | import { sendTelegramMessage } from './utils/telegram-notification'; 13 | import { TELEGRAM_CHAT_ID } from './constants'; 14 | import { launchBot, telegram_bot } from './config/telegraf'; 15 | const app = new Hono<{ Bindings: Env }>(); 16 | 17 | app.use(logger()); 18 | app.use(prettyJSON()); 19 | app.use('*', async (c, next) => { 20 | c.env = { 21 | GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID!, 22 | GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET!, 23 | JWT_SECRET: process.env.JWT_SECRET!, 24 | DB: db, 25 | }; 26 | await next(); 27 | }); 28 | 29 | app.use('/*', cors()); 30 | 31 | app.get('/', (c) => { 32 | sendTelegramMessage(TELEGRAM_CHAT_ID, 'Potatoe API is up and running!'); 33 | return c.json({ 34 | name: 'Potatoe GitHub Users API', 35 | version: '1.0.0', 36 | description: '⚡ Instantly tip developers with SOL for their contributions', 37 | }); 38 | }); 39 | 40 | app.get('/db-test', async (c) => { 41 | try { 42 | const result = await c.env.DB.query(sql`SELECT NOW()`); 43 | return c.json({ 44 | status: 'Connected', 45 | timestamp: result[0].now, 46 | message: 'Database connection successful!', 47 | }); 48 | } catch (error) { 49 | return c.json( 50 | { 51 | status: 'Error', 52 | 53 | message: 54 | error instanceof Error ? error.message : 'An unknown error occurred', 55 | }, 56 | 500, 57 | ); 58 | } 59 | }); 60 | 61 | const routes = [ 62 | { path: '/auth', handler: authRouter }, 63 | { path: '/wallet', handler: walletsRoute }, 64 | { path: '/user', handler: userRoute }, 65 | { path: '/tx-records', handler: txRecordsRoute }, 66 | ]; 67 | 68 | routes.forEach(({ path, handler }) => { 69 | app.route(path, handler); 70 | }); 71 | 72 | launchBot(); 73 | logger(); 74 | 75 | export default app; 76 | -------------------------------------------------------------------------------- /apps/web-app/src/components/typography/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { cva, type VariantProps } from "class-variance-authority"; 3 | import { cn } from "@/lib/utils"; 4 | 5 | const typographyVariants = cva("", { 6 | variants: { 7 | variant: { 8 | h1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl", 9 | h2: "scroll-m-20 text-3xl font-semibold tracking-tight", 10 | h3: "scroll-m-20 text-2xl font-semibold tracking-tight", 11 | h4: "scroll-m-20 text-xl font-semibold tracking-tight", 12 | h5: "scroll-m-20 text-lg font-semibold tracking-tight", 13 | h6: "scroll-m-20 text-base font-semibold tracking-tight", 14 | p: "leading-7 [&:not(:first-child)]:mt-6", 15 | blockquote: "mt-6 border-l-2 pl-6 italic", 16 | list: "my-6 ml-6 list-disc [&>li]:mt-2", 17 | body1: "text-base leading-7", 18 | body2: "text-sm leading-6", 19 | caption: "text-xs leading-5", 20 | muted: "text-sm text-muted-foreground", 21 | }, 22 | color: { 23 | default: "", 24 | primary: "text-primary", 25 | secondary: "text-secondary", 26 | muted: "text-muted-foreground", 27 | accent: "text-accent-foreground", 28 | destructive: "text-destructive", 29 | }, 30 | weight: { 31 | normal: "font-normal", 32 | medium: "font-medium", 33 | semibold: "font-semibold", 34 | bold: "font-bold", 35 | extrabold: "font-extrabold", 36 | }, 37 | }, 38 | defaultVariants: { 39 | variant: "body1", 40 | color: "default", 41 | weight: "normal", 42 | }, 43 | }); 44 | 45 | interface TypographyProps 46 | extends React.HTMLAttributes, 47 | VariantProps { 48 | as?: keyof JSX.IntrinsicElements; 49 | children: React.ReactNode; 50 | weight?: "normal" | "medium" | "semibold" | "bold" | "extrabold"; 51 | } 52 | 53 | export const Typography = ({ 54 | as: Component = "p", 55 | children, 56 | variant, 57 | color, 58 | weight, 59 | className, 60 | ...props 61 | }: TypographyProps) => { 62 | return ( 63 | 67 | {children} 68 | 69 | ); 70 | }; 71 | 72 | export default Typography; 73 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | `inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap 9 | rounded-md text-sm font-medium transition-all disabled:pointer-events-none 10 | disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 11 | shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 12 | focus-visible:ring-[3px] aria-invalid:ring-destructive/20 13 | dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive`, 14 | { 15 | variants: { 16 | variant: { 17 | default: 18 | "bg-red-400 text-primary-foreground shadow-xs hover:bg-primary/90", 19 | destructive: 20 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 21 | outline: 22 | "border bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 23 | secondary: 24 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 25 | ghost: 26 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 27 | link: "text-primary underline-offset-4 hover:underline", 28 | }, 29 | size: { 30 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 31 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 32 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 33 | icon: "size-9", 34 | }, 35 | }, 36 | defaultVariants: { 37 | variant: "default", 38 | size: "default", 39 | }, 40 | }, 41 | ); 42 | 43 | function Button({ 44 | className, 45 | variant, 46 | size, 47 | asChild = false, 48 | ...props 49 | }: React.ComponentProps<"button"> & 50 | VariantProps & { 51 | asChild?: boolean; 52 | }) { 53 | const Comp = asChild ? Slot : "button"; 54 | 55 | return ( 56 | 61 | ); 62 | } 63 | 64 | export { Button, buttonVariants }; 65 | -------------------------------------------------------------------------------- /apps/web-app/src/components/pages/explore/potatoe-users.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { UserService } from "@/services"; 3 | import { GithubUserCardSkeleton } from "@/components/github/GithubUserCardSkeleton"; 4 | import { GithubUserCard } from "@/components/github/GithubUserCard"; 5 | import { GithubUserSearch } from "@/components/search/GithubUserSearch"; 6 | import { useQuery } from "@tanstack/react-query"; 7 | import { IPotatoeUserData } from "@/interface/users.interface"; 8 | 9 | const fetchPotatoeUsers = async () => { 10 | const response = await UserService.fetchAllPotatoeUsers(); 11 | return response; 12 | }; 13 | 14 | const PotatoeUsers = () => { 15 | const [searchQuery, setSearchQuery] = useState(""); 16 | 17 | const { 18 | data: users, 19 | isLoading, 20 | isError, 21 | } = useQuery({ 22 | queryKey: ["potatoe-users"], 23 | queryFn: fetchPotatoeUsers, 24 | staleTime: 1000 * 60 * 5, 25 | }); 26 | 27 | const filteredUsers = users?.filter(({ users: user }) => 28 | user.username.toLowerCase().includes(searchQuery.toLowerCase()), 29 | ); 30 | 31 | return ( 32 |
33 | {}} 37 | loading={isLoading} 38 | /> 39 | 40 |
41 | {isLoading ? ( 42 | Array.from({ length: 6 }).map((_, index) => ( 43 | 44 | )) 45 | ) : isError ? ( 46 |

Failed to load users

47 | ) : filteredUsers?.length > 0 ? ( 48 | filteredUsers?.map((data: IPotatoeUserData, index: number) => { 49 | const users = data.users; 50 | 51 | const userData = { 52 | name: users.name, 53 | avatar_url: users.avatarUrl, 54 | login: users.username, 55 | email: users.email, 56 | }; 57 | return ( 58 | 63 | ); 64 | }) 65 | ) : ( 66 |

No users found

67 | )} 68 |
69 |
70 | ); 71 | }; 72 | 73 | export default PotatoeUsers; 74 | -------------------------------------------------------------------------------- /apps/web-app/src/components/pages/explore/general-users.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { toast } from "sonner"; 3 | import { GithubUserCardSkeleton } from "@/components/github/GithubUserCardSkeleton"; 4 | import { GithubUserCard } from "@/components/github/GithubUserCard"; 5 | import { GithubUserSearch } from "@/components/search/GithubUserSearch"; 6 | 7 | interface GitHubUser { 8 | login: string; 9 | avatar_url: string; 10 | name: string; 11 | bio: string; 12 | } 13 | 14 | const GeneralGithubUsers = () => { 15 | const [searchQuery, setSearchQuery] = useState(""); 16 | const [users, setUsers] = useState([]); 17 | const [loading, setLoading] = useState(false); 18 | 19 | const searchGithubUser = async () => { 20 | if (!searchQuery) { 21 | toast.error("Please enter a GitHub username"); 22 | return; 23 | } 24 | 25 | setLoading(true); 26 | try { 27 | const response = await fetch( 28 | `https://api.github.com/search/users?q=${searchQuery}&per_page=10`, 29 | ); 30 | const data = await response.json(); 31 | 32 | if (response.ok) { 33 | const detailedUsers = await Promise.all( 34 | data.items.map(async (user: any) => { 35 | const userResponse = await fetch( 36 | `https://api.github.com/users/${user.login}`, 37 | ); 38 | return userResponse.json(); 39 | }), 40 | ); 41 | 42 | setUsers(detailedUsers); 43 | toast.success(`Found ${detailedUsers.length} users!`); 44 | } else { 45 | setUsers([]); 46 | toast.error("No users found"); 47 | } 48 | } catch (error) { 49 | toast.error("Error fetching GitHub users"); 50 | console.error("Error:", error); 51 | } finally { 52 | setLoading(false); 53 | } 54 | }; 55 | return ( 56 |
57 | 63 | 64 |
65 | {loading ? ( 66 | <> 67 | {Array.from({ length: 6 }).map((_, index) => ( 68 | 69 | ))} 70 | 71 | ) : ( 72 | users?.map((user) => ) 73 | )} 74 |
75 |
76 | ); 77 | }; 78 | 79 | export default GeneralGithubUsers; 80 | -------------------------------------------------------------------------------- /apps/extension/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { supabaseObject } from "../../libs/supabase"; 3 | import { FaGithub } from "react-icons/fa"; 4 | import browser from "webextension-polyfill"; 5 | 6 | function SignIn() { 7 | const signInWithGithub = async () => { 8 | try { 9 | const { data, error } = await supabaseObject.auth.signInWithOAuth({ 10 | provider: "github", 11 | options: { 12 | redirectTo: browser.identity.getRedirectURL(), 13 | }, 14 | }); 15 | 16 | if (error) { 17 | console.error("Error during GitHub sign-in:", error.message); 18 | throw error; 19 | } 20 | 21 | if (data?.url) { 22 | await browser.tabs.create({ url: data.url }); 23 | console.log("GitHub sign-in initiated, redirected to:", data.url); 24 | } 25 | } catch (err) { 26 | console.error("Failed to sign in with GitHub:", err); 27 | } 28 | }; 29 | 30 | const getUser = async () => { 31 | try { 32 | const { 33 | data: { user }, 34 | error, 35 | } = await supabaseObject.auth.getUser(); 36 | 37 | if (error) { 38 | console.error("Error fetching user:", error.message); 39 | throw error; 40 | } 41 | 42 | console.log("Logged-in user:", user); 43 | } catch (err) { 44 | console.error("Failed to fetch user:", err); 45 | } 46 | }; 47 | 48 | return ( 49 |
55 |
56 |
57 |

Welcome Back

58 |

Sign in to your account using GitHub

59 |
60 | 61 | 68 | 69 | {/**/} 73 | {/* Get Current User*/} 74 | {/**/} 75 |
76 |
77 | ); 78 | } 79 | 80 | export default SignIn; 81 | -------------------------------------------------------------------------------- /apps/web-app/src/components/github/GithubUserCard.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { GithubIcon, ExternalLinkIcon } from "lucide-react"; 3 | import { Button } from "@/components/ui/button"; 4 | import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; 5 | import { truncateText } from "@/util/content-utils"; 6 | interface GitHubUser { 7 | login: string; 8 | avatar_url: string; 9 | name: string; 10 | bio: string; 11 | } 12 | 13 | interface GithubUserCardProps { 14 | user: GitHubUser; 15 | } 16 | 17 | export function GithubUserCard({ user }: GithubUserCardProps) { 18 | return ( 19 | 24 |
25 | 26 | 27 | 28 | {user?.login?.slice(0, 2).toUpperCase()} 29 | 30 | 31 |
32 |
33 |

34 | {user.name || user.login} 35 |

36 | 48 |
49 | {user.bio && ( 50 |

51 | {truncateText(user.bio, 30)} 52 |

53 | )} 54 | 64 |
65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /apps/github-bot/test/index.test.ts: -------------------------------------------------------------------------------- 1 | // You can import your modules 2 | // import index from '../src/index' 3 | 4 | import nock from "nock"; 5 | // Requiring our app implementation 6 | import myProbotApp from "../src/index.js"; 7 | import { Probot, ProbotOctokit } from "probot"; 8 | // Requiring our fixtures 9 | //import payload from "./fixtures/issues.opened.json" with { "type": "json"}; 10 | import fs from "fs"; 11 | import path from "path"; 12 | import { fileURLToPath } from "url"; 13 | import { describe, beforeEach, afterEach, test, expect } from "vitest"; 14 | 15 | const issueCreatedBody = { body: "Thanks for opening this issue!" }; 16 | 17 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 18 | 19 | const privateKey = fs.readFileSync( 20 | path.join(__dirname, "fixtures/mock-cert.pem"), 21 | "utf-8", 22 | ); 23 | 24 | const payload = JSON.parse( 25 | fs.readFileSync(path.join(__dirname, "fixtures/issues.opened.json"), "utf-8"), 26 | ); 27 | 28 | describe("My Probot app", () => { 29 | let probot: any; 30 | 31 | beforeEach(() => { 32 | nock.disableNetConnect(); 33 | probot = new Probot({ 34 | appId: 123, 35 | privateKey, 36 | // disable request throttling and retries for testing 37 | Octokit: ProbotOctokit.defaults({ 38 | retry: { enabled: false }, 39 | throttle: { enabled: false }, 40 | }), 41 | }); 42 | // Load our app into probot 43 | probot.load(myProbotApp); 44 | }); 45 | 46 | test("creates a comment when an issue is opened", async () => { 47 | const mock = nock("https://api.github.com") 48 | // Test that we correctly return a test token 49 | .post("/app/installations/2/access_tokens") 50 | .reply(200, { 51 | token: "test", 52 | permissions: { 53 | issues: "write", 54 | }, 55 | }) 56 | 57 | // Test that a comment is posted 58 | .post("/repos/hiimbex/testing-things/issues/1/comments", (body: any) => { 59 | expect(body).toMatchObject(issueCreatedBody); 60 | return true; 61 | }) 62 | .reply(200); 63 | 64 | // Receive a webhook event 65 | await probot.receive({ name: "issues", payload }); 66 | 67 | expect(mock.pendingMocks()).toStrictEqual([]); 68 | }); 69 | 70 | afterEach(() => { 71 | nock.cleanAll(); 72 | nock.enableNetConnect(); 73 | }); 74 | }); 75 | 76 | // For more information about testing with Jest see: 77 | // https://facebook.github.io/jest/ 78 | 79 | // For more information about using TypeScript in your tests, Jest recommends: 80 | // https://github.com/kulshekhar/ts-jest 81 | 82 | // For more information about testing with Nock see: 83 | // https://github.com/nock/nock 84 | -------------------------------------------------------------------------------- /apps/web-app/src/components/views/settings-drawer-view.tsx: -------------------------------------------------------------------------------- 1 | import AddressAndBadge from "../profile/addressAndBadge"; 2 | import ProductHuntBadge from "../misc/product-hunt-badge"; 3 | import { useUserStore } from "@/store/user.store"; 4 | import { toast } from "sonner"; 5 | import { Power } from "lucide-react"; 6 | import ModalLayout from "@/components/popups/modals"; 7 | import { DialogDescription } from "@/components/ui/dialog.tsx"; 8 | import useAuth from "@/hooks/useAuth.ts"; 9 | import { Button } from "@/components/ui/button.tsx"; 10 | 11 | const SettingsDrawerView = () => { 12 | const { user } = useUserStore() || {}; 13 | 14 | const { username: profile_name } = user?.users || {}; 15 | const { address } = user?.wallets || {}; 16 | 17 | const { logout } = useAuth(); 18 | 19 | const copyBadgeCode = () => { 20 | const badgeCode = ` 21 | 22 | Potatoe Squeezy - Support GitHub contributors with crypto 29 | 30 | `.trim(); 31 | navigator.clipboard.writeText(badgeCode); 32 | toast.success("Badge code copied to clipboard!"); 33 | }; 34 | return ( 35 |
36 | {}} 39 | onCopyBadge={copyBadgeCode} 40 | currentAddress={address} 41 | /> 42 |
43 | 44 | 45 | 51 |
52 | 53 |

Log out

54 |
55 |
56 | } 57 | title={"Log Out"} 58 | > 59 | 60 | Your account would be logged out 61 | 62 | 63 | 66 | 67 |
68 |
69 | ); 70 | }; 71 | export default SettingsDrawerView; 72 | -------------------------------------------------------------------------------- /apps/github-bot/src/controllers/commands.controller.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "probot"; 2 | import COMMANDS from "../enums/commands.ts"; 3 | 4 | class BountyBot { 5 | private context: Context; 6 | 7 | constructor(context: Context) { 8 | this.context = context; 9 | } 10 | 11 | async handleComment() { 12 | const { comment: commentContext, sender } = this.context.payload; 13 | const { body } = commentContext; 14 | console.log(body); 15 | 16 | if (body.startsWith(COMMANDS.BOUNTY)) { 17 | const bountyMatch = body.match(/^\/bounty\s+(\d+(?:\.\d+)?)\s+(\w+)$/); 18 | if (bountyMatch) { 19 | const [_, amount, currency] = bountyMatch; 20 | await this.registerBounty(amount, currency); 21 | } else { 22 | await this.registerBounty(); 23 | } 24 | } 25 | 26 | if (body.startsWith(COMMANDS.CLAIM)) { 27 | await this.claimBounty(); 28 | } 29 | 30 | if (body.startsWith(COMMANDS.EDIT_BOUNTY)) { 31 | await this.editBounty(); 32 | } 33 | 34 | if (body.startsWith(COMMANDS.HELP)) { 35 | await this.sendHelp(); 36 | } 37 | } 38 | 39 | private async registerBounty(amount?: string, currency?: string) { 40 | if (!amount || !currency) { 41 | const comment = this.context.issue({ 42 | body: `❌ Invalid bounty format. Please use: \`/bounty \`\nExample: \`/bounty 10 USDC\``, 43 | }); 44 | await this.context.octokit.issues.createComment(comment); 45 | return; 46 | } 47 | 48 | const comment = this.context.issue({ 49 | body: `💰 Bounty registered: ${amount} ${currency}`, 50 | }); 51 | await this.context.octokit.issues.createComment(comment); 52 | } 53 | 54 | private async rewardBounty() { 55 | const comment = this.context.issue({ 56 | body: `💰 Bounty registered!`, 57 | }); 58 | await this.context.octokit.issues.createComment(comment); 59 | } 60 | 61 | private async claimBounty() { 62 | const sender = this.context.payload.sender.login; 63 | const comment = this.context.issue({ 64 | body: `🛠️ @${sender} is claiming this bounty.`, 65 | }); 66 | await this.context.octokit.issues.createComment(comment); 67 | } 68 | 69 | private async editBounty() { 70 | const comment = this.context.issue({ 71 | body: `✏️ Bounty updated.`, 72 | }); 73 | await this.context.octokit.issues.createComment(comment); 74 | } 75 | 76 | private async sendHelp() { 77 | const comment = this.context.issue({ 78 | body: ` 79 | 🧠 **Potatoe Squeezy Bot Help** 80 | - \`/bounty 10 USDC\` 81 | - \`/claim\` 82 | - \`/edit-bounty 15 USDC\` 83 | - \`/cancel-bounty\` 84 | - \`/help\` 85 | `, 86 | }); 87 | await this.context.octokit.issues.createComment(comment); 88 | } 89 | } 90 | 91 | export default BountyBot; 92 | -------------------------------------------------------------------------------- /apps/extension/public/icon-with-shadow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /apps/web-app/src/pages/profile/userProfileCard.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import CelebrateUser from "./celebrateUser"; 3 | import { Skeleton } from "@/components/ui/skeleton"; 4 | import ProfilePanel from "@/components/profile/profilePanel"; 5 | import WalletNotConnected from "@/components/fallbacks/wallet-no-connect"; 6 | import { useWallet } from "@solana/wallet-adapter-react"; 7 | import useExtractUserWallet from "@/hooks/extract-user-wallet"; 8 | 9 | interface GitHubUser { 10 | login: string; 11 | name: string; 12 | bio: string; 13 | avatar_url: string; 14 | } 15 | 16 | function UserProfileCard() { 17 | const [userData, setUserData] = useState(null); 18 | const [loading, setLoading] = useState(true); 19 | 20 | const searchParams = new URLSearchParams(window.location.search); 21 | const username = searchParams.get("user"); 22 | 23 | const { wallet } = useExtractUserWallet(username || ""); 24 | const { address } = wallet || {}; 25 | 26 | const { connected } = useWallet(); 27 | 28 | useEffect(() => { 29 | const fetchUserData = async () => { 30 | const searchParams = new URLSearchParams(window.location.search); 31 | const username = searchParams.get("user"); 32 | 33 | if (!username) { 34 | setLoading(false); 35 | return; 36 | } 37 | 38 | try { 39 | const response = await fetch( 40 | `https://api.github.com/users/${username}`, 41 | ); 42 | const data = await response.json(); 43 | 44 | if (response.ok) { 45 | setUserData(data); 46 | } else { 47 | setUserData(null); 48 | } 49 | } catch (error) { 50 | console.error("Failed to fetch user:", error); 51 | setUserData(null); 52 | } finally { 53 | setLoading(false); 54 | } 55 | }; 56 | 57 | fetchUserData(); 58 | }, []); 59 | 60 | if (loading) { 61 | return ( 62 |
63 | 64 |
65 | ); 66 | } 67 | 68 | if (!userData) { 69 | return ( 70 |
71 | User not found 72 |
73 | ); 74 | } 75 | 76 | if (!connected) { 77 | return ; 78 | } 79 | 80 | return ( 81 |
82 | 89 | 90 |
91 | ); 92 | } 93 | 94 | export default UserProfileCard; 95 | -------------------------------------------------------------------------------- /apps/web-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview", 11 | "format": "prettier --write ." 12 | }, 13 | "dependencies": { 14 | "@radix-ui/react-alert-dialog": "^1.1.14", 15 | "@radix-ui/react-avatar": "^1.1.10", 16 | "@radix-ui/react-dialog": "^1.1.14", 17 | "@radix-ui/react-slider": "^1.3.5", 18 | "@radix-ui/react-slot": "^1.2.3", 19 | "@radix-ui/react-tabs": "^1.1.12", 20 | "@radix-ui/react-tooltip": "^1.2.7", 21 | "@solana-mobile/wallet-adapter-mobile": "2.2.0", 22 | "@solana/wallet-adapter-base": "^0.9.26", 23 | "@solana/wallet-adapter-react": "^0.15.38", 24 | "@solana/wallet-adapter-react-ui": "^0.9.38", 25 | "@solana/wallet-adapter-wallets": "^0.19.36", 26 | "@solana/web3.js": "1.98.2", 27 | "@supabase/supabase-js": "^2.49.9", 28 | "@tailwindcss/vite": "^4.1.8", 29 | "@tanstack/react-query": "^5.80.0", 30 | "@tanstack/react-router": "^1.120.15", 31 | "@types/js-cookie": "^3.0.6", 32 | "@vercel/analytics": "^1.5.0", 33 | "@walletconnect/solana-adapter": "^0.0.8", 34 | "avatar": "^0.1.0", 35 | "axios": "^1.9.0", 36 | "boring-avatars": "^1.11.2", 37 | "card": "^2.5.4", 38 | "class-variance-authority": "^0.7.1", 39 | "clsx": "^2.1.1", 40 | "date-fns": "^4.1.0", 41 | "framer-motion": "^12.16.0", 42 | "js-cookie": "^3.0.5", 43 | "lucid": "^2.21.0", 44 | "lucide-react": "^0.511.0", 45 | "motion": "^12.16.0", 46 | "next-themes": "^0.4.6", 47 | "react": "^19.1.0", 48 | "react-confetti": "^6.4.0", 49 | "react-dom": "^19.1.0", 50 | "react-hot-toast": "^2.5.2", 51 | "react-share": "^5.2.2", 52 | "rpc-websockets": "^10.0.0", 53 | "skeleton": "^1.0.0", 54 | "sonner": "^2.0.5", 55 | "tailwind-merge": "^3.3.0", 56 | "tailwindcss": "4.1.8", 57 | "tailwindcss-animate": "^1.0.7", 58 | "vite-plugin-node-polyfills": "^0.23.0", 59 | "zod": "^3.25.49", 60 | "zustand": "^5.0.5" 61 | }, 62 | "devDependencies": { 63 | "@eslint/js": "^9.28.0", 64 | "@locator/babel-jsx": "^0.4.4", 65 | "@tanstack/router-cli": "^1.120.15", 66 | "@tanstack/router-devtools": "^1.120.15", 67 | "@tanstack/router-plugin": "^1.120.15", 68 | "@types/node": "^22.15.29", 69 | "@types/react": "^19.1.6", 70 | "@types/react-dom": "^19.1.5", 71 | "@vitejs/plugin-react": "^4.5.1", 72 | "@vitejs/plugin-react-swc": "^3.10.1", 73 | "eslint": "^9.28.0", 74 | "eslint-plugin-react-hooks": "^5.2.0", 75 | "eslint-plugin-react-refresh": "^0.4.20", 76 | "globals": "^16.2.0", 77 | "tailwindcss-motion": "^1.1.0", 78 | "typescript": "~5.8.3", 79 | "typescript-eslint": "^8.33.1", 80 | "vite": "^6.3.5", 81 | "vite-plugin-pwa": "^1.0.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /apps/web-app/src/components/profile/addressAndBadge.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "../ui/button"; 2 | import { ChevronRight, CopyIcon } from "lucide-react"; 3 | import { TipBadge } from "./TipBadge"; 4 | import Typography from "../typography"; 5 | import ModalLayout from "../popups/modals"; 6 | import AddOrUpdateAddress from "../pages/settings/add-or-update-address"; 7 | import UpdateProfile from "../pages/settings/update-profile"; 8 | 9 | interface AddressAndBadgeProps { 10 | username: string; 11 | onCopyBadge: () => void; 12 | } 13 | 14 | const AddressAndBadge = ({ username, onCopyBadge }: AddressAndBadgeProps) => { 15 | return ( 16 |
17 | 21 |

22 | 💼 Add/Update Wallet Address 23 |

24 | 25 |
26 | } 27 | > 28 | 29 | 30 | 31 | 35 |

✨ Generate Badge

36 | 37 |
38 | } 39 | > 40 |
41 | 45 | Your Tip Badge ✨ 46 | 47 |
48 | 49 |
50 | 56 |
57 | 58 | 59 | 63 |

👤 Update Profile

64 | 65 |
66 | } 67 | > 68 | 69 | 70 |
71 | ); 72 | }; 73 | 74 | export default AddressAndBadge; 75 | -------------------------------------------------------------------------------- /apps/web-app/src/pages/usersPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import GithubUsersCard from "../misc/github_users_card.tsx"; 3 | import { Search } from "lucide-react"; 4 | import DefaultButton from "../button/index.tsx"; 5 | 6 | interface GitHubUser { 7 | id: number; 8 | login: string; 9 | avatar_url: string; 10 | name: string; 11 | bio?: string; 12 | } 13 | 14 | function UsersPage() { 15 | const [users, setUsers] = useState([]); 16 | const [searchQuery, setSearchQuery] = useState(""); 17 | const [loading, setLoading] = useState(false); 18 | 19 | const searchGithubUsers = async () => { 20 | if (!searchQuery) return; 21 | 22 | setLoading(true); 23 | try { 24 | const response = await fetch( 25 | `https://api.github.com/search/users?q=${searchQuery}&per_page=10`, 26 | ); 27 | const data = await response.json(); 28 | 29 | const detailedUsers = await Promise.all( 30 | data.items.map(async (user: GitHubUser) => { 31 | const userResponse = await fetch( 32 | `https://api.github.com/users/${user.login}`, 33 | ); 34 | return userResponse.json(); 35 | }), 36 | ); 37 | 38 | setUsers(detailedUsers); 39 | } catch (error) { 40 | console.error("Error fetching GitHub users:", error); 41 | } finally { 42 | setLoading(false); 43 | } 44 | }; 45 | 46 | const handleSearch = (e: React.FormEvent) => { 47 | e.preventDefault(); 48 | searchGithubUsers(); 49 | }; 50 | 51 | return ( 52 |
53 |
54 |
55 |
56 | setSearchQuery(e.target.value)} 60 | placeholder="Search GitHub users..." 61 | className="w-full p-3 bg-gray-900 rounded-xl pl-10" 62 | /> 63 | 67 |
68 | {loading ? "Searching..." : "Search"} 69 |
70 |
71 | 72 |
73 | {users.map((user) => ( 74 | 81 | ))} 82 |
83 | 84 | {users.length === 0 && !loading && ( 85 |
86 | Search for GitHub users to get started 87 |
88 | )} 89 |
90 | ); 91 | } 92 | 93 | export default UsersPage; 94 | -------------------------------------------------------------------------------- /apps/web-app/src/components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cn } from "@/lib/utils"; 3 | 4 | function Table({ className, ...props }: React.ComponentProps<"table">) { 5 | return ( 6 |
10 | 15 | 16 | ); 17 | } 18 | 19 | function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { 20 | return ( 21 | 26 | ); 27 | } 28 | 29 | function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { 30 | return ( 31 | 36 | ); 37 | } 38 | 39 | function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { 40 | return ( 41 | tr]:last:border-b-0", 44 | className, 45 | )} 46 | data-slot="table-footer" 47 | {...props} 48 | /> 49 | ); 50 | } 51 | 52 | function TableRow({ className, ...props }: React.ComponentProps<"tr">) { 53 | return ( 54 | 62 | ); 63 | } 64 | 65 | function TableHead({ className, ...props }: React.ComponentProps<"th">) { 66 | return ( 67 |
[role=checkbox]]:translate-y-[2px]", 70 | className, 71 | )} 72 | data-slot="table-head" 73 | {...props} 74 | /> 75 | ); 76 | } 77 | 78 | function TableCell({ className, ...props }: React.ComponentProps<"td">) { 79 | return ( 80 | [role=checkbox]]:translate-y-[2px]", 83 | className, 84 | )} 85 | data-slot="table-cell" 86 | {...props} 87 | /> 88 | ); 89 | } 90 | 91 | function TableCaption({ 92 | className, 93 | ...props 94 | }: React.ComponentProps<"caption">) { 95 | return ( 96 |
101 | ); 102 | } 103 | 104 | export { 105 | Table, 106 | TableHeader, 107 | TableBody, 108 | TableFooter, 109 | TableHead, 110 | TableRow, 111 | TableCell, 112 | TableCaption, 113 | }; 114 | --------------------------------------------------------------------------------