diff --git a/app/allocation/page.tsx b/app/allocation/page.tsx index c2a1d5d..4231401 100644 --- a/app/allocation/page.tsx +++ b/app/allocation/page.tsx @@ -29,7 +29,7 @@ import { useCategories } from '../comparison/utils/data-fetching/categories'; import WorldIdSignInSuccessModal from './components/WorldIdSignInSuccessModal'; import FarcasterModal from './components/FarcasterModal'; import DelegateModal from '../delegation/DelegationModal'; -import { FarcasterLookup } from '../delegation/farcaster/FarcasterLookup'; +import { SocialLookup } from '../delegation/farcaster/FarcasterLookup'; import FarcasterSuccess from '../delegation/farcaster/FarcasterSuccess'; import { axiosInstance } from '../utils/axiosInstance'; import { TargetDelegate } from '../delegation/farcaster/types'; @@ -66,8 +66,10 @@ const budgetCategory: BudgetCategory = { enum DelegationState { Initial, DelegationMethod, - Lookup, - Success, + FarcasterLookup, + XLookup, + WarpcastSuccess, + XSuccess, } const AllocationPage = () => { @@ -150,14 +152,16 @@ const AllocationPage = () => { }); }; - const handleDelegate = async (username: string, target: TargetDelegate) => { + const handleDelegate = async (username: string, target: TargetDelegate, isX?: boolean) => { if (!categoryToDelegate) return; posthog.capture('Delegating vote power'); - await axiosInstance.post('flow/delegate/farcaster', { - collectionId: categoryToDelegate.id, - targetUsername: username, - }); + if (!isX) { + await axiosInstance.post('flow/delegate/farcaster', { + collectionId: categoryToDelegate.id, + targetUsername: username, + }); + } queryClient.refetchQueries({ queryKey: ['fetch-delegates'], @@ -169,7 +173,12 @@ const AllocationPage = () => { queryKey: ['categories'], }); setTargetDelegate(target); - setDelegationState(DelegationState.Success); + if (isX) { + setDelegationState(DelegationState.XSuccess); + } + else { + setDelegationState(DelegationState.WarpcastSuccess); + } }; const handleAttestationModalClose = () => { @@ -408,25 +417,46 @@ const AllocationPage = () => { posthog.capture('Find delegate on Farcaster', { category: categoryToDelegate!.name, }); - setDelegationState(DelegationState.Lookup); + setDelegationState(DelegationState.FarcasterLookup); }} - onFindDelegatesTwitter={() => {}} + onFindDelegatesTwitter={() => { + posthog.capture('Find delegate on Farcaster', { + category: categoryToDelegate!.name, + }); + setDelegationState(DelegationState.XLookup); + }} + /> + )} + + {delegationState === DelegationState.FarcasterLookup && ( + )} - {delegationState === DelegationState.Lookup && ( - + )} + {delegationState === DelegationState.WarpcastSuccess && targetDelegate && ( + )} - {delegationState === DelegationState.Success && targetDelegate && ( + {delegationState === DelegationState.XSuccess && targetDelegate && ( )} diff --git a/app/api/checkTwitterUser/route.ts b/app/api/checkTwitterUser/route.ts new file mode 100644 index 0000000..cc95785 --- /dev/null +++ b/app/api/checkTwitterUser/route.ts @@ -0,0 +1,88 @@ +import { NextRequest, NextResponse } from 'next/server'; +import puppeteerCore from 'puppeteer-core'; +import chromium from '@sparticuz/chromium'; +import puppeteer from 'puppeteer'; + +interface IRequestBody { + url: string +} +export async function POST(req: NextRequest) { + const body: IRequestBody = await req.json(); + let url: URL; + try { + url = new URL(body.url); + } + catch (error) { + return NextResponse.json( + { error: 'Invalid URL provided.' }, + { status: 400 } + ); + } + if (url.host !== 'x.com' && url.host !== 'twitter.com') { + return NextResponse.json( + { error: 'URL provided doesn\'t belong to X(Twitter)' }, + { status: 400 } + ); + } + const regex = /^https?:\/\/(www\.)?(?:x\.com|twitter\.com)\/[A-Za-z0-9_]{1,15}$/; + if (!regex.test(url.toString())) { + return NextResponse.json( + { error: 'URL isn\'t valid' }, + { status: 400 } + ); + } + const paramsArray = url.pathname.split('/'); + if (paramsArray.length !== 2) { + /* most probably wont happen because of regex */ + return NextResponse.json( + { error: 'URL isn\'t a valid' }, + { status: 400 } + ); + } + const userhandle = paramsArray[1]; + + console.log(process.env.NODE_ENV, await chromium.executablePath()); + let browser = null; + if (process.env.NODE_ENV === 'development') { + browser = await puppeteer.launch({ headless: true }); + } + else { + browser = await puppeteerCore.launch({ + headless: chromium.headless, + args: [ + ...chromium.args, + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--single-process', + '--disable-gpu', + ], + defaultViewport: chromium.defaultViewport, + executablePath: await chromium.executablePath(), + }); + } + + const page = await browser.newPage(); + await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'); + await page.goto(url.toString(), { waitUntil: 'networkidle2' }); + const html = await page.content(); + + const doesntExist = html.includes('This account doesn’t exist'); + + if (doesntExist) { + await browser.close(); + return NextResponse.json( + { error: 'Account doesn\'t exists' }, + { status: 400 } + ); + } + + return NextResponse.json( + { + data: { + username: userhandle, + }, + }, + { status: 200 } + ); +} diff --git a/app/delegation/DelegationModal.tsx b/app/delegation/DelegationModal.tsx index c9fcfb1..ab53fee 100644 --- a/app/delegation/DelegationModal.tsx +++ b/app/delegation/DelegationModal.tsx @@ -1,6 +1,7 @@ import Image from 'next/image'; import React from 'react'; import { WarpcastIcon } from '@/public/assets/icon-components/WarpcastIcon'; +import { XIcon } from '@/public/assets/icon-components/XIcon'; interface Props { categoryName: string @@ -11,6 +12,7 @@ interface Props { const DelegateModal: React.FC = ({ categoryName, onFindDelegatesFarcaster, + onFindDelegatesTwitter, }) => { return (
@@ -45,7 +47,16 @@ const DelegateModal: React.FC = ({ heard by delegating your voting power to a delegate.

-
+
+ +
+
-
- {displayName} +
+ {isX + ? ( + X Icon + ) + : ( + {displayName!} + )}
-

{displayName}

-

+ {displayName && (

{displayName}

)} +

@ {username}

@@ -56,44 +75,27 @@ const DelegationConfirmation: React.FC = ({

I just delegated on @pairwise - 's Liquid Democracy Experiment. -
-
- I chose + {' '} + for Retro funding 6 [Category Name] to {' '} @ {username} - {' '} - to vote (or delegate) for me in the - - {categoryName} - - category of - {' '} - @optimism - 's RF6. -
-
- You can try it out too! -
-
- https://app.pairwise.vote/

{ posthog.capture('Post of Farcaster'); }} > diff --git a/app/delegation/farcaster/types.ts b/app/delegation/farcaster/types.ts index aec0a34..dfac98d 100644 --- a/app/delegation/farcaster/types.ts +++ b/app/delegation/farcaster/types.ts @@ -40,7 +40,7 @@ export type FarcasterUserByFid = { }; export type TargetDelegate = { - displayName: string + displayName?: string username: string - profilePicture: string + profilePicture?: string } diff --git a/public/assets/images/x2.svg b/public/assets/images/x2.svg new file mode 100644 index 0000000..cd4147a --- /dev/null +++ b/public/assets/images/x2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/vercel.json b/vercel.json index 1f9b786..0d68312 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,8 @@ { "functions": { + "app/api/checkTwitterUser/route.js": { + "includeFiles": "node_modules/@sparticuz/chromium/**" + }, "app/api/verifyTwitter/route.js": { "includeFiles": "node_modules/@sparticuz/chromium/**" } diff --git a/yarn.lock b/yarn.lock index 0135af0..805d891 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1511,10 +1511,10 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@puppeteer/browsers@2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-2.4.1.tgz#7afd271199cc920ece2ff25109278be0a3e8a225" - integrity sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng== +"@puppeteer/browsers@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@puppeteer/browsers/-/browsers-2.5.0.tgz#7e4f7ba8f04e54f11501b78dc7bcc4033de935d4" + integrity sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w== dependencies: debug "^4.3.7" extract-zip "^2.0.1" @@ -5288,15 +5288,6 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^11.2.0: - version "11.2.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" - integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -5401,14 +5392,13 @@ get-tsconfig@^4.7.5: resolve-pkg-maps "^1.0.0" get-uri@^6.0.1: - version "6.0.3" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.3.tgz#0d26697bc13cf91092e519aa63aa60ee5b6f385a" - integrity sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw== + version "6.0.4" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.4.tgz#6daaee9e12f9759e19e55ba313956883ef50e0a7" + integrity sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ== dependencies: basic-ftp "^5.0.2" data-uri-to-buffer "^6.0.2" debug "^4.3.4" - fs-extra "^11.2.0" glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" @@ -5521,7 +5511,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.11, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6285,15 +6275,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" @@ -7431,12 +7412,12 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -puppeteer-core@23.9.0, puppeteer-core@^23.9.0: - version "23.9.0" - resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-23.9.0.tgz#24add69fb58dde4ac49d165872b44a30d2bf5b32" - integrity sha512-hLVrav2HYMVdK0YILtfJwtnkBAwNOztUdR4aJ5YKDvgsbtagNr6urUJk9HyjRA9e+PaLI3jzJ0wM7A4jSZ7Qxw== +puppeteer-core@23.10.1, puppeteer-core@^23.9.0: + version "23.10.1" + resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-23.10.1.tgz#46feb3150454e799f1364c8e91d207dce3adbcb9" + integrity sha512-ey6NwixHYEUnhCA/uYi7uQQ4a0CZw4k+MatbHXGl5GEzaiRQziYUxc2HGpdQZ/gnh4KQWAKkocyIg1/dIm5d0g== dependencies: - "@puppeteer/browsers" "2.4.1" + "@puppeteer/browsers" "2.5.0" chromium-bidi "0.8.0" debug "^4.3.7" devtools-protocol "0.0.1367902" @@ -7444,15 +7425,15 @@ puppeteer-core@23.9.0, puppeteer-core@^23.9.0: ws "^8.18.0" puppeteer@^23.9.0: - version "23.9.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-23.9.0.tgz#69a5f3f4a9589865e0f96214f815112e9e206beb" - integrity sha512-WfB8jGwFV+qrD9dcJJVvWPFJBU6kxeu2wxJz9WooDGfM3vIiKLgzImEDBxUQnCBK/2cXB3d4dV6gs/LLpgfLDg== + version "23.10.1" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-23.10.1.tgz#b9f32cf7652d7a878ad5a96040d02b71578bc515" + integrity sha512-kbcO+vu91fgUyBzEwByPe4q5lEEuBq4cuOZnZeRL42G7r5UrfbUFlxBJayXBLBsD6pREdk/92ZFwFQq3MaN6ww== dependencies: - "@puppeteer/browsers" "2.4.1" + "@puppeteer/browsers" "2.5.0" chromium-bidi "0.8.0" cosmiconfig "^9.0.0" devtools-protocol "0.0.1367902" - puppeteer-core "23.9.0" + puppeteer-core "23.10.1" typed-query-selector "^2.12.0" qr-code-styling@^1.6.0-rc.1: @@ -8240,9 +8221,9 @@ streamsearch@^1.1.0: integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== streamx@^2.15.0, streamx@^2.20.0: - version "2.20.2" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.20.2.tgz#6a8911959d6f307c19781a1d19ecd94b5f042d78" - integrity sha512-aDGDLU+j9tJcUdPGOaHmVF1u/hhI+CsGkT02V3OKlHDV7IukOI+nTWAGkiZEKCO35rWN1wIr4tS7YFr1f4qSvA== + version "2.21.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.21.0.tgz#ef42a3b3fada6887ba06964443adbbbec60c5851" + integrity sha512-Qz6MsDZXJ6ur9u+b+4xCG18TluU7PGlRfXVAAjNiGsFrBUt/ioyLkxbFaKJygoPs+/kW4VyBj0bSj89Qu0IGyg== dependencies: fast-fifo "^1.3.2" queue-tick "^1.0.1" @@ -8861,11 +8842,6 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"