├── .babelrc ├── .env ├── .eslintrc.json ├── .gitignore ├── README.md ├── api ├── activity.ts ├── banner.ts ├── bridge.ts ├── buyOrder.ts ├── collection.ts ├── discover.ts ├── featured.ts ├── general.ts ├── launchpad.ts ├── profile.ts ├── search.ts ├── sellOrder.ts ├── settings.ts ├── stats.ts └── token.ts ├── components ├── Asset │ ├── Asset.tsx │ ├── AssetActivity │ │ └── AssetActivity.tsx │ ├── AssetAttributes │ │ ├── AssetAttributes.tsx │ │ └── styles.ts │ ├── AssetButtons │ │ ├── AcceptOfferButton.tsx │ │ ├── BridgeButton.tsx │ │ ├── BuySellOrderButton.tsx │ │ ├── CancelOfferButton.tsx │ │ ├── CancelSellOrderButton.tsx │ │ ├── LowerPriceButton.tsx │ │ ├── OfferButton.tsx │ │ ├── SellButton.tsx │ │ ├── TransferButton.tsx │ │ └── styles.ts │ ├── AssetDescription │ │ ├── AssetDescription.tsx │ │ └── styles.ts │ ├── AssetDetails │ │ ├── AssetDetails.tsx │ │ └── styles.ts │ ├── AssetHero │ │ ├── AssetHero.tsx │ │ ├── AssetMedia.tsx │ │ ├── AssetMediaModal.tsx │ │ ├── AssetMenu.tsx │ │ ├── CartButton.tsx │ │ ├── Erc1155Owners.tsx │ │ └── styles.ts │ ├── AssetListings │ │ ├── AssetListings.tsx │ │ └── styles.ts │ ├── AssetOffers │ │ ├── AssetOffers.tsx │ │ └── styles.ts │ ├── AssetPriceHistory │ │ ├── AssetPriceHistory.tsx │ │ └── styles.ts │ ├── MoreFromCollection │ │ ├── MoreFromCollection.tsx │ │ └── styles.ts │ └── styles.ts ├── AssetCard │ ├── AssetCard.tsx │ ├── AssetCardBridge.tsx │ ├── AssetCardGhost.tsx │ └── styles.ts ├── BrandAssets │ ├── BrandAssets.tsx │ └── styles.ts ├── Bridge │ ├── Bridge.tsx │ ├── DepositModal.tsx │ ├── WithdrawModal.tsx │ └── styles.ts ├── Cart │ ├── Cart.tsx │ └── styles.ts ├── Collection │ ├── Collection.tsx │ ├── CollectionActivity.tsx │ ├── Filters │ │ ├── ActivityFilters.tsx │ │ └── TokenFilters.tsx │ ├── SweepButton │ │ ├── SweepButton.tsx │ │ └── styles.ts │ └── styles.ts ├── CollectionCard │ ├── CollectionCard.tsx │ ├── CollectionCardGhost.tsx │ ├── CollectionCardLarge.tsx │ └── styles.ts ├── CollectionSettings │ ├── CollectionDetails.tsx │ ├── CollectionGreenlist.tsx │ ├── CollectionMetadata.tsx │ ├── CollectionMint.tsx │ ├── CollectionRewards.tsx │ ├── CollectionRoyalties.tsx │ ├── CollectionSettings.tsx │ └── CollectionVerification.tsx ├── Collections │ ├── Collections.tsx │ └── styles.ts ├── Common │ ├── Activity │ │ ├── ActivityRow.tsx │ │ ├── ActivityRowGhost.tsx │ │ ├── AssetActivityRow.tsx │ │ └── styles.ts │ ├── Carousel │ │ └── styles.ts │ ├── Container │ │ └── styles.ts │ ├── Filters │ │ ├── FilterUtils.tsx │ │ ├── Filters.tsx │ │ └── styles.ts │ ├── Images │ │ ├── CollectionImage.tsx │ │ ├── ProfileImage.tsx │ │ ├── TokenImage.tsx │ │ └── styles.ts │ ├── Menu │ │ └── styles.ts │ ├── Settings │ │ └── styles.ts │ ├── StyledModal │ │ └── styles.ts │ ├── Utils.tsx │ └── styles.ts ├── Custom │ ├── Optimism │ │ ├── Optimism.tsx │ │ └── styles.ts │ └── RabbitHole │ │ ├── RabbitHole.tsx │ │ └── styles.ts ├── Explore │ ├── Explore.tsx │ ├── ExploreActivity.tsx │ ├── ExploreTokens.tsx │ ├── Filters │ │ ├── ActivityFilters.tsx │ │ └── TokenFilters.tsx │ └── styles.ts ├── Following │ ├── Following.tsx │ └── styles.ts ├── Footer │ ├── Footer.tsx │ └── styles.ts ├── Header │ ├── AccountButton │ │ ├── AccountButton.tsx │ │ └── styles.ts │ ├── Banner │ │ ├── Banner.tsx │ │ └── styles.ts │ ├── Header.tsx │ ├── Notifications │ │ ├── Notifications.tsx │ │ └── styles.ts │ ├── Search │ │ ├── Search.tsx │ │ └── styles.ts │ └── styles.ts ├── Home │ ├── EndingSoon │ │ ├── EndingSoon.tsx │ │ └── styles.ts │ ├── Explore │ │ ├── Explore.tsx │ │ └── styles.ts │ ├── FeaturedAssets │ │ ├── FeaturedAssets.tsx │ │ └── styles.ts │ ├── FeaturedCollections │ │ ├── FeaturedCollections.tsx │ │ └── styles.ts │ ├── GasTracker │ │ ├── GasTracker.tsx │ │ └── styles.ts │ ├── Hero │ │ ├── Hero.tsx │ │ └── styles.ts │ ├── Home.tsx │ ├── Mirror │ │ ├── Mirror.tsx │ │ ├── MirrorCard.tsx │ │ └── styles.ts │ ├── Network │ │ ├── Arbitrum.tsx │ │ ├── Optimism.tsx │ │ └── styles.ts │ ├── OptimismNFTs │ │ ├── OptimismNFTs.tsx │ │ └── styles.ts │ ├── Quixotic │ │ ├── Quixotic.tsx │ │ └── styles.ts │ ├── Trending │ │ ├── Trending.tsx │ │ └── styles.ts │ └── styles.ts ├── Launch │ ├── Launch.tsx │ └── styles.ts ├── Launchpad │ ├── Launchpad.tsx │ ├── MintButton.tsx │ └── styles.ts ├── LaunchpadDeploy │ ├── LaunchpadDeploy.tsx │ └── styles.ts ├── List │ ├── List.tsx │ └── styles.ts ├── Loader │ ├── Loader.tsx │ └── styles.ts ├── LoginModal │ ├── LoginModal.tsx │ └── styles.ts ├── Maintenance │ ├── Maintenance.tsx │ └── styles.ts ├── MyCollections │ ├── MyCollections.tsx │ └── styles.ts ├── NotFound │ ├── NotFound.tsx │ └── styles.ts ├── NotLoggedIn │ ├── NotLoggedIn.tsx │ └── styles.ts ├── Offers │ ├── Offers.tsx │ └── styles.ts ├── OnboardModal │ ├── OnboardModal.tsx │ └── styles.ts ├── Privacy │ ├── Privacy.tsx │ └── styles.ts ├── Profile │ ├── Filters │ │ ├── ActivityFilters.tsx │ │ └── TokenFilters.tsx │ ├── Profile.tsx │ ├── ProfileActivity.tsx │ ├── ProfileCreated.tsx │ ├── ProfileErc1155Tokens.tsx │ ├── ProfileFeaturedTokens.tsx │ ├── ProfileHiddenTokens.tsx │ ├── ProfileLikedTokens.tsx │ ├── ProfileTokens.tsx │ └── styles.ts ├── ProfileCard │ ├── ProfileCard.tsx │ └── styles.ts ├── Search │ ├── Search.tsx │ └── styles.ts ├── Settings │ ├── ProfileNotifications.tsx │ ├── ProfileOffers.tsx │ ├── ProfileSettings.tsx │ ├── Settings.tsx │ └── styles.ts ├── Stats │ ├── Stats.tsx │ └── styles.ts └── Terms │ ├── Terms.tsx │ └── styles.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── [handle].tsx ├── [handle] │ └── [collection].tsx ├── _app.tsx ├── _document.tsx ├── asset │ ├── [contractId] │ │ └── [tokenId].tsx │ └── eth │ │ └── [contractId] │ │ └── [tokenId].tsx ├── brand-assets.tsx ├── bridge.tsx ├── cart.tsx ├── collection │ ├── [collection].tsx │ ├── [collection] │ │ └── settings.tsx │ └── eth │ │ ├── [collection].tsx │ │ └── [collection] │ │ └── settings.tsx ├── collections.tsx ├── collections │ └── [handle].tsx ├── explore.tsx ├── index.tsx ├── launch.tsx ├── launch │ ├── [collection].tsx │ └── deploy.tsx ├── listings.tsx ├── offers.tsx ├── onboard.tsx ├── privacy.tsx ├── profile.tsx ├── rabbithole.tsx ├── search.tsx ├── settings.tsx ├── stats.tsx └── terms.tsx ├── public ├── Quix_Logos.zip ├── bankless.png ├── bridge │ ├── network_eth.png │ └── network_op.png ├── display-themes │ ├── contained.svg │ ├── covered.svg │ └── padded.svg ├── etherscan.svg ├── hop.jpeg ├── launch │ ├── alchemy.jpeg │ ├── dune.png │ ├── ethereumbots.png │ ├── galaxy.jpeg │ ├── guild.png │ ├── layer3.jpeg │ ├── litprotocol.png │ ├── mintplex.png │ ├── niftykit.jpeg │ ├── niftykit.png │ ├── opt.png │ ├── optimism.webp │ ├── rainbow.webp │ └── simplehash.png ├── login │ ├── coinbase.svg │ ├── metamask.svg │ └── walletconnect.svg ├── logos │ ├── opt_full.png │ └── opt_full_dark.png ├── onboard │ ├── 0.png │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── 4.png ├── opog.png ├── opt.png ├── opt_banner.png ├── opt_banner_slim.png ├── opt_favicon.png ├── opt_twitter.png ├── optimism_gateway.webp ├── payment_tokens │ ├── ETH.png │ ├── OP.png │ └── WETH.png ├── profile.png └── rabbithole │ ├── 0.gif │ ├── 1.gif │ ├── 2.gif │ └── profile.png ├── shared ├── config.ts ├── theme.ts └── types.ts ├── store ├── address.ts ├── balance.ts ├── banner.ts ├── gridLayout.ts ├── hydrate.ts ├── index.ts ├── login.ts ├── onboard.ts ├── profile.ts └── showUSD.ts ├── tsconfig.json ├── utils ├── abi │ ├── erc1155ABI.ts │ ├── erc165ABI.ts │ ├── erc20ABI.ts │ └── erc721ABI.ts ├── bridge │ ├── bridgedErc721.ts │ ├── bridgedErc721ABI.ts │ ├── l1ERC721Bridge.ts │ ├── l1ERC721BridgeABI.ts │ ├── l2Erc721Bridge.ts │ └── l2Erc721BridgeABI.ts ├── constants.ts ├── errors.ts ├── exchange │ ├── exchange.ts │ ├── sendBuyOrder.ts │ ├── sendDutchAuction.ts │ └── sendSellOrder.ts ├── launchpad │ ├── launchpad.ts │ └── launchpadABI.ts ├── mixpanel.ts ├── onboard │ ├── onboard.ts │ └── onboardABI.ts ├── rewards │ ├── campaignTrackerABI.ts │ ├── rewardDeriverABI.ts │ ├── rewards.ts │ └── seaportWrapperABI.ts ├── signatureUtils.ts └── wallet.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "presets": ["next/babel"], 5 | "plugins": [ 6 | [ 7 | "babel-plugin-styled-components", 8 | { "ssr": true, "displayName": true, "preprocess": false } 9 | ] 10 | ] 11 | }, 12 | "production": { 13 | "presets": ["next/babel"], 14 | "plugins": [ 15 | [ 16 | "babel-plugin-styled-components", 17 | { "ssr": true, "displayName": false, "preprocess": false } 18 | ] 19 | ] 20 | }, 21 | "test": { 22 | "presets": ["next/babel"] 23 | } 24 | }, 25 | "presets": ["next/babel"], 26 | "plugins": [ 27 | [ 28 | "babel-plugin-styled-components", 29 | { "ssr": true, "displayName": false, "preprocess": false } 30 | ] 31 | ] 32 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_NETWORK="opt-mainnet" -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | .env.kovan.local 33 | .env.* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The Quix frontend is built on [Next.js](https://nextjs.org/). 2 | 3 | ## Getting Started 4 | 5 | First, configure the frontend to connect to the [Quix backend](https://github.com/quixotic-dev/backend). All configuration settings are stored in `/shared/config.ts`. 6 | 7 | Initially, the only settings that need to be updated are the `BACKEND_URL` and `BACKEND_TOKEN`, though you'll likely want to eventually configure the frontend to use the shared Seaport deployment (or your own deployment). 8 | 9 | The `BACKEND_URL` should point to where you are running the Quix backend. The `BACKEND_TOKEN` can be generated using the Django admin under `AUTH TOKEN` (not to be confused with `API KEY`). 10 | 11 | Lastly, the frontend is configured to run on Optimism Mainnet by default. To instead run on Optimism Goerli, update the environment variable `NEXT_PUBLIC_NETWORK` from `opt-mainnet` to `opt-goerli`. 12 | 13 | ## Running the Code 14 | 15 | First, run the development server: 16 | 17 | ```bash 18 | npm run dev 19 | # or 20 | yarn dev 21 | ``` 22 | 23 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 24 | 25 | ## Deploy on Vercel 26 | 27 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 28 | 29 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 30 | -------------------------------------------------------------------------------- /api/activity.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import fetch from "node-fetch"; 3 | import { activityEventRegistry } from "../components/Common/Utils"; 4 | import { siteConfig } from "../shared/config"; 5 | 6 | export async function fetchActivity() { 7 | const url = `${siteConfig.BACKEND_URL}/api/activity/`; 8 | 9 | const res = await fetch(url, { 10 | method: "GET", 11 | headers: { 12 | "Content-Type": "application/json", 13 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 14 | }, 15 | }); 16 | 17 | if (res.status < 400) { 18 | const responseJson = await res.json(); 19 | return responseJson; 20 | } else { 21 | return null; 22 | } 23 | } 24 | 25 | let activityController = null; 26 | export async function fetchFilteredActivity( 27 | sort, 28 | collections, 29 | events, 30 | minPrice, 31 | maxPrice, 32 | paymentToken 33 | ) { 34 | const sort_string = "&activity_sort=" + sort; 35 | const collections_string = collections 36 | .map((collection) => `collection=${collection}`) 37 | .join("&"); 38 | const events_string = 39 | events.length > 0 40 | ? "&" + 41 | events 42 | .map( 43 | (event) => 44 | `event=${encodeURIComponent(activityEventRegistry[event])}` 45 | ) 46 | .join("&") 47 | : ""; 48 | const price_string = 49 | minPrice || maxPrice 50 | ? "&price=" + 51 | (minPrice ? ethers.utils.parseUnits(minPrice, "gwei") : 0) + 52 | (maxPrice && ":" + ethers.utils.parseUnits(maxPrice, "gwei")) 53 | : ""; 54 | const currency_string = paymentToken ? "¤cy=" + paymentToken : ""; 55 | 56 | const url = `${siteConfig.BACKEND_URL}/api/activity/?${collections_string}${sort_string}${events_string}${price_string}${currency_string}`; 57 | 58 | if (activityController) activityController.abort(); 59 | activityController = new AbortController(); 60 | const signal = activityController.signal; 61 | 62 | try { 63 | const res = await fetch(url, { 64 | signal, 65 | method: "GET", 66 | headers: { 67 | "Content-Type": "application/json", 68 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 69 | }, 70 | }); 71 | 72 | if (res.status < 400) { 73 | const responseJson = await res.json(); 74 | return responseJson; 75 | } else { 76 | return null; 77 | } 78 | } catch (error) { 79 | return null; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /api/banner.ts: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "../shared/config"; 2 | 3 | export async function fetchSiteBanner() { 4 | const url = `${siteConfig.BACKEND_URL}/api/banner/`; 5 | 6 | const res = await fetch(url, { 7 | method: "GET", 8 | headers: { 9 | "Content-Type": "application/json", 10 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 11 | }, 12 | }); 13 | 14 | if (res.status < 400) { 15 | const responseJson = await res.json(); 16 | return responseJson; 17 | } else { 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /api/bridge.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { toast } from "react-toastify"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | export async function fetchTokenMetadata(address, token_id, layer) { 6 | const url = `${siteConfig.BACKEND_URL}/api/token-metadata/${address}:${token_id}/?layer=${layer}`; 7 | 8 | const res = await fetch(url, { 9 | method: "GET", 10 | headers: { 11 | "Content-Type": "application/json", 12 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 13 | }, 14 | }); 15 | 16 | if (res.status < 400) { 17 | const responseJson = await res.json(); 18 | return responseJson; 19 | } else { 20 | return null; 21 | } 22 | } 23 | 24 | export async function fetchL1Token(l2address) { 25 | const url = `${siteConfig.BACKEND_URL}/api/erc721bridge/get-l1-address/?l2Address=${l2address}`; 26 | 27 | const res = await fetch(url, { 28 | method: "GET", 29 | headers: { 30 | "Content-Type": "application/json", 31 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 32 | }, 33 | }); 34 | 35 | if (res.status < 400) { 36 | const responseJson = await res.json(); 37 | return responseJson; 38 | } else { 39 | return null; 40 | } 41 | } 42 | 43 | export async function fetchL2Token(l1address) { 44 | const url = `${siteConfig.BACKEND_URL}/api/erc721bridge/get-l2-address/?l1Address=${l1address}`; 45 | 46 | const res = await fetch(url, { 47 | method: "GET", 48 | headers: { 49 | "Content-Type": "application/json", 50 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 51 | }, 52 | }); 53 | 54 | if (res.status < 400) { 55 | const responseJson = await res.json(); 56 | return responseJson; 57 | } else { 58 | return null; 59 | } 60 | } 61 | 62 | export async function initiateL1Contract(l1address) { 63 | const url = `${siteConfig.BACKEND_URL}/api/erc721bridge/initiate-contract/?l1Address=${l1address}`; 64 | 65 | const res = await fetch(url, { 66 | method: "POST", 67 | headers: { 68 | "Content-Type": "application/json", 69 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 70 | }, 71 | }); 72 | 73 | return res.status; 74 | } 75 | -------------------------------------------------------------------------------- /api/buyOrder.ts: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | import { siteConfig } from "../shared/config"; 3 | 4 | export const postSeaportBuyOrder = async (order, orderHash) => { 5 | const url = `${siteConfig.BACKEND_URL}/api/buyorder/`; 6 | const res = await fetch(url, { 7 | method: "POST", 8 | headers: { 9 | "Content-Type": "application/json", 10 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 11 | }, 12 | body: JSON.stringify({ order, orderHash }), 13 | }); 14 | 15 | if (res.status < 400) { 16 | return await res.json(); 17 | } else { 18 | const json = await res.json(); 19 | if (json && json.error) { 20 | toast.error(`Error: ${json.error}`); 21 | } else { 22 | toast.error("There was an error completing this transaction"); 23 | } 24 | return null; 25 | } 26 | }; 27 | 28 | export const fetchBuyOrder = async (buyOrderId) => { 29 | const url = `${siteConfig.BACKEND_URL}/api/buyorder/${buyOrderId}`; 30 | const res = await fetch(url, { 31 | method: "GET", 32 | headers: { 33 | "Content-Type": "application/json", 34 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 35 | }, 36 | }); 37 | 38 | if (res.status < 400) { 39 | return await res.json(); 40 | } else { 41 | toast.error("There was an error completing this transaction"); 42 | return null; 43 | } 44 | }; 45 | 46 | export const fetchBuyOrders = async (collectionAddress, tokenId) => { 47 | const url = `${siteConfig.BACKEND_URL}/api/buyorder/get-buy-orders-for-token/?token=${collectionAddress}:${tokenId}`; 48 | const res = await fetch(url, { 49 | method: "GET", 50 | headers: { 51 | "Content-Type": "application/json", 52 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 53 | }, 54 | }); 55 | 56 | if (res.status < 400) { 57 | return await res.json(); 58 | } else { 59 | toast.error("There was an error completing this transaction"); 60 | return null; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /api/discover.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { siteConfig } from "../shared/config"; 3 | 4 | export async function fetchProfileFeed(address) { 5 | const url = `${siteConfig.BACKEND_URL}/api/profile/${address}/feed/`; 6 | 7 | const res = await fetch(url, { 8 | method: "GET", 9 | headers: { 10 | "Content-Type": "application/json", 11 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 12 | }, 13 | }); 14 | 15 | if (res.status < 400) { 16 | const responseJson = await res.json(); 17 | return responseJson; 18 | } else { 19 | return null; 20 | } 21 | } 22 | 23 | export async function fetchFollowedProfiles(address) { 24 | const url = `${siteConfig.BACKEND_URL}/api/profile/${address}/followed-profiles/`; 25 | 26 | const res = await fetch(url, { 27 | method: "GET", 28 | headers: { 29 | "Content-Type": "application/json", 30 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 31 | }, 32 | }); 33 | 34 | if (res.status < 400) { 35 | const responseJson = await res.json(); 36 | return responseJson; 37 | } else { 38 | return null; 39 | } 40 | } 41 | 42 | export async function fetchFollowedCollections(address) { 43 | const url = `${siteConfig.BACKEND_URL}/api/profile/${address}/followed-collections/`; 44 | 45 | const res = await fetch(url, { 46 | method: "GET", 47 | headers: { 48 | "Content-Type": "application/json", 49 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 50 | }, 51 | }); 52 | 53 | if (res.status < 400) { 54 | const responseJson = await res.json(); 55 | return responseJson; 56 | } else { 57 | return null; 58 | } 59 | } 60 | 61 | export async function updateProfileFollow( 62 | address, 63 | follow_address, 64 | action, 65 | type 66 | ) { 67 | const url = `${siteConfig.BACKEND_URL}/api/profile/${address}/${action}/`; 68 | 69 | const res = await fetch(url, { 70 | method: "POST", 71 | headers: { 72 | "Content-Type": "application/json", 73 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 74 | }, 75 | body: JSON.stringify({ 76 | follow_address: follow_address, 77 | type: type, 78 | }), 79 | }); 80 | 81 | if (res.status < 400) { 82 | const responseJson = await res.json(); 83 | return responseJson; 84 | } else { 85 | return null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /api/featured.ts: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "../shared/config"; 2 | 3 | export async function fetchFeaturedItems() { 4 | const url = `${siteConfig.BACKEND_URL}/api/featured/`; 5 | 6 | const res = await fetch(url, { 7 | method: "GET", 8 | headers: { 9 | "Content-Type": "application/json", 10 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 11 | }, 12 | }); 13 | 14 | if (res.status < 400) { 15 | const responseJson = await res.json(); 16 | return responseJson; 17 | } else { 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /api/general.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { siteConfig } from "../shared/config"; 3 | 4 | export async function fetchMoreByURL(url) { 5 | const res = await fetch(url, { 6 | method: "GET", 7 | headers: { 8 | "Content-Type": "application/json", 9 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 10 | }, 11 | }); 12 | 13 | if (res.status < 400) { 14 | const responseJson = await res.json(); 15 | return responseJson; 16 | } else { 17 | return null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /api/search.ts: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "../shared/config"; 2 | 3 | export async function fetchSearchResults(query) { 4 | const url = `${siteConfig.BACKEND_URL}/api/search/?term=${query}`; 5 | 6 | const res = await fetch(url, { 7 | method: "GET", 8 | headers: { 9 | "Content-Type": "application/json", 10 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 11 | }, 12 | }); 13 | 14 | if (res.status < 400) { 15 | const responseJson = await res.json(); 16 | return responseJson; 17 | } else { 18 | return null; 19 | } 20 | } 21 | 22 | export async function fetchExtendedSearchResults(query) { 23 | const url = `${siteConfig.BACKEND_URL}/api/search/extended/?term=${query}`; 24 | 25 | const res = await fetch(url, { 26 | method: "GET", 27 | headers: { 28 | "Content-Type": "application/json", 29 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 30 | }, 31 | }); 32 | 33 | if (res.status < 400) { 34 | const responseJson = await res.json(); 35 | return responseJson; 36 | } else { 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/sellOrder.ts: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | import { siteConfig } from "../shared/config"; 3 | 4 | export const postSeaportSellOrder = async (order, orderHash) => { 5 | const url = `${siteConfig.BACKEND_URL}/api/sellorder/`; 6 | const res = await fetch(url, { 7 | method: "POST", 8 | headers: { 9 | "Content-Type": "application/json", 10 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 11 | }, 12 | body: JSON.stringify({ order, orderHash }), 13 | }); 14 | 15 | if (res.status < 400) { 16 | return await res.json(); 17 | } else { 18 | toast.error("There was an error completing this transaction"); 19 | return null; 20 | } 21 | }; 22 | 23 | export const fetchSellOrderTimestamps = async (duration) => { 24 | const url = `${siteConfig.BACKEND_URL}/api/sellorder/get-timestamps/?duration=${duration}`; 25 | const res = await fetch(url, { 26 | method: "GET", 27 | headers: { 28 | "Content-Type": "application/json", 29 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 30 | }, 31 | }); 32 | 33 | if (res.status < 400) { 34 | return await res.json(); 35 | } else { 36 | toast.error( 37 | "The current block is cancelled, please try again in a few minutes" 38 | ); 39 | return null; 40 | } 41 | }; 42 | 43 | export const fetchSellOrder = async (orderId) => { 44 | const url = `${siteConfig.BACKEND_URL}/api/sellorder/${orderId}/`; 45 | const res = await fetch(url, { 46 | method: "GET", 47 | headers: { 48 | "Content-Type": "application/json", 49 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 50 | }, 51 | }); 52 | 53 | if (res.status < 400) { 54 | return await res.json(); 55 | } else { 56 | toast.error("There was an error completing this transaction"); 57 | return null; 58 | } 59 | }; 60 | 61 | export const fetchSellOrders = async ( 62 | collectionAddress, 63 | tokenId, 64 | sellerAddress 65 | ) => { 66 | const url = `${siteConfig.BACKEND_URL}/api/sellorder/?collectionAddress=${collectionAddress}&tokenId=${tokenId}&sellerAddress=${sellerAddress}`; 67 | const res = await fetch(url, { 68 | method: "GET", 69 | headers: { 70 | "Content-Type": "application/json", 71 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 72 | }, 73 | }); 74 | 75 | if (res.status < 400) { 76 | return await res.json(); 77 | } else { 78 | toast.error("There was an error completing this transaction"); 79 | return null; 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /api/stats.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { siteConfig } from "../shared/config"; 3 | 4 | let statsController = null; 5 | export async function fetchMarketplaceStats(sort, range, first = null) { 6 | const url = `${siteConfig.BACKEND_URL}/api/collection/stats/?&sort=${sort}&range=${range}`; 7 | 8 | if (first) { 9 | const res = await fetch(url, { 10 | method: "GET", 11 | headers: { 12 | "Content-Type": "application/json", 13 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 14 | }, 15 | }); 16 | 17 | if (res.status < 400) { 18 | const responseJson = await res.json(); 19 | return responseJson; 20 | } else { 21 | return null; 22 | } 23 | } else { 24 | if (statsController) statsController.abort(); 25 | statsController = new AbortController(); 26 | const signal = statsController.signal; 27 | 28 | try { 29 | const res = await fetch(url, { 30 | signal, 31 | method: "GET", 32 | headers: { 33 | "Content-Type": "application/json", 34 | Authorization: `Token ${siteConfig.BACKEND_TOKEN}`, 35 | }, 36 | }); 37 | 38 | if (res.status < 400) { 39 | const responseJson = await res.json(); 40 | return responseJson; 41 | } else { 42 | return null; 43 | } 44 | } catch (error) { 45 | return null; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /components/Asset/AssetAttributes/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const AttributesGrid = styled.div` 4 | display: grid; 5 | grid-template-columns: 1fr 1fr; 6 | grid-gap: 10px; 7 | 8 | @media (min-width: ${(props) => props.theme.small_width}) { 9 | grid-template-columns: 1fr 1fr 1fr; 10 | } 11 | `; 12 | 13 | export const Attribute = styled.div` 14 | position: relative; 15 | display: flex; 16 | flex-direction: column; 17 | grid-gap: 5px; 18 | font-size: 14px; 19 | font-weight: 700; 20 | padding: 15px; 21 | border-radius: 6px; 22 | overflow: hidden; 23 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.03), 24 | -5px -5px 15px rgba(0, 0, 0, 0.03); 25 | background: ${(props) => props.theme.colors.secondary}; 26 | color: ${(props) => props.theme.colors.primary}; 27 | height: 100%; 28 | transition: 0.2s all; 29 | 30 | &:hover { 31 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.06), 32 | -5px -5px 15px rgba(0, 0, 0, 0.06); 33 | } 34 | `; 35 | 36 | export const AttributeLabel = styled.div` 37 | font-size: 12px; 38 | font-weight: 500; 39 | color: ${(props) => props.theme.colors.network}; 40 | 41 | margin-bottom: auto; 42 | `; 43 | 44 | export const AttributeName = styled.div` 45 | margin-bottom: auto; 46 | `; 47 | 48 | export const AttributeRarity = styled.div` 49 | font-size: 13px; 50 | font-weight: 400; 51 | 52 | color: ${(props) => props.theme.colors.accent}; 53 | `; 54 | -------------------------------------------------------------------------------- /components/Asset/AssetButtons/BridgeButton.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { TbBuildingBridge } from "react-icons/tb"; 3 | import { Button, ButtonText } from "./styles"; 4 | 5 | export const BridgeButton = ({ token }) => { 6 | return ( 7 |
8 | 13 | 28 | 29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /components/Asset/AssetDescription/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const DescriptionGrid = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | grid-gap: 15px; 7 | `; 8 | 9 | export const DescriptionText = styled.div` 10 | white-space: pre-line; 11 | word-break: break-word; 12 | font-size: 14px; 13 | color: ${(props) => props.theme.colors.accent}; 14 | 15 | a { 16 | color: ${(props) => props.theme.colors.network}; 17 | } 18 | 19 | a:hover { 20 | color: ${(props) => props.theme.colors.primary}; 21 | } 22 | `; 23 | 24 | export const CollectionLinksGrid = styled.div` 25 | display: flex; 26 | grid-gap: 15px; 27 | `; 28 | 29 | export const CollectionLink = styled.div` 30 | display: flex; 31 | align-items: center; 32 | justify-content: space-around; 33 | font-size: 17px; 34 | 35 | &.etherscan { 36 | display: block; 37 | width: 15px; 38 | height: 15px; 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /components/Asset/AssetDetails/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const DetailsGrid = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | grid-gap: 15px; 7 | `; 8 | 9 | export const Detail = styled.div` 10 | display: flex; 11 | justify-content: space-between; 12 | grid-gap: 20px; 13 | font-size: 14px; 14 | 15 | overflow: hidden; 16 | word-break: break-all; 17 | `; 18 | 19 | export const DetailLabel = styled.div` 20 | flex-shrink: 0; 21 | font-weight: 500; 22 | `; 23 | 24 | export const DetailText = styled.div` 25 | display: -webkit-box; 26 | -webkit-line-clamp: 1; 27 | -webkit-box-orient: vertical; 28 | overflow: hidden; 29 | color: ${(props) => props.theme.colors.accent}; 30 | font-weight: 500; 31 | 32 | a { 33 | color: ${(props) => props.theme.colors.network}; 34 | } 35 | `; 36 | -------------------------------------------------------------------------------- /components/Asset/AssetHero/AssetMedia.tsx: -------------------------------------------------------------------------------- 1 | import "@google/model-viewer"; 2 | import { FaEthereum } from "react-icons/fa"; 3 | import { TbBuildingBridge } from "react-icons/tb"; 4 | import ReactTooltip from "react-tooltip"; 5 | import { siteConfig } from "../../../shared/config"; 6 | import { TokenMedia } from "../../Common/Images/TokenImage"; 7 | import { AssetMediaModal } from "./AssetMediaModal"; 8 | import { AssetMediaContainer, Network, Rank } from "./styles"; 9 | 10 | export const AssetMedia = ({ token, lightboxIsOpen, setLightboxIsOpen }) => { 11 | return ( 12 | <> 13 | 25 | token.collection.image_url && !token.animation_url 26 | ? setLightboxIsOpen(true) 27 | : null 28 | } 29 | > 30 | 31 | {token.network != siteConfig.NETWORK && ( 32 | <> 33 | 34 | 35 | 36 | 43 | Blockchain: Ethereum 44 | 45 | 46 | )} 47 | {token.bridged && ( 48 | <> 49 | 50 | 51 | 52 | 59 | Bridged from Ethereum 60 | 61 | 62 | )} 63 | {token.collection.ranking_enabled && token.rank && ( 64 | <> 65 | 66 | #{token.rank} 67 | 68 | 75 | Quix Rarity Rank 76 | 77 | 78 | )} 79 | 80 | 85 | 86 | ); 87 | }; 88 | -------------------------------------------------------------------------------- /components/Asset/AssetHero/AssetMediaModal.tsx: -------------------------------------------------------------------------------- 1 | import "@google/model-viewer"; 2 | import { useState } from "react"; 3 | import { TokenMedia } from "../../Common/Images/TokenImage"; 4 | import { AssetMediaContainer, StyledModal } from "./styles"; 5 | 6 | export const AssetMediaModal = ({ 7 | token, 8 | lightboxIsOpen, 9 | setLightboxIsOpen, 10 | }) => { 11 | const [opacity, setOpacity] = useState(0); 12 | 13 | async function toggleModal(e) { 14 | setOpacity(0); 15 | setLightboxIsOpen(!lightboxIsOpen); 16 | } 17 | 18 | function afterOpen() { 19 | setTimeout(() => { 20 | setOpacity(1); 21 | }, 100); 22 | } 23 | 24 | function beforeClose() { 25 | setOpacity(0); 26 | } 27 | 28 | return ( 29 | 39 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /components/Asset/AssetHero/CartButton.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { MdAddShoppingCart, MdRemoveShoppingCart } from "react-icons/md"; 3 | import { siteConfig } from "../../../shared/config"; 4 | import { AssetMenuButton } from "./styles"; 5 | 6 | export const CartButton = ({ token, expanded }) => { 7 | const [isInCart, setIsInCart] = useState( 8 | typeof window !== "undefined" && 9 | JSON.parse( 10 | localStorage.getItem(`${siteConfig.CHAIN_ID}_cart_token_ids`) || "[]" 11 | ).indexOf(token.contract_address + ":" + token.token_id) > -1 12 | ); 13 | 14 | const addToCart = () => { 15 | let cart_ids = JSON.parse( 16 | localStorage.getItem(`${siteConfig.CHAIN_ID}_cart_token_ids`) || "[]" 17 | ); 18 | cart_ids.push(token.contract_address + ":" + token.token_id); 19 | localStorage.setItem( 20 | `${siteConfig.CHAIN_ID}_cart_token_ids`, 21 | JSON.stringify(cart_ids) 22 | ); 23 | setIsInCart(true); 24 | window.dispatchEvent(new Event("cart")); 25 | }; 26 | 27 | const removeFromCart = () => { 28 | let cart_ids = JSON.parse( 29 | localStorage.getItem(`${siteConfig.CHAIN_ID}_cart_token_ids`) || "[]" 30 | ); 31 | cart_ids = cart_ids.filter( 32 | (e) => e !== token.contract_address + ":" + token.token_id 33 | ); 34 | localStorage.setItem( 35 | `${siteConfig.CHAIN_ID}_cart_token_ids`, 36 | JSON.stringify(cart_ids) 37 | ); 38 | setIsInCart(false); 39 | window.dispatchEvent(new Event("cart")); 40 | }; 41 | 42 | return ( 43 | 48 | {isInCart ? : } 49 | 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /components/Asset/AssetListings/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const BuyOrdersGrid = styled.div` 4 | display: grid; 5 | grid-template-columns: 6 | minmax(auto, auto) minmax(auto, auto) minmax(auto, auto) 7 | minmax(auto, auto); 8 | grid-gap: 20px; 9 | `; 10 | 11 | export const BuyOrdersRow = styled.div` 12 | display: contents; 13 | align-items: center; 14 | border-radius: 8px; 15 | background: ${(props) => props.theme.colors.secondary}; 16 | 17 | &.title { 18 | padding: 0 0 5px; 19 | } 20 | `; 21 | 22 | export const BuyOrdersText = styled.div` 23 | display: flex; 24 | grid-gap: 5px; 25 | align-items: center; 26 | font-weight: 500; 27 | font-size: 14px; 28 | color: ${(props) => props.theme.colors.primary}; 29 | flex-shrink: 0; 30 | white-space: nowrap; 31 | 32 | @media (min-width: ${(props) => props.theme.medium_width}) { 33 | font-size: 15px; 34 | } 35 | 36 | a { 37 | color: ${(props) => props.theme.colors.network}; 38 | } 39 | 40 | &.item { 41 | grid-gap: 10px; 42 | } 43 | 44 | &.title { 45 | font-size: 14px; 46 | font-weight: 800; 47 | 48 | @media (min-width: ${(props) => props.theme.medium_width}) { 49 | font-size: 16px; 50 | } 51 | } 52 | `; 53 | -------------------------------------------------------------------------------- /components/Asset/AssetOffers/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const BuyOrdersGrid = styled.div` 4 | display: grid; 5 | grid-template-columns: 6 | minmax(auto, auto) minmax(auto, auto) minmax(auto, auto) 7 | minmax(auto, auto) minmax(auto, auto) minmax(auto, auto); 8 | grid-gap: 20px; 9 | `; 10 | 11 | export const BuyOrdersRow = styled.div` 12 | display: contents; 13 | align-items: center; 14 | border-radius: 8px; 15 | background: ${(props) => props.theme.colors.secondary}; 16 | 17 | &.title { 18 | padding: 0 0 5px; 19 | } 20 | `; 21 | 22 | export const BuyOrdersText = styled.div` 23 | display: flex; 24 | grid-gap: 3px; 25 | align-items: center; 26 | font-weight: 400; 27 | font-size: 14px; 28 | color: ${(props) => props.theme.colors.primary}; 29 | flex-shrink: 0; 30 | 31 | @media (min-width: ${(props) => props.theme.medium_width}) { 32 | font-size: 15px; 33 | } 34 | 35 | a { 36 | color: ${(props) => props.theme.colors.network}; 37 | } 38 | 39 | &.item { 40 | grid-gap: 10px; 41 | } 42 | 43 | &.title { 44 | font-size: 14px; 45 | font-weight: 800; 46 | 47 | @media (min-width: ${(props) => props.theme.medium_width}) { 48 | font-size: 15px; 49 | } 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /components/Asset/AssetPriceHistory/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const ChartContainer = styled.div` 4 | position: relative; 5 | width: 99%; 6 | height: 250px; 7 | `; 8 | -------------------------------------------------------------------------------- /components/Asset/MoreFromCollection/MoreFromCollection.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { FiChevronDown, FiChevronRight } from "react-icons/fi"; 3 | import { IoGrid } from "react-icons/io5"; 4 | import { AssetCard } from "../../AssetCard/AssetCard"; 5 | import { 6 | Section, 7 | SectionContent, 8 | SectionTitle, 9 | SectionTitleText, 10 | } from "../styles"; 11 | import { CardGrid } from "./styles"; 12 | 13 | export const MoreFromCollection = ({ collectionName, tokens }) => { 14 | const [collapsed, setCollapsed] = useState(false); 15 | 16 | return ( 17 | tokens.length > 0 && ( 18 |
19 | setCollapsed(!collapsed)}> 20 | 21 | 22 | More from {collectionName} 23 | 24 | {collapsed ? : } 25 | 26 | {!collapsed && ( 27 | 28 | 29 | {tokens.map((token, index) => ( 30 | 31 | ))} 32 | 33 | 34 | )} 35 |
36 | ) 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /components/Asset/MoreFromCollection/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const CardGrid = styled.div` 4 | display: grid; 5 | grid-template-columns: repeat(auto-fill, minmax(225px, 1fr)); 6 | grid-gap: 20px; 7 | grid-template-rows: 1fr; 8 | 9 | @media (min-width: ${(props) => props.theme.small_width}) { 10 | grid-gap: 25px; 11 | } 12 | 13 | div:nth-child(4) { 14 | @media (min-width: ${(props) => props.theme.medium_width}) { 15 | display: none; 16 | } 17 | 18 | @media (min-width: 1097px) { 19 | display: flex; 20 | } 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /components/AssetCard/AssetCardGhost.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AssetName, 3 | Card, 4 | CardContent, 5 | CardSection, 6 | CollectionName, 7 | TokenImageContainer, 8 | } from "./styles"; 9 | 10 | export const AssetCardGhost = () => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /components/BrandAssets/BrandAssets.tsx: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "../../shared/config"; 2 | import { 3 | ContainerBackground, 4 | ContainerExtended, 5 | NoItemsButton, 6 | Subtitle, 7 | Title, 8 | } from "./styles"; 9 | 10 | export const BrandAssets = () => { 11 | return ( 12 | 13 | 14 | Brand Assets 15 | 16 | Download official Quix logos to use on your marketing materials or 17 | website. 18 | 19 | 20 | Download Logos 21 | 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /components/BrandAssets/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)``; 7 | 8 | export const Title = styled.div` 9 | font-size: 26px; 10 | font-weight: 600; 11 | margin-bottom: 15px; 12 | 13 | @media (min-width: ${(props) => props.theme.small_width}) { 14 | font-size: 32px; 15 | margin-bottom: 20px; 16 | } 17 | `; 18 | 19 | export const Subtitle = styled.div` 20 | margin-bottom: 20px; 21 | max-width: 700px; 22 | color: ${(props) => props.theme.colors.accent}; 23 | 24 | @media (min-width: ${(props) => props.theme.small_width}) { 25 | margin-bottom: 40px; 26 | } 27 | `; 28 | 29 | export const NoItemsButton = styled.div` 30 | margin-top: 30px; 31 | margin-bottom: 30px; 32 | background: ${(props) => props.theme.colors.network}; 33 | color: ${(props) => props.theme.colors.networkLight}; 34 | border-radius: 52px; 35 | padding: 12px 20px; 36 | text-align: center; 37 | font-weight: 600; 38 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.05), 39 | -5px -5px 15px rgba(0, 0, 0, 0.05); 40 | transition: all 0.2s; 41 | 42 | @media (min-width: ${(props) => props.theme.small_width}) { 43 | width: fit-content; 44 | padding: 12px 30px; 45 | } 46 | 47 | &:hover { 48 | cursor: pointer; 49 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.1), 50 | -5px -5px 15px rgba(0, 0, 0, 0.1); 51 | } 52 | 53 | &.no-click { 54 | cursor: default; 55 | } 56 | `; 57 | -------------------------------------------------------------------------------- /components/Collection/SweepButton/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const SweepBtn = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-around; 7 | width: 40px; 8 | height: 40px; 9 | font-size: 18px; 10 | border-radius: 12px; 11 | transition: all 0.2s; 12 | background: ${(props) => props.theme.colors.lightGray}; 13 | color: ${(props) => props.theme.colors.primary}; 14 | 15 | 16 | &:hover { 17 | cursor: pointer; 18 | background: ${(props) => props.theme.colors.gray}; 19 | } 20 | `; 21 | 22 | export const ButtonText = styled.div` 23 | display: flex; 24 | align-items: center; 25 | grid-gap: 8px; 26 | width: fit-content; 27 | margin: auto; 28 | `; 29 | -------------------------------------------------------------------------------- /components/CollectionCard/CollectionCardGhost.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Card, 3 | CardContent, 4 | CardDetails, 5 | CollectionDescription, 6 | CollectionImageContainer, 7 | CollectionName, 8 | CollectionStats, 9 | Stat, 10 | StatText, 11 | } from "./styles"; 12 | 13 | export const CollectionCardGhost = () => { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /components/CollectionSettings/CollectionVerification.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { toast } from "react-toastify"; 3 | import { siteConfig } from "../../shared/config"; 4 | import { 5 | EditorDescription, 6 | EditorGrid, 7 | EditorInput, 8 | EditorRow, 9 | EditorRowLabel, 10 | EditorRowText, 11 | EditorTextArea, 12 | EditorTitle, 13 | GenerateButton, 14 | } from "../Common/Settings/styles"; 15 | 16 | export const CollectionVerification = ({ collection, hostedCollection }) => { 17 | const copySourceCode = async () => { 18 | await navigator.clipboard.writeText(hostedCollection.src_code); 19 | toast.success("Copied source code to clipboard"); 20 | }; 21 | 22 | return ( 23 | 24 | 25 | Source Code 26 | 27 | Verify and publish your contract source code 28 | 29 | 30 | 31 | 32 | 33 | Compiler Type 34 | 35 | 36 | 37 | 38 | 39 | 40 | Compiler Version 41 | 42 | 43 | 44 | 45 | 46 | 47 | Open Source License Type 48 | 49 | 50 | 51 | 52 | 53 | 54 | Solidity Contract Code 55 | 56 | copySourceCode()} 59 | value={hostedCollection.src_code} 60 | readonly 61 | /> 62 | 63 | 64 | 65 | 70 | Verify Source Code 71 | 72 | 73 | 74 | ); 75 | }; 76 | -------------------------------------------------------------------------------- /components/Collections/Collections.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import InfiniteScroll from "react-infinite-scroll-component"; 3 | import { fetchMoreByURL } from "../../api/general"; 4 | import { siteConfig } from "../../shared/config"; 5 | import { CollectionCardGhost } from "../CollectionCard/CollectionCardGhost"; 6 | import { CollectionCardLarge } from "../CollectionCard/CollectionCardLarge"; 7 | import { CollectionsGrid } from "../CollectionCard/styles"; 8 | import { 9 | ContainerBackground, 10 | ContainerExtended, 11 | Subtitle, 12 | Title, 13 | } from "./styles"; 14 | 15 | export const Collections = ({ profile, collections, setCollections }) => { 16 | const handle = profile 17 | ? profile.username 18 | ? profile.username 19 | : profile.reverse_ens 20 | ? profile.reverse_ens 21 | : profile.address 22 | : ""; 23 | 24 | const [moreCollections, setMoreCollections] = useState( 25 | collections.next ? true : false 26 | ); 27 | 28 | const [collectionResults, setCollectionResults] = useState( 29 | collections.results 30 | ); 31 | 32 | async function fetchMoreCollections() { 33 | const moreCollections = await fetchMoreByURL(collections.next); 34 | 35 | if (!moreCollections.next) { 36 | setMoreCollections(false); 37 | } 38 | 39 | setCollections(moreCollections); 40 | setCollectionResults(collectionResults.concat(moreCollections.results)); 41 | } 42 | 43 | return ( 44 | 45 | 46 | {handle} Collections 47 | 48 | {collectionResults.length > 0 ? ( 49 | <>Explore NFT collections by {handle} on Quix 50 | ) : ( 51 | <>No collections found 52 | )} 53 | 54 | 55 | 56 | ( 61 | 62 | ))} 63 | style={{ display: "contents", overflow: "visible" }} 64 | > 65 | {collectionResults.map((collection) => ( 66 | 71 | ))} 72 | 73 | 74 | 75 | 76 | ); 77 | }; 78 | -------------------------------------------------------------------------------- /components/Collections/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)``; 7 | 8 | export const Title = styled.div` 9 | font-size: 26px; 10 | font-weight: 600; 11 | margin-bottom: 10px; 12 | 13 | @media (min-width: ${(props) => props.theme.small_width}) { 14 | font-size: 32px; 15 | } 16 | `; 17 | 18 | export const Subtitle = styled.div` 19 | margin-bottom: 20px; 20 | 21 | @media (min-width: ${(props) => props.theme.small_width}) { 22 | margin-bottom: 30px; 23 | } 24 | `; 25 | -------------------------------------------------------------------------------- /components/Common/Activity/ActivityRowGhost.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ActivityGridMobileRow, 3 | ActivityGridRow, 4 | ActivityInfo, 5 | ActivityText, 6 | CollectionName, 7 | DateContainer, 8 | TokenImageContainer, 9 | TokenName, 10 | } from "./styles"; 11 | 12 | export const ActivityRowGhost = () => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /components/Common/Carousel/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const responsive = { 4 | max_width: { 5 | breakpoint: { max: 5000, min: 1280 }, 6 | items: 3, 7 | }, 8 | medium_width: { 9 | breakpoint: { max: 1279, min: 769 }, 10 | items: 2, 11 | partialVisibilityGutter: 50, 12 | }, 13 | small_width: { 14 | breakpoint: { max: 768, min: 0 }, 15 | items: 1, 16 | partialVisibilityGutter: 50, 17 | }, 18 | }; 19 | 20 | export const CarouselContainer = styled.div` 21 | margin: 0 -10px; 22 | 23 | .carousel-item-class { 24 | padding: 0 10px; 25 | } 26 | `; 27 | 28 | export const Card = styled.div` 29 | display: flex; 30 | flex-direction: column; 31 | grid-gap: 10px; 32 | position: relative; 33 | border-radius: 16px; 34 | height: 100%; 35 | `; 36 | 37 | export const CardImageContainer = styled.div` 38 | background: ${(props) => props.theme.colors.gray}; 39 | border-radius: 12px; 40 | overflow: hidden; 41 | border: 1px solid ${(props) => props.theme.colors.gray}; 42 | 43 | img { 44 | border-radius: 12px; 45 | transition: all 0.2s; 46 | 47 | &:hover { 48 | transform: scale(1.1); 49 | } 50 | } 51 | `; 52 | 53 | export const CardTextContainer = styled.div` 54 | display: flex; 55 | flex-direction: column; 56 | align-items: flex-start; 57 | grid-gap: 5px; 58 | padding: 5px 10px; 59 | `; 60 | 61 | export const CardTitle = styled.div` 62 | font-size: 16px; 63 | font-weight: 600; 64 | margin-top: auto; 65 | color: ${(props) => props.theme.colors.primary}; 66 | 67 | @media (min-width: ${(props) => props.theme.small_width}) { 68 | font-size: 18px; 69 | } 70 | `; 71 | 72 | export const CardDescription = styled.div` 73 | font-size: 14px; 74 | color: ${(props) => props.theme.colors.accent}; 75 | 76 | @media (min-width: ${(props) => props.theme.small_width}) { 77 | font-size: 15px; 78 | } 79 | `; 80 | -------------------------------------------------------------------------------- /components/Common/Container/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const Container = styled.div` 4 | max-width: ${(props) => props.theme.max_width}; 5 | margin: 0 auto; 6 | padding: 20px 20px 40px; 7 | 8 | @media (min-width: ${(props) => props.theme.small_width}) { 9 | padding: 40px; 10 | } 11 | `; 12 | -------------------------------------------------------------------------------- /components/Common/Images/CollectionImage.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { allowedDomains } from "../Utils"; 3 | import { ImageContainer, ImagePlaceholder } from "./styles"; 4 | 5 | export const CollectionImage = ({ collection, cover = true }) => { 6 | return ( 7 | <> 8 | {collection.image_url ? ( 9 | allowedDomains.has(collection.image_url.split("/")[2]) ? ( 10 | 20 | ) : ( 21 | 22 | 23 | 24 | ) 25 | ) : ( 26 | 30 | )} 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /components/Common/Images/ProfileImage.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { ImagePlaceholder } from "./styles"; 3 | 4 | export const ProfileImage = ({ profile }) => { 5 | return ( 6 | <> 7 | {profile.profile_image ? ( 8 | 18 | ) : ( 19 | 23 | )} 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /components/Common/Images/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const ImageContainer = styled.div` 4 | width: 100%; 5 | height: 100%; 6 | 7 | img { 8 | display: flex; 9 | width: 100%; 10 | height: 100%; 11 | object-fit: contain; 12 | aspect-ratio: 1; 13 | } 14 | 15 | &.cover { 16 | img { 17 | object-fit: cover; 18 | } 19 | } 20 | 21 | &.token-media { 22 | position: relative; 23 | 24 | img { 25 | position: absolute; 26 | } 27 | } 28 | `; 29 | 30 | interface ImagePlaceholderProps { 31 | start: string; 32 | end: string; 33 | } 34 | 35 | export const ImagePlaceholder = styled.div` 36 | width: 100%; 37 | height: 100%; 38 | background: ${(props) => 39 | `linear-gradient(-45deg, #${props.start}, #${props.end})`}; 40 | `; 41 | -------------------------------------------------------------------------------- /components/Common/Menu/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const Menu = styled.div` 4 | display: flex; 5 | align-items: center; 6 | width: 100%; 7 | border-bottom: 1px solid ${(props) => props.theme.colors.gray}; 8 | margin-bottom: 20px; 9 | grid-gap: 25px; 10 | overflow-x: scroll; 11 | 12 | -ms-overflow-style: none; 13 | scrollbar-width: none; 14 | &::-webkit-scrollbar { 15 | display: none; 16 | } 17 | 18 | @media (min-width: ${(props) => props.theme.small_width}) { 19 | justify-content: start; 20 | grid-gap: 20px; 21 | margin-top: 20px; 22 | margin-bottom: 30px; 23 | } 24 | 25 | @media (min-width: ${(props) => props.theme.medium_width}) { 26 | grid-gap: 30px; 27 | } 28 | 29 | &.collection { 30 | margin: 25px 0 0; 31 | 32 | @media (min-width: ${(props) => props.theme.small_width}) { 33 | margin: 30px 0; 34 | } 35 | } 36 | 37 | &.profile { 38 | margin: 20px 0; 39 | 40 | @media (min-width: ${(props) => props.theme.small_width}) { 41 | margin: 30px 0; 42 | } 43 | } 44 | `; 45 | 46 | export const MenuItem = styled.div` 47 | display: flex; 48 | align-items: center; 49 | grid-gap: 8px; 50 | font-size: 14px; 51 | font-weight: 600; 52 | color: ${(props) => props.theme.colors.accent}; 53 | padding-bottom: 8px; 54 | transition: color 0.2s; 55 | flex-shrink: 0; 56 | 57 | @media (min-width: ${(props) => props.theme.small_width}) { 58 | font-size: 15px; 59 | } 60 | 61 | &.selected { 62 | border-bottom: 2px solid ${(props) => props.theme.colors.primary}; 63 | color: ${(props) => props.theme.colors.primary}; 64 | padding-bottom: 6px; 65 | } 66 | 67 | &:hover { 68 | cursor: pointer; 69 | color: ${(props) => props.theme.colors.primary}; 70 | } 71 | 72 | &.edit { 73 | margin-left: auto; 74 | } 75 | `; 76 | 77 | export const MenuIcon = styled.div` 78 | display: flex; 79 | font-size: 15px; 80 | font-weight: 400; 81 | `; 82 | -------------------------------------------------------------------------------- /components/Common/StyledModal/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { BaseModalBackground } from "styled-react-modal"; 3 | 4 | export const ModalBackground = styled(BaseModalBackground)` 5 | opacity: ${(props) => props.opacity}; 6 | transition: all 0.2s ease-in-out; 7 | z-index: 9999; 8 | background: rgba(0, 0, 0, 0.7); 9 | `; 10 | -------------------------------------------------------------------------------- /components/Common/Utils.tsx: -------------------------------------------------------------------------------- 1 | import { FaParachuteBox } from "react-icons/fa"; 2 | import { IoMdCart, IoMdPricetag, IoMdTrash } from "react-icons/io"; 3 | import { IoCreate } from "react-icons/io5"; 4 | import { MdPriceChange } from "react-icons/md"; 5 | import { RiArrowLeftRightLine } from "react-icons/ri"; 6 | import { TbBuildingBridge } from "react-icons/tb"; 7 | import buildFormatter from "react-timeago/lib/formatters/buildFormatter"; 8 | import { siteConfig } from "../../shared/config"; 9 | 10 | export const eligiblePaymentTokens = ["ETH", "OP"]; 11 | 12 | export const allowedDomains = new Set([ 13 | "fanbase-1.s3.amazonaws.com", 14 | "ipfs.quixotic.io", 15 | "quixotic.infura-ipfs.io", 16 | "ipfs.io", 17 | "fanbase-1.s3.us-west-2.amazonaws.com", 18 | "gateway.pinata.cloud", 19 | "cloudflare-ipfs.com", 20 | "cf-ipfs.com", 21 | "ipfs.infura.io", 22 | "storage.googleapis.com", 23 | "firebasestorage.googleapis.com", 24 | "arweave.net", 25 | "cryptotesters.mypinata.cloud", 26 | ]); 27 | 28 | export const offerFormatter = buildFormatter({ 29 | prefixAgo: null, 30 | prefixFromNow: "in", 31 | suffixAgo: "ago", 32 | suffixFromNow: null, 33 | seconds: "a minute", 34 | minute: "a minute", 35 | minutes: "%d minutes", 36 | hour: "an hour", 37 | hours: "%d hours", 38 | day: "a day", 39 | days: "%d days", 40 | month: "a month", 41 | months: "%d months", 42 | year: "a year", 43 | years: "%d years", 44 | wordSeparator: " ", 45 | }); 46 | 47 | export const expirationFormatter = buildFormatter({ 48 | prefixAgo: null, 49 | prefixFromNow: null, 50 | suffixAgo: "ago", 51 | suffixFromNow: "", 52 | seconds: "< 1 min", 53 | minute: "1 min", 54 | minutes: "%d min", 55 | hour: "1 hour", 56 | hours: "%d hours", 57 | day: "1 day", 58 | days: "%d days", 59 | month: "1 month", 60 | months: "%d months", 61 | year: "1 year", 62 | years: "%d years", 63 | wordSeparator: " ", 64 | }); 65 | 66 | export const activityIconRegistry = { 67 | Transfer: , 68 | Sale: , 69 | Mint: , 70 | List: , 71 | Offer: , 72 | Burn: , 73 | Airdrop: , 74 | Bridge: , 75 | }; 76 | 77 | // This list should match backend ActivityType model 78 | export const activityEventRegistry = { 79 | Mint: "MI", 80 | Sale: "SA", 81 | Transfer: "TR", 82 | Offer: "OF", 83 | List: "LI", 84 | Burn: "BU", 85 | Airdrop: "AD", 86 | Bridge: "BR", 87 | }; 88 | 89 | export const chainRegistry = { 90 | opt: "Optimism", 91 | eth: "Ethereum", 92 | }; 93 | 94 | export const chainIconRegistry = { 95 | opt: "OP", 96 | eth: "ETH", 97 | }; 98 | -------------------------------------------------------------------------------- /components/Common/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const TextTruncater = styled.div` 4 | display: -webkit-box; 5 | -webkit-line-clamp: 1; 6 | -webkit-box-orient: vertical; 7 | overflow: hidden; 8 | word-break: break-all; 9 | `; 10 | 11 | export const NoItems = styled.div` 12 | padding-bottom: 150px; 13 | 14 | h1 { 15 | font-size: 18px; 16 | 17 | @media (min-width: ${(props) => props.theme.small_width}) { 18 | font-size: 20px; 19 | } 20 | } 21 | 22 | p { 23 | font-size: 15px; 24 | } 25 | `; 26 | 27 | export const PriceIcon = styled.div` 28 | display: inline-block; 29 | width: 14px; 30 | height: 14px; 31 | margin-top: 1px; 32 | 33 | &.small { 34 | width: 10px; 35 | height: 10px; 36 | } 37 | 38 | &.large { 39 | width: 24px; 40 | height: 24px; 41 | } 42 | 43 | &.margin-right { 44 | margin-right: 3px; 45 | } 46 | `; 47 | -------------------------------------------------------------------------------- /components/Explore/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div` 5 | &.filtersVisible { 6 | position: fixed; 7 | 8 | @media (min-width: ${(props) => props.theme.small_width}) { 9 | position: initial; 10 | } 11 | } 12 | `; 13 | 14 | export const ContainerExtended = styled(Container)` 15 | max-width: none; 16 | `; 17 | 18 | export const Title = styled.div` 19 | font-size: 26px; 20 | font-weight: 600; 21 | margin-bottom: 20px; 22 | 23 | @media (min-width: ${(props) => props.theme.small_width}) { 24 | font-size: 32px; 25 | margin-bottom: 30px; 26 | } 27 | `; 28 | 29 | export const TwoColGrid = styled.div` 30 | display: flex; 31 | flex-direction: column; 32 | grid-gap: 30px; 33 | 34 | @media (min-width: ${(props) => props.theme.medium_width}) { 35 | display: grid; 36 | grid-template-columns: 275px 1fr; 37 | grid-gap: 40px; 38 | } 39 | `; 40 | 41 | export const GridCol = styled.div` 42 | display: flex; 43 | flex-direction: column; 44 | grid-gap: 20px; 45 | 46 | @media (min-width: ${(props) => props.theme.small_width}) { 47 | grid-gap: 30px; 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /components/Following/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div` 5 | &.filtersVisible { 6 | position: fixed; 7 | 8 | @media (min-width: ${(props) => props.theme.small_width}) { 9 | position: initial; 10 | } 11 | } 12 | `; 13 | 14 | export const ContainerExtended = styled(Container)` 15 | max-width: none; 16 | `; 17 | 18 | export const Title = styled.div` 19 | font-size: 18px; 20 | font-weight: 600; 21 | margin-bottom: 20px; 22 | 23 | @media (min-width: ${(props) => props.theme.small_width}) { 24 | font-size: 32px; 25 | margin-bottom: 30px; 26 | } 27 | `; 28 | 29 | export const SearchGrid = styled.div` 30 | display: flex; 31 | flex-direction: column; 32 | grid-gap: 40px; 33 | `; 34 | 35 | export const SectionTitle = styled.div` 36 | font-size: 20px; 37 | font-weight: 600; 38 | margin-bottom: 20px; 39 | `; 40 | -------------------------------------------------------------------------------- /components/Footer/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div` 5 | background: ${(props) => props.theme.colors.footer}; 6 | `; 7 | 8 | export const ContainerExtended = styled(Container)` 9 | color: white; 10 | 11 | a { 12 | color: white; 13 | } 14 | 15 | a:hover { 16 | color: white; 17 | } 18 | `; 19 | 20 | export const FooterGrid = styled.div` 21 | display: flex; 22 | flex-direction: column; 23 | grid-gap: 25px; 24 | 25 | @media (min-width: ${(props) => props.theme.small_width}) { 26 | grid-gap: 30px; 27 | } 28 | `; 29 | 30 | export const FooterRow = styled.div` 31 | display: flex; 32 | flex-direction: column; 33 | grid-gap: 25px; 34 | 35 | @media (min-width: ${(props) => props.theme.small_width}) { 36 | flex-direction: row; 37 | justify-content: space-between; 38 | grid-gap: 10px; 39 | } 40 | 41 | &.copyright { 42 | font-size: 13px; 43 | grid-gap: 15px; 44 | } 45 | `; 46 | 47 | export const FooterSectionGrid = styled.div` 48 | display: flex; 49 | grid-gap: 12px; 50 | font-size: 12px; 51 | 52 | @media (min-width: ${(props) => props.theme.small_width}) { 53 | grid-gap: 18px; 54 | font-size: 13px; 55 | } 56 | 57 | &.vertical { 58 | flex-direction: column; 59 | grid-gap: 10px; 60 | 61 | @media (min-width: ${(props) => props.theme.small_width}) { 62 | grid-gap: 15px; 63 | } 64 | } 65 | 66 | &.socials { 67 | grid-gap: 10px; 68 | 69 | @media (min-width: ${(props) => props.theme.small_width}) { 70 | justify-content: right; 71 | grid-gap: 15px; 72 | } 73 | } 74 | `; 75 | 76 | export const Logo = styled.div` 77 | font-size: 24px; 78 | font-weight: 700; 79 | `; 80 | 81 | export const Title = styled.div` 82 | font-size: 16px; 83 | font-weight: 600; 84 | 85 | @media (min-width: ${(props) => props.theme.small_width}) { 86 | font-size: 18px; 87 | } 88 | `; 89 | 90 | export const Description = styled.div` 91 | line-height: 22px; 92 | font-size: 14px; 93 | 94 | br { 95 | display: none; 96 | } 97 | 98 | @media (min-width: ${(props) => props.theme.small_width}) { 99 | font-size: 15px; 100 | 101 | br { 102 | display: block; 103 | } 104 | } 105 | `; 106 | 107 | export const FooterIcon = styled.div` 108 | display: flex; 109 | align-items: center; 110 | border-radius: 6px; 111 | padding: 12px; 112 | background: #333333; 113 | font-size: 15px; 114 | 115 | @media (min-width: ${(props) => props.theme.small_width}) { 116 | font-size: 18px; 117 | } 118 | 119 | &:hover { 120 | cursor: pointer; 121 | } 122 | `; 123 | 124 | export const Divider = styled.div` 125 | border: 1px solid #333333; 126 | `; 127 | -------------------------------------------------------------------------------- /components/Header/Banner/Banner.tsx: -------------------------------------------------------------------------------- 1 | import ReactMarkdown from "react-markdown"; 2 | import { BannerText, Container, ContainerBackground } from "./styles"; 3 | 4 | export const Banner = ({ scrolled, message }) => { 5 | return ( 6 | 7 | 8 | 9 | 14 | {message} 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /components/Header/Banner/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const ContainerBackground = styled.div` 4 | width: 100%; 5 | background: ${(props) => props.theme.colors.networkLight}; 6 | transition: all 0.2s; 7 | height: 50px; 8 | z-index: 9999; 9 | `; 10 | 11 | export const Container = styled.div` 12 | display: flex; 13 | max-width: ${(props) => props.theme.max_width}; 14 | margin: auto; 15 | height: 100%; 16 | align-items: center; 17 | justify-content: space-around; 18 | padding: 0 40px; 19 | `; 20 | 21 | export const BannerText = styled.div` 22 | text-align: center; 23 | font-size: 13px; 24 | font-weight: 600; 25 | color: ${(props) => props.theme.colors.network}; 26 | 27 | a { 28 | color: ${(props) => props.theme.colors.network}; 29 | text-decoration: underline; 30 | } 31 | 32 | a:hover { 33 | color: ${(props) => props.theme.colors.network}; 34 | } 35 | 36 | @media (min-width: ${(props) => props.theme.small_width}) { 37 | font-size: 15px; 38 | 39 | br { 40 | display: none; 41 | } 42 | } 43 | `; 44 | -------------------------------------------------------------------------------- /components/Home/EndingSoon/EndingSoon.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { MdOutlineArrowForward } from "react-icons/md"; 3 | import { AssetCard } from "../../AssetCard/AssetCard"; 4 | import { SectionTitle, Subtitle, Title } from "../styles"; 5 | import { CardGrid, ContainerBackground, ContainerExtended } from "./styles"; 6 | 7 | export const EndingSoon = ({ tokens }) => { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | Ending Soon 15 | 16 | 17 | 18 | 19 | 20 | View All 21 | 22 | 23 | 24 | 25 | 26 | 27 | {tokens.map((token, index) => ( 28 | 29 | ))} 30 | 31 | 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /components/Home/EndingSoon/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | padding: 20px; 8 | 9 | @media (min-width: ${(props) => props.theme.small_width}) { 10 | padding: 20px 40px 40px; 11 | } 12 | `; 13 | 14 | export const CardGrid = styled.div` 15 | display: grid; 16 | grid-gap: 12px; 17 | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); 18 | 19 | @media (min-width: ${(props) => props.theme.small_width}) { 20 | grid-template-columns: 1fr 1fr 1fr 1fr; 21 | } 22 | 23 | @media (min-width: ${(props) => props.theme.medium_width}) { 24 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr; 25 | grid-gap: 20px; 26 | } 27 | 28 | div:nth-child(9) { 29 | @media (min-width: 676px) { 30 | display: none; 31 | } 32 | 33 | @media (min-width: ${(props) => props.theme.medium_width}) { 34 | display: flex; 35 | } 36 | } 37 | 38 | div:nth-child(10) { 39 | @media (min-width: 514px) { 40 | display: none; 41 | } 42 | 43 | @media (min-width: ${(props) => props.theme.medium_width}) { 44 | display: flex; 45 | } 46 | } 47 | `; 48 | -------------------------------------------------------------------------------- /components/Home/Explore/Explore.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { 3 | ContainerBackground, 4 | ContainerExtended, 5 | ExploreButton, 6 | } from "./styles"; 7 | 8 | export const Explore = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | Explore NFTs 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /components/Home/Explore/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | padding: 0 20px 40px; 8 | 9 | @media (min-width: ${(props) => props.theme.small_width}) { 10 | padding: 20px 40px 60px; 11 | } 12 | `; 13 | 14 | export const ExploreButton = styled.div` 15 | width: fit-content; 16 | margin: auto; 17 | padding: 12px 30px; 18 | font-weight: 600; 19 | font-size: 16px; 20 | text-align: center; 21 | background: ${(props) => props.theme.colors.network}; 22 | color: ${(props) => props.theme.colors.secondary}; 23 | border-radius: 52px; 24 | transition: all 0.2s; 25 | 26 | @media (min-width: ${(props) => props.theme.small_width}) { 27 | padding: 15px 50px; 28 | margin: 30px 0; 29 | margin: auto; 30 | } 31 | 32 | &:hover { 33 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.1), 34 | -5px -5px 15px rgba(0, 0, 0, 0.1); 35 | cursor: pointer; 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /components/Home/FeaturedAssets/FeaturedAssets.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { AssetCard } from "../../AssetCard/AssetCard"; 3 | import { SectionTitle, Title } from "../styles"; 4 | import { CardGrid, ContainerBackground, ContainerExtended } from "./styles"; 5 | 6 | export const FeaturedAssets = ({ tokens }) => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | Trending NFTs 14 | 15 | 16 | 17 | 18 | 19 | {tokens.map((token, index) => ( 20 | 21 | ))} 22 | 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /components/Home/FeaturedAssets/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)``; 7 | 8 | export const CardGrid = styled.div` 9 | display: grid; 10 | grid-gap: 12px; 11 | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); 12 | 13 | @media (min-width: ${(props) => props.theme.small_width}) { 14 | grid-template-columns: 1fr 1fr 1fr 1fr; 15 | } 16 | 17 | @media (min-width: ${(props) => props.theme.medium_width}) { 18 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr; 19 | grid-gap: 20px; 20 | } 21 | 22 | div:nth-child(9) { 23 | @media (min-width: 676px) { 24 | display: none; 25 | } 26 | 27 | @media (min-width: ${(props) => props.theme.medium_width}) { 28 | display: flex; 29 | } 30 | } 31 | 32 | div:nth-child(10) { 33 | @media (min-width: 514px) { 34 | display: none; 35 | } 36 | 37 | @media (min-width: ${(props) => props.theme.medium_width}) { 38 | display: flex; 39 | } 40 | } 41 | `; 42 | -------------------------------------------------------------------------------- /components/Home/FeaturedCollections/FeaturedCollections.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { MdOutlineArrowForward } from "react-icons/md"; 3 | import { CollectionCardLarge } from "../../CollectionCard/CollectionCardLarge"; 4 | import { SectionTitle, Subtitle, Title } from "../styles"; 5 | import { 6 | CardContainer, 7 | CardGrid, 8 | ContainerBackground, 9 | ContainerExtended, 10 | } from "./styles"; 11 | 12 | export const FeaturedCollections = ({ collections }) => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | Featured Projects 20 | 21 | 22 | 23 | 24 | 25 | View All 26 | 27 | 28 | 29 | 30 | 31 | 32 | {collections.map((collection) => ( 33 | 34 | 38 | 39 | ))} 40 | 41 | 42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /components/Home/FeaturedCollections/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | @media (min-width: ${(props) => props.theme.small_width}) { 8 | padding: 50px 40px; 9 | } 10 | `; 11 | 12 | export const CardContainer = styled.div``; 13 | 14 | export const CardGrid = styled.div` 15 | display: grid; 16 | grid-gap: 15px; 17 | grid-template-columns: repeat(auto-fill, minmax(325px, 1fr)); 18 | 19 | @media (min-width: ${(props) => props.theme.medium_width}) { 20 | grid-gap: 20px; 21 | grid-template-columns: repeat(auto-fill, minmax(375px, 1fr)); 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /components/Home/GasTracker/GasTracker.tsx: -------------------------------------------------------------------------------- 1 | import CountUp from "react-countup"; 2 | import VisibilitySensor from "react-visibility-sensor"; 3 | import "slick-carousel/slick/slick-theme.css"; 4 | import "slick-carousel/slick/slick.css"; 5 | import { siteConfig } from "../../../shared/config"; 6 | import { 7 | ContainerBackground, 8 | ContainerExtended, 9 | GasContainer, 10 | GasSubtitle, 11 | GasTitle, 12 | } from "./styles"; 13 | 14 | export const GasTracker = () => { 15 | const today = new Date(); 16 | const prevDate = new Date("09-28-2022"); 17 | const daysDiff = (today.getTime() - prevDate.getTime()) / (1000 * 3600 * 24); 18 | const numTransactions = 147446 + daysDiff * 1750; 19 | const osFee = 0.014; 20 | const quixFee = 0.0005; 21 | const avgEthPrice = 2128; 22 | const total = (osFee - quixFee) * numTransactions * avgEthPrice; 23 | 24 | return ( 25 | 26 | 27 | 28 | 29 | {({ isVisible }) => ( 30 | 31 | {isVisible ? ( 32 | 39 | ) : ( 40 | "$500,000+" 41 | )} 42 | 43 | )} 44 | 45 | 46 | ⛽️ gas fee savings 47 | 48 | 49 | 50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /components/Home/GasTracker/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | padding: 0 20px 40px; 8 | 9 | @media (min-width: ${(props) => props.theme.small_width}) { 10 | padding: 20px 40px 40px; 11 | } 12 | `; 13 | 14 | export const GasContainer = styled.div` 15 | color: ${(props) => props.theme.colors.primary}; 16 | text-align: center; 17 | `; 18 | 19 | export const GasTitle = styled.div` 20 | font-size: 42px; 21 | font-weight: 800; 22 | margin-bottom: 10px; 23 | 24 | @media (min-width: ${(props) => props.theme.small_width}) { 25 | font-size: 64px; 26 | margin-bottom: 15px; 27 | } 28 | `; 29 | 30 | export const GasSubtitle = styled.div` 31 | font-size: 16px; 32 | font-weight: 500; 33 | 34 | @media (min-width: ${(props) => props.theme.small_width}) { 35 | font-size: 20px; 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /components/Home/Hero/Hero.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { MdVerified } from "react-icons/md"; 3 | import { siteConfig } from "../../../shared/config"; 4 | import { CollectionImage } from "../../Common/Images/CollectionImage"; 5 | import { TextTruncater } from "../../Common/styles"; 6 | import { 7 | AssetName, 8 | Button, 9 | CardContent, 10 | CardImage, 11 | CollectionIcon, 12 | ContainerBackground, 13 | ContainerExtended, 14 | Grid, 15 | ImageContainer, 16 | Subtitle, 17 | TextContainer, 18 | Title, 19 | } from "./styles"; 20 | 21 | export const Hero = ({ collection }) => { 22 | return ( 23 |
24 | 25 | 26 | 27 | 28 | 29 | LAYER 2 <br /> 30 | IS HERE 31 | 32 | 33 | Discover, collect, and sell digital items
34 | on Optimism's largest NFT marketplace 35 |
36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | {collection && collection.image_url && ( 44 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {collection.name} 58 | {collection.verified && ( 59 | 60 | 61 | 62 | )} 63 | 64 | 65 | 66 | 67 | 68 | )} 69 |
70 |
71 |
72 |
73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /components/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "../../shared/config"; 2 | import { Explore } from "./Explore/Explore"; 3 | import { FeaturedAssets } from "./FeaturedAssets/FeaturedAssets"; 4 | import { FeaturedCollections } from "./FeaturedCollections/FeaturedCollections"; 5 | import { GasTracker } from "./GasTracker/GasTracker"; 6 | import { Hero } from "./Hero/Hero"; 7 | import { Mirror } from "./Mirror/Mirror"; 8 | import { Arbitrum } from "./Network/Arbitrum"; 9 | import { Optimism } from "./Network/Optimism"; 10 | import { OptimismNFTs } from "./OptimismNFTs/OptimismNFTs"; 11 | import { Quixotic } from "./Quixotic/Quixotic"; 12 | import { Trending } from "./Trending/Trending"; 13 | 14 | export const Home = ({ featured, collections }) => { 15 | return ( 16 | <> 17 | 18 | {featured && featured.collections.length > 0 && ( 19 | 20 | )} 21 | 22 | {featured && featured.mirror && featured.mirror.length > 0 && ( 23 | 24 | )} 25 | 26 | {collections && collections.results.length > 0 && ( 27 | 28 | )} 29 | 30 | {featured && featured.opog && featured.opog.length > 0 && ( 31 | 32 | )} 33 | 34 | 35 | 36 | {featured && featured.tokens.length > 0 && ( 37 | 38 | )} 39 | 40 | 41 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /components/Home/Mirror/Mirror.tsx: -------------------------------------------------------------------------------- 1 | import { CardContainer } from "../FeaturedCollections/styles"; 2 | import { MirrorCard } from "./MirrorCard"; 3 | import { 4 | AboutDescription, 5 | CardGrid, 6 | ContainerBackground, 7 | ContainerExtended, 8 | Title, 9 | TitleSection, 10 | } from "./styles"; 11 | 12 | export const Mirror = ({ collections }) => { 13 | return ( 14 | 15 | 16 | 17 | Mirror Writing NFTs 18 | 19 | Explore and collect the best writing in web3 20 | 21 | 22 | 23 | {collections.slice(0, 4).map((collection) => ( 24 | 25 | 26 | 27 | ))} 28 | 29 | 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /components/Home/Mirror/MirrorCard.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { CollectionImage } from "../../Common/Images/CollectionImage"; 3 | import { Card } from "./styles"; 4 | 5 | export const MirrorCard = ({ collection }) => { 6 | return ( 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /components/Home/Mirror/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div` 5 | background: linear-gradient(#007aff00, #007aff20, #007aff00); 6 | `; 7 | 8 | export const ContainerExtended = styled(Container)` 9 | @media (min-width: ${(props) => props.theme.small_width}) { 10 | margin-bottom: 40px; 11 | } 12 | `; 13 | 14 | export const TitleSection = styled.div` 15 | color: ${(props) => props.theme.colors.primary}; 16 | margin: 0 0 30px; 17 | 18 | @media (min-width: ${(props) => props.theme.small_width}) { 19 | margin: 10px 0 40px; 20 | } 21 | `; 22 | 23 | export const Title = styled.div` 24 | position: relative; 25 | display: flex; 26 | justify-content: space-around; 27 | text-align: center; 28 | font-size: 30px; 29 | font-weight: 600; 30 | margin-bottom: 10px; 31 | 32 | @media (min-width: ${(props) => props.theme.small_width}) { 33 | font-size: 42px; 34 | } 35 | 36 | @media (min-width: ${(props) => props.theme.medium_width}) { 37 | font-size: 54px; 38 | } 39 | `; 40 | 41 | export const AboutDescription = styled.div` 42 | color: ${(props) => props.theme.colors.accent}; 43 | text-align: center; 44 | font-size: 15px; 45 | 46 | @media (min-width: ${(props) => props.theme.small_width}) { 47 | font-size: 18px; 48 | } 49 | 50 | @media (min-width: ${(props) => props.theme.medium_width}) { 51 | font-size: 20px; 52 | } 53 | `; 54 | 55 | export const CardGrid = styled.div` 56 | display: grid; 57 | grid-gap: 15px; 58 | grid-template-columns: 1fr 1fr; 59 | 60 | @media (min-width: ${(props) => props.theme.small_width}) { 61 | grid-template-columns: 1fr 1fr 1fr 1fr; 62 | } 63 | 64 | @media (min-width: ${(props) => props.theme.max_width}) { 65 | grid-gap: 20px; 66 | grid-template-columns: 1fr 1fr 1fr 1fr; 67 | } 68 | `; 69 | 70 | export const Card = styled.div` 71 | height: 100%; 72 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.05), 73 | -5px -5px 15px rgba(0, 0, 0, 0.05); 74 | background: ${(props) => props.theme.colors.secondary}; 75 | border-radius: 12px; 76 | overflow: hidden; 77 | transition: all 0.2s; 78 | color: ${(props) => props.theme.colors.primary}; 79 | 80 | @media (min-width: ${(props) => props.theme.small_width}) { 81 | min-width: auto; 82 | 83 | &:hover { 84 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 85 | 5px 5px 20px rgba(0, 0, 0, 0.1), -5px -5px 20px rgba(0, 0, 0, 0.1); 86 | cursor: pointer; 87 | } 88 | } 89 | `; 90 | -------------------------------------------------------------------------------- /components/Home/Network/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div` 5 | background: ${(props) => 6 | `linear-gradient(${props.theme.colors.networkLight}00, ${props.theme.colors.networkLight}, ${props.theme.colors.networkLight}00)`}; 7 | `; 8 | 9 | export const ContainerExtended = styled(Container)` 10 | @media (min-width: ${(props) => props.theme.small_width}) { 11 | padding: 60px 40px; 12 | margin-bottom: 20px; 13 | } 14 | `; 15 | 16 | export const TitleSection = styled.div` 17 | color: ${(props) => props.theme.colors.primary}; 18 | margin: 0 0 30px; 19 | 20 | @media (min-width: ${(props) => props.theme.small_width}) { 21 | margin: 10px 0 40px; 22 | } 23 | `; 24 | 25 | export const Title = styled.div` 26 | position: relative; 27 | display: flex; 28 | justify-content: space-around; 29 | text-align: center; 30 | font-size: 34px; 31 | font-weight: 600; 32 | margin-bottom: 10px; 33 | 34 | @media (min-width: ${(props) => props.theme.small_width}) { 35 | font-size: 48px; 36 | } 37 | 38 | @media (min-width: ${(props) => props.theme.medium_width}) { 39 | font-size: 56px; 40 | } 41 | `; 42 | 43 | export const AboutDescription = styled.div` 44 | color: ${(props) => props.theme.colors.accent}; 45 | text-align: center; 46 | font-size: 16px; 47 | 48 | @media (min-width: ${(props) => props.theme.small_width}) { 49 | font-size: 18px; 50 | } 51 | 52 | @media (min-width: ${(props) => props.theme.medium_width}) { 53 | font-size: 22px; 54 | } 55 | `; 56 | -------------------------------------------------------------------------------- /components/Home/OptimismNFTs/OptimismNFTs.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { MdOutlineArrowForward } from "react-icons/md"; 3 | import { CollectionCardLarge } from "../../CollectionCard/CollectionCardLarge"; 4 | import { SectionTitle, Subtitle, Title } from "../styles"; 5 | import { 6 | CardContainer, 7 | CardGrid, 8 | ContainerBackground, 9 | ContainerExtended, 10 | } from "./styles"; 11 | 12 | export const OptimismNFTs = ({ collections }) => { 13 | return ( 14 | 15 | 16 | 17 | OP OG Collection 18 | 19 | 20 | 21 | {collections.map((collection) => ( 22 | 23 | 27 | 28 | ))} 29 | 30 | 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /components/Home/OptimismNFTs/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | @media (min-width: ${(props) => props.theme.small_width}) { 8 | padding: 50px 40px; 9 | } 10 | `; 11 | 12 | export const CardContainer = styled.div``; 13 | 14 | export const CardGrid = styled.div` 15 | display: grid; 16 | grid-gap: 15px; 17 | grid-template-columns: repeat(auto-fill, minmax(325px, 1fr)); 18 | 19 | @media (min-width: ${(props) => props.theme.medium_width}) { 20 | grid-gap: 20px; 21 | grid-template-columns: repeat(auto-fill, minmax(375px, 1fr)); 22 | } 23 | `; 24 | -------------------------------------------------------------------------------- /components/Home/Quixotic/Quixotic.tsx: -------------------------------------------------------------------------------- 1 | import { BsLightningChargeFill } from "react-icons/bs"; 2 | import { FaEthereum } from "react-icons/fa"; 3 | import { IoLockClosed } from "react-icons/io5"; 4 | import { siteConfig } from "../../../shared/config"; 5 | import { 6 | AboutDescription, 7 | AboutGrid, 8 | AboutItem, 9 | AboutItemGrid, 10 | AboutTitle, 11 | AboutTitleGrid, 12 | ContainerBackground, 13 | ContainerExtended, 14 | ItemDescription, 15 | ItemIcon, 16 | ItemTitle, 17 | } from "./styles"; 18 | 19 | export const Quixotic = () => { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | This is 27 |
28 | Quix 29 |
30 | Built for layer 2 31 |
32 | 33 | 34 | 35 | 36 | 37 | Scalable 38 | 39 | Experience the next chapter of Ethereum on Quix, the largest NFT 40 | marketplace on Optimism 41 | 42 | 43 | 44 | 45 | 46 | 47 | Fast 48 | 49 | Transact in seconds and save up to 100x on gas fees with the 50 | Ethereum you know and love 51 | 52 | 53 | 54 | 55 | 56 | 57 | Secure 58 | 59 | Inherit the security of Ethereum, the most decentralized smart 60 | contract platform in the world 61 | 62 | 63 | 64 |
65 |
66 |
67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /components/Home/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const SectionTitle = styled.div` 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | grid-gap: 20px; 8 | margin-bottom: 20px; 9 | 10 | @media (min-width: ${(props) => props.theme.small_width}) { 11 | margin-bottom: 25px; 12 | } 13 | `; 14 | 15 | export const Title = styled.div` 16 | font-size: 18px; 17 | font-weight: 600; 18 | white-space: nowrap; 19 | overflow: hidden; 20 | text-overflow: ellipsis; 21 | color: ${(props) => props.theme.colors.primary}; 22 | 23 | @media (min-width: ${(props) => props.theme.small_width}) { 24 | font-size: 22px; 25 | font-weight: 700; 26 | } 27 | `; 28 | 29 | export const Subtitle = styled.div` 30 | display: flex; 31 | align-items: center; 32 | grid-gap: 5px; 33 | font-size: 14px; 34 | font-weight: 400; 35 | width: fit-content; 36 | white-space: nowrap; 37 | 38 | &:hover { 39 | cursor: pointer; 40 | } 41 | 42 | @media (min-width: ${(props) => props.theme.small_width}) { 43 | font-size: 16px; 44 | } 45 | `; 46 | -------------------------------------------------------------------------------- /components/Launch/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)``; 7 | 8 | export const Title = styled.div` 9 | text-align: center; 10 | font-size: 32px; 11 | font-weight: 600; 12 | margin-top: 15px; 13 | margin-bottom: 8px; 14 | 15 | @media (min-width: ${(props) => props.theme.small_width}) { 16 | font-size: 48px; 17 | margin-bottom: 10px; 18 | } 19 | `; 20 | 21 | export const Subtitle = styled.div` 22 | text-align: center; 23 | color: ${(props) => props.theme.colors.accent}; 24 | margin-bottom: 40px; 25 | 26 | @media (min-width: ${(props) => props.theme.small_width}) { 27 | margin-bottom: 60px; 28 | font-size: 22px; 29 | } 30 | `; 31 | 32 | export const SectionTitle = styled.div` 33 | text-align: center; 34 | font-weight: 500; 35 | font-size: 18px; 36 | margin-bottom: 15px; 37 | 38 | @media (min-width: ${(props) => props.theme.small_width}) { 39 | font-weight: 800; 40 | font-size: 22px; 41 | margin-bottom: 30px; 42 | } 43 | 44 | @media (min-width: ${(props) => props.theme.medium_width}) { 45 | font-size: 24px; 46 | } 47 | `; 48 | 49 | export const SectionSubtitle = styled.span` 50 | color: ${(props) => props.theme.colors.accent}; 51 | font-weight: 500; 52 | `; 53 | 54 | export const SectionGrid = styled.div` 55 | display: flex; 56 | flex-direction: column; 57 | grid-gap: 25px; 58 | 59 | @media (min-width: ${(props) => props.theme.small_width}) { 60 | grid-gap: 50px; 61 | } 62 | 63 | @media (min-width: ${(props) => props.theme.max_width}) { 64 | grid-gap: 75px; 65 | } 66 | `; 67 | 68 | export const CollectionsGrid = styled.div` 69 | display: grid; 70 | grid-template-columns: 1fr; 71 | grid-gap: 12px; 72 | 73 | @media (min-width: ${(props) => props.theme.small_width}) { 74 | grid-template-columns: 1fr 1fr; 75 | } 76 | 77 | @media (min-width: ${(props) => props.theme.max_width}) { 78 | grid-template-columns: 1fr 1fr 1fr; 79 | } 80 | 81 | .infinite-scroll-component__outerdiv { 82 | display: contents; 83 | } 84 | `; 85 | -------------------------------------------------------------------------------- /components/Launchpad/MintButton.tsx: -------------------------------------------------------------------------------- 1 | import { FaWallet } from "react-icons/fa"; 2 | import { useDispatch } from "react-redux"; 3 | import { updateLogin } from "../../store/login"; 4 | import { Button, ButtonText } from "./styles"; 5 | 6 | export const MintButton = ({ 7 | collection, 8 | hostedCollection, 9 | greenlistAccess, 10 | address, 11 | toggleModal, 12 | }) => { 13 | const dispatch = useDispatch(); 14 | 15 | if (collection.supply == hostedCollection.max_supply) { 16 | return ( 17 | 20 | ); 21 | } 22 | 23 | if (hostedCollection.mint_enabled) { 24 | if (address) { 25 | return ( 26 | 31 | ); 32 | } else { 33 | return ( 34 | 43 | ); 44 | } 45 | } 46 | 47 | if (hostedCollection.premint_enabled) { 48 | if (address) { 49 | if (greenlistAccess) { 50 | return ( 51 | 56 | ); 57 | } else { 58 | return ( 59 | 62 | ); 63 | } 64 | } else { 65 | return ( 66 | 75 | ); 76 | } 77 | } 78 | 79 | return ( 80 | 83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /components/List/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | /* max-width: none; */ 8 | max-width: 850px; 9 | `; 10 | 11 | export const Grid = styled.div` 12 | display: flex; 13 | flex-direction: column; 14 | grid-gap: 40px; 15 | overflow: scroll; 16 | `; 17 | 18 | export const Title = styled.div` 19 | font-size: 24px; 20 | font-weight: 600; 21 | margin-bottom: 20px; 22 | 23 | @media (min-width: ${(props) => props.theme.small_width}) { 24 | font-size: 32px; 25 | margin-bottom: 25px; 26 | } 27 | `; 28 | 29 | export const BuyOrdersGrid = styled.div` 30 | display: grid; 31 | grid-template-columns: 200px 1fr 1fr 1fr 100px; 32 | grid-gap: 15px 20px; 33 | 34 | @media (min-width: ${(props) => props.theme.small_width}) { 35 | grid-template-columns: 250px 1fr 1fr 1fr 1fr; 36 | grid-gap: 20px 30px; 37 | } 38 | `; 39 | 40 | export const BuyOrdersRow = styled.div` 41 | display: contents; 42 | align-items: center; 43 | border-radius: 8px; 44 | background: ${(props) => props.theme.colors.secondary}; 45 | 46 | &.title { 47 | padding: 0 0 5px; 48 | } 49 | `; 50 | 51 | export const BuyOrdersText = styled.div` 52 | display: flex; 53 | grid-gap: 3px; 54 | align-items: center; 55 | font-weight: 400; 56 | font-size: 14px; 57 | color: ${(props) => props.theme.colors.primary}; 58 | flex-shrink: 0; 59 | white-space: nowrap; 60 | 61 | @media (min-width: ${(props) => props.theme.medium_width}) { 62 | font-size: 15px; 63 | } 64 | 65 | a { 66 | color: ${(props) => props.theme.colors.network}; 67 | } 68 | 69 | &.item { 70 | grid-gap: 10px; 71 | } 72 | 73 | &.title { 74 | font-size: 14px; 75 | font-weight: 800; 76 | 77 | @media (min-width: ${(props) => props.theme.medium_width}) { 78 | font-size: 15px; 79 | } 80 | } 81 | 82 | &.button { 83 | /* margin-left: auto; */ 84 | } 85 | `; 86 | -------------------------------------------------------------------------------- /components/Loader/Loader.tsx: -------------------------------------------------------------------------------- 1 | import { ContainerExtended, LoadingRing } from "./styles"; 2 | 3 | export const Loader = () => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /components/Loader/styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerExtended = styled(Container)``; 5 | 6 | const LoadingRingKeyframes = keyframes` 7 | 0% { transform: rotate(0deg); } 8 | 100% { transform: rotate(360deg); } 9 | `; 10 | 11 | export const LoadingRing = styled.div` 12 | border: 4px solid ${(props) => props.theme.colors.lightGray}; 13 | border-top: 4px solid ${(props) => props.theme.colors.network}; 14 | border-radius: 50%; 15 | width: 50px; 16 | height: 50px; 17 | margin: 150px auto; 18 | animation: ${LoadingRingKeyframes} 2s linear infinite; 19 | `; 20 | -------------------------------------------------------------------------------- /components/LoginModal/styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from "styled-components"; 2 | import Modal from "styled-react-modal"; 3 | 4 | export const StyledModal = Modal.styled` 5 | position: relative; 6 | width: 90%; 7 | max-width: 350px; 8 | height: 360px; 9 | overflow: auto; 10 | background: ${(props) => props.theme.colors.secondary}; 11 | opacity: ${(props) => props.opacity}; 12 | border-radius: 18px; 13 | z-index: 9999; 14 | margin-top: -12%; 15 | 16 | @media (min-width: ${(props) => props.theme.small_width}) { 17 | width: 360px; 18 | height: 360px; 19 | max-height: 90%; 20 | } 21 | `; 22 | 23 | export const ContentContainer = styled.div` 24 | display: flex; 25 | grid-gap: 15px; 26 | align-items: center; 27 | flex-direction: column; 28 | justify-content: space-between; 29 | padding: 30px 30px; 30 | text-align: center; 31 | height: 100%; 32 | `; 33 | 34 | export const ModalSection = styled.div` 35 | display: flex; 36 | flex-direction: column; 37 | grid-gap: 6px; 38 | width: 100%; 39 | 40 | &.wallets { 41 | grid-gap: 10px; 42 | } 43 | `; 44 | 45 | export const LargeText = styled.div` 46 | font-size: 24px; 47 | font-weight: 800; 48 | `; 49 | 50 | export const SmallText = styled.div` 51 | font-weight: 400; 52 | font-size: 14px; 53 | color: ${(props) => props.theme.colors.accent}; 54 | `; 55 | 56 | export const Button = styled.div` 57 | border: 1px solid ${(props) => props.theme.colors.primary}; 58 | border-radius: 52px; 59 | padding: 10px 20px; 60 | width: 100%; 61 | transition: all 0.2s; 62 | background: ${(props) => props.theme.colors.primary}; 63 | color: ${(props) => props.theme.colors.secondary}; 64 | 65 | &:hover { 66 | cursor: pointer; 67 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.1), 68 | -5px -5px 15px rgba(0, 0, 0, 0.1); 69 | } 70 | `; 71 | 72 | export const LoginButton = styled(Button)` 73 | /* padding: 5px 20px; */ 74 | height: 48px; 75 | padding: 0 15px; 76 | font-weight: 600; 77 | font-size: 15px; 78 | `; 79 | 80 | export const ButtonLogo = styled.div` 81 | height: 35px; 82 | width: 35px; 83 | position: relative; 84 | 85 | &.coinbase { 86 | height: 30px; 87 | width: 30px; 88 | } 89 | `; 90 | 91 | export const LoginButtonContent = styled.div` 92 | display: flex; 93 | align-items: center; 94 | justify-content: space-between; 95 | height: 100%; 96 | `; 97 | 98 | export const CancelButton = styled(Button)` 99 | margin-top: 20px; 100 | `; 101 | 102 | const LoadingRingKeyframes = keyframes` 103 | 0% { transform: rotate(0deg); } 104 | 100% { transform: rotate(360deg); } 105 | `; 106 | 107 | export const LoadingRing = styled.div` 108 | border: 4px solid ${(props) => props.theme.colors.lightGray}; 109 | border-top: 4px solid ${(props) => props.theme.colors.network}; 110 | border-radius: 50%; 111 | width: 50px; 112 | height: 50px; 113 | margin: 20px auto; 114 | animation: ${LoadingRingKeyframes} 2s linear infinite; 115 | `; 116 | -------------------------------------------------------------------------------- /components/Maintenance/Maintenance.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { siteConfig } from "../../shared/config"; 3 | import { 4 | ContainerBackground, 5 | ContainerExtended, 6 | ImageContainer, 7 | Subtitle, 8 | Title, 9 | } from "./styles"; 10 | 11 | export const Maintenance = () => { 12 | return ( 13 | 14 | 15 | 16 | 26 | 27 | Maintenance Mode 28 | 29 | Quix is currently undergoing maintenance. During this time our 30 | marketplace will be temporarily unavailable. We apologize for the 31 | inconvenience. 32 | 33 | 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /components/Maintenance/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | max-width: 800px; 8 | margin: auto; 9 | margin-top: 15vh; 10 | `; 11 | 12 | export const Title = styled.div` 13 | font-size: 26px; 14 | font-weight: 800; 15 | margin-bottom: 15px; 16 | 17 | @media (min-width: ${(props) => props.theme.small_width}) { 18 | font-size: 32px; 19 | margin-bottom: 20px; 20 | } 21 | `; 22 | 23 | export const Subtitle = styled.div` 24 | margin-bottom: 20px; 25 | color: ${(props) => props.theme.colors.accent}; 26 | font-size: 15px; 27 | line-height: 1.5; 28 | 29 | @media (min-width: ${(props) => props.theme.small_width}) { 30 | margin-bottom: 40px; 31 | font-size: 18px; 32 | } 33 | `; 34 | 35 | export const ImageContainer = styled.div` 36 | margin-bottom: 30px; 37 | border-radius: 8px; 38 | overflow: hidden; 39 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.05), 40 | -5px -5px 15px rgba(0, 0, 0, 0.05); 41 | `; 42 | -------------------------------------------------------------------------------- /components/MyCollections/MyCollections.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { useState } from "react"; 3 | import InfiniteScroll from "react-infinite-scroll-component"; 4 | import { fetchMoreByURL } from "../../api/general"; 5 | import { siteConfig } from "../../shared/config"; 6 | import { CollectionCardGhost } from "../CollectionCard/CollectionCardGhost"; 7 | import { CollectionCardLarge } from "../CollectionCard/CollectionCardLarge"; 8 | import { CollectionsGrid } from "../CollectionCard/styles"; 9 | import { NoItems } from "../Common/styles"; 10 | import { 11 | ContainerBackground, 12 | ContainerExtended, 13 | NoItemsButton, 14 | Subtitle, 15 | Title, 16 | } from "./styles"; 17 | 18 | export const MyCollections = ({ collections, setCollections }) => { 19 | const [moreCollections, setMoreCollections] = useState( 20 | collections.next ? true : false 21 | ); 22 | 23 | const [collectionResults, setCollectionResults] = useState( 24 | collections.results 25 | ); 26 | 27 | async function fetchMoreCollections() { 28 | const collectionsRes = await fetchMoreByURL(collections.next); 29 | 30 | if (!collectionsRes.next) { 31 | setMoreCollections(false); 32 | } 33 | 34 | setCollections(collectionsRes); 35 | setCollectionResults(collectionResults.concat(collectionsRes.results)); 36 | } 37 | 38 | return ( 39 | 40 | 41 | Collections 42 | 43 | {collectionResults.length > 0 ? ( 44 | <>Manage your Optimism NFT collections on Quix 45 | ) : ( 46 | 47 |

48 | Get started by creating your first NFT collection on Optimism 49 |

50 | 51 | 52 | Get Started 53 | 54 | 55 |
56 | )} 57 |
58 | 59 | 60 | ( 65 | 66 | ))} 67 | style={{ display: "contents", overflow: "visible" }} 68 | > 69 | {collectionResults.map((collection) => ( 70 | 75 | ))} 76 | 77 | 78 |
79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /components/MyCollections/styles.ts: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)``; 7 | 8 | export const Title = styled.div` 9 | font-size: 26px; 10 | font-weight: 600; 11 | margin-bottom: 10px; 12 | 13 | @media (min-width: ${(props) => props.theme.small_width}) { 14 | font-size: 32px; 15 | } 16 | `; 17 | 18 | export const Subtitle = styled.div` 19 | margin-bottom: 20px; 20 | 21 | @media (min-width: ${(props) => props.theme.small_width}) { 22 | margin-bottom: 30px; 23 | } 24 | `; 25 | 26 | export const NoItemsButton = styled.div` 27 | margin-top: 30px; 28 | margin-bottom: 30px; 29 | background: ${(props) => props.theme.colors.primary}; 30 | color: ${(props) => props.theme.colors.secondary}; 31 | border-radius: 52px; 32 | padding: 10px 30px; 33 | text-align: center; 34 | font-size: 0.95rem; 35 | font-weight: 600; 36 | 37 | @media (min-width: ${(props) => props.theme.small_width}) { 38 | width: fit-content; 39 | } 40 | 41 | &:hover { 42 | cursor: pointer; 43 | } 44 | 45 | &.no-click { 46 | cursor: default; 47 | } 48 | `; 49 | 50 | const LoadingRingKeyframes = keyframes` 51 | 0% { transform: rotate(0deg); } 52 | 100% { transform: rotate(360deg); } 53 | `; 54 | 55 | export const LoadingRing = styled.div` 56 | border: 4px solid ${(props) => props.theme.colors.lightGray}; 57 | border-top: 4px solid ${(props) => props.theme.colors.network}; 58 | border-radius: 50%; 59 | width: 50px; 60 | height: 50px; 61 | margin: 20px auto; 62 | margin-top: 60px; 63 | animation: ${LoadingRingKeyframes} 2s linear infinite; 64 | `; 65 | -------------------------------------------------------------------------------- /components/NotFound/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { 3 | ContainerBackground, 4 | ContainerExtended, 5 | NoItemsButton, 6 | Subtitle, 7 | Title, 8 | } from "./styles"; 9 | 10 | export const NotFound = () => { 11 | return ( 12 | 13 | 14 | 404 Not Found 15 | We couldn't find the page you were looking for 16 | 17 | 18 | Back to home 19 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /components/NotFound/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | text-align: center; 8 | margin-top: 10%; 9 | `; 10 | 11 | export const Title = styled.div` 12 | font-size: 32px; 13 | font-weight: 600; 14 | margin-bottom: 15px; 15 | 16 | @media (min-width: ${(props) => props.theme.small_width}) { 17 | font-size: 42px; 18 | margin-bottom: 20px; 19 | } 20 | `; 21 | 22 | export const Subtitle = styled.div` 23 | margin-bottom: 20px; 24 | line-height: 22px; 25 | font-size: 14px; 26 | 27 | @media (min-width: ${(props) => props.theme.small_width}) { 28 | margin-bottom: 40px; 29 | font-size: 18px; 30 | line-height: 24px; 31 | } 32 | `; 33 | 34 | export const NoItemsButton = styled.div` 35 | margin: 30px auto; 36 | background: ${(props) => props.theme.colors.primary}; 37 | color: ${(props) => props.theme.colors.secondary}; 38 | border-radius: 52px; 39 | padding: 12px 20px; 40 | text-align: center; 41 | font-weight: 600; 42 | 43 | @media (min-width: ${(props) => props.theme.small_width}) { 44 | width: fit-content; 45 | padding: 12px 40px; 46 | } 47 | 48 | &:hover { 49 | cursor: pointer; 50 | } 51 | 52 | &.no-click { 53 | cursor: default; 54 | } 55 | `; 56 | -------------------------------------------------------------------------------- /components/NotLoggedIn/NotLoggedIn.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | import { useDispatch } from "react-redux"; 4 | import { updateLogin } from "../../store/login"; 5 | import { 6 | ContainerBackground, 7 | ContainerExtended, 8 | Title, 9 | Subtitle, 10 | NoItemsButton, 11 | } from "./styles"; 12 | 13 | export const NotLoggedIn = () => { 14 | const dispatch = useDispatch(); 15 | 16 | return ( 17 | 18 | 19 | Connect Wallet 20 | Please connect your wallet to view this page 21 | { 23 | updateLogin(true, dispatch); 24 | }} 25 | > 26 | Connect 27 | 28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /components/NotLoggedIn/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | text-align: center; 8 | margin-top: 10%; 9 | `; 10 | 11 | export const Title = styled.div` 12 | font-size: 32px; 13 | font-weight: 600; 14 | margin-bottom: 15px; 15 | 16 | @media (min-width: ${(props) => props.theme.small_width}) { 17 | font-size: 42px; 18 | margin-bottom: 20px; 19 | } 20 | `; 21 | 22 | export const Subtitle = styled.div` 23 | margin-bottom: 20px; 24 | line-height: 22px; 25 | font-size: 14px; 26 | 27 | @media (min-width: ${(props) => props.theme.small_width}) { 28 | margin-bottom: 40px; 29 | font-size: 18px; 30 | line-height: 24px; 31 | } 32 | `; 33 | 34 | export const NoItemsButton = styled.div` 35 | margin: 30px auto; 36 | background: ${(props) => props.theme.colors.primary}; 37 | color: ${(props) => props.theme.colors.secondary}; 38 | border-radius: 52px; 39 | padding: 12px 20px; 40 | text-align: center; 41 | font-weight: 600; 42 | 43 | @media (min-width: ${(props) => props.theme.small_width}) { 44 | width: fit-content; 45 | padding: 12px 40px; 46 | } 47 | 48 | &:hover { 49 | cursor: pointer; 50 | } 51 | 52 | &.no-click { 53 | cursor: default; 54 | } 55 | `; 56 | -------------------------------------------------------------------------------- /components/Offers/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | /* max-width: none; */ 8 | `; 9 | 10 | export const Grid = styled.div` 11 | display: flex; 12 | flex-direction: column; 13 | grid-gap: 40px; 14 | overflow: scroll; 15 | `; 16 | 17 | export const Title = styled.div` 18 | font-size: 24px; 19 | font-weight: 600; 20 | margin-bottom: 20px; 21 | 22 | @media (min-width: ${(props) => props.theme.small_width}) { 23 | font-size: 32px; 24 | margin-bottom: 25px; 25 | } 26 | `; 27 | 28 | export const BuyOrdersGrid = styled.div` 29 | display: grid; 30 | grid-template-columns: 200px 1fr 1fr 1fr 1fr 1fr 1fr 100px; 31 | grid-gap: 15px 20px; 32 | 33 | @media (min-width: ${(props) => props.theme.small_width}) { 34 | grid-template-columns: 250px 1fr 1fr 1fr 1fr 1fr 1fr 100px; 35 | grid-gap: 20px 30px; 36 | } 37 | `; 38 | 39 | export const BuyOrdersRow = styled.div` 40 | display: contents; 41 | align-items: center; 42 | border-radius: 8px; 43 | background: ${(props) => props.theme.colors.secondary}; 44 | 45 | &.title { 46 | padding: 0 0 5px; 47 | } 48 | `; 49 | 50 | export const BuyOrdersText = styled.div` 51 | display: flex; 52 | grid-gap: 3px; 53 | align-items: center; 54 | font-weight: 400; 55 | font-size: 14px; 56 | color: ${(props) => props.theme.colors.primary}; 57 | flex-shrink: 0; 58 | white-space: nowrap; 59 | 60 | @media (min-width: ${(props) => props.theme.medium_width}) { 61 | font-size: 15px; 62 | } 63 | 64 | a { 65 | color: ${(props) => props.theme.colors.network}; 66 | } 67 | 68 | &.item { 69 | grid-gap: 10px; 70 | } 71 | 72 | &.title { 73 | font-size: 14px; 74 | font-weight: 800; 75 | 76 | @media (min-width: ${(props) => props.theme.medium_width}) { 77 | font-size: 15px; 78 | } 79 | } 80 | 81 | &.button { 82 | margin-left: auto; 83 | } 84 | `; 85 | -------------------------------------------------------------------------------- /components/Privacy/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | h3 { 8 | margin-top: 30px; 9 | } 10 | 11 | h4 { 12 | margin-top: 20px; 13 | } 14 | 15 | p { 16 | color: ${(props) => props.theme.colors.accent}; 17 | } 18 | 19 | li { 20 | color: ${(props) => props.theme.colors.accent}; 21 | } 22 | `; 23 | 24 | export const Title = styled.div` 25 | font-size: 26px; 26 | font-weight: 600; 27 | margin-bottom: 10px; 28 | 29 | @media (min-width: ${(props) => props.theme.small_width}) { 30 | font-size: 32px; 31 | } 32 | `; 33 | 34 | export const Subtitle = styled.div` 35 | margin-bottom: 20px; 36 | 37 | @media (min-width: ${(props) => props.theme.small_width}) { 38 | margin-bottom: 30px; 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /components/Profile/Filters/ActivityFilters.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { siteConfig } from "../../../shared/config"; 3 | import { State } from "../../../store"; 4 | import { 5 | ActivitySortFilter, 6 | ChainFilter, 7 | CollectionFilter, 8 | CurrencyFilter, 9 | EventTypesFilter, 10 | PriceFilter, 11 | } from "../../Common/Filters/Filters"; 12 | import { 13 | FiltersButton, 14 | FiltersGrid, 15 | FiltersSection, 16 | SaveFiltersButton, 17 | } from "../../Common/Filters/styles"; 18 | 19 | export const ActivityFilters = ({ 20 | filters, 21 | setFilters, 22 | filtersUI, 23 | setFiltersUI, 24 | collectionFilters, 25 | setCollectionFilters, 26 | }) => { 27 | const banner = useSelector((state: State) => state.banner); 28 | 29 | return ( 30 | <> 31 | setFiltersUI({ ...filtersUI, filtersVisible: true })} 33 | > 34 | Filters 35 | 36 | 47 | 48 | 54 | 55 | 61 | 62 | 68 | 69 | 75 | 76 | 82 | 83 | {collectionFilters.collectionResults.length > 0 && ( 84 | 92 | )} 93 | 94 | 95 | setFiltersUI({ ...filtersUI, filtersVisible: false })} 97 | > 98 | Save 99 | 100 | 101 | 102 | ); 103 | }; 104 | -------------------------------------------------------------------------------- /components/Profile/ProfileCreated.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import InfiniteScroll from "react-infinite-scroll-component"; 3 | import { fetchMoreByURL } from "../../api/general"; 4 | import { fetchProfileOwnedCollections } from "../../api/profile"; 5 | import { CollectionCardGhost } from "../CollectionCard/CollectionCardGhost"; 6 | import { CollectionCardLarge } from "../CollectionCard/CollectionCardLarge"; 7 | import { CollectionsGrid } from "../CollectionCard/styles"; 8 | import { NoItems } from "../Common/styles"; 9 | 10 | export const ProfileCreated = ({ 11 | profileAddress, 12 | createdState, 13 | setCreatedState, 14 | }) => { 15 | const fetchCreated = async () => { 16 | const collections = await fetchProfileOwnedCollections(profileAddress); 17 | 18 | setCreatedState({ 19 | ...createdState, 20 | collections: collections, 21 | moreCollections: collections.next ? true : false, 22 | collectionResults: collections.results, 23 | collectionsUpdating: false, 24 | }); 25 | }; 26 | 27 | const fetchMoreCreated = async () => { 28 | if (createdState.collections && createdState.collections.next) { 29 | const moreCollections = await fetchMoreByURL( 30 | createdState.collections.next 31 | ); 32 | 33 | setCreatedState({ 34 | ...createdState, 35 | collections: moreCollections, 36 | moreCollections: moreCollections.next ? true : false, 37 | collectionResults: createdState.collectionResults.concat( 38 | moreCollections.results 39 | ), 40 | }); 41 | } 42 | }; 43 | 44 | useEffect(() => { 45 | if (!createdState.collections) { 46 | fetchCreated(); 47 | } 48 | }, []); 49 | 50 | return ( 51 | <> 52 | {!!createdState.collectionsUpdating ? ( 53 | 54 | {[...Array(6)].map((e, i) => ( 55 | 56 | ))} 57 | 58 | ) : ( 59 | <> 60 | {!!createdState.collectionResults && 61 | createdState.collectionResults.length > 0 ? ( 62 | 63 | ( 68 | 69 | ))} 70 | style={{ display: "contents", overflow: "visible" }} 71 | > 72 | {createdState.collectionResults.map((collection, index) => ( 73 | 78 | ))} 79 | 80 | 81 | ) : ( 82 | 83 |

No collections to display

84 |
85 | )} 86 | 87 | )} 88 | 89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /components/ProfileCard/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const ProfilesGrid = styled.div` 4 | display: grid; 5 | grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); 6 | grid-gap: 12px; 7 | 8 | @media (min-width: ${(props) => props.theme.small_width}) { 9 | grid-gap: 20px; 10 | } 11 | 12 | .infinite-scroll-component__outerdiv { 13 | display: contents; 14 | } 15 | `; 16 | 17 | export const Card = styled.div` 18 | display: flex; 19 | align-items: center; 20 | justify-content: space-between; 21 | grid-gap: 20px; 22 | padding: 12px 15px 12px 12px; 23 | height: 100%; 24 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 5px 5px 15px rgba(0, 0, 0, 0.05), 25 | -5px -5px 15px rgba(0, 0, 0, 0.05); 26 | background: ${(props) => props.theme.colors.secondary}; 27 | border-radius: 10px; 28 | overflow: hidden; 29 | transition: all 0.2s; 30 | color: ${(props) => props.theme.colors.primary}; 31 | 32 | @media (min-width: ${(props) => props.theme.small_width}) { 33 | min-width: 280px; 34 | 35 | &:hover { 36 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.015), 37 | 5px 5px 20px rgba(0, 0, 0, 0.1), -5px -5px 20px rgba(0, 0, 0, 0.1); 38 | cursor: pointer; 39 | } 40 | } 41 | `; 42 | 43 | export const CardContent = styled.div` 44 | display: flex; 45 | align-items: center; 46 | grid-gap: 15px; 47 | `; 48 | 49 | export const CardSection = styled.div` 50 | display: flex; 51 | flex-direction: column; 52 | justify-content: space-between; 53 | grid-gap: 5px; 54 | `; 55 | 56 | export const ProfileImageContainer = styled.div` 57 | position: relative; 58 | background: ${(props) => props.theme.colors.lightGray}; 59 | width: 60px; 60 | height: 60px; 61 | border-radius: 52px; 62 | overflow: hidden; 63 | flex-shrink: 0; 64 | `; 65 | 66 | export const Name = styled.div` 67 | font-size: 16px; 68 | font-weight: 700; 69 | `; 70 | 71 | export const SmallName = styled.div` 72 | font-size: 13px; 73 | color: ${(props) => props.theme.colors.accent}; 74 | `; 75 | 76 | export const FollowIcon = styled.div` 77 | display: flex; 78 | font-size: 20px; 79 | padding: 10px; 80 | margin: -10px; 81 | `; 82 | -------------------------------------------------------------------------------- /components/Search/Search.tsx: -------------------------------------------------------------------------------- 1 | import { AssetCard } from "../AssetCard/AssetCard"; 2 | import { CardGrid } from "../AssetCard/styles"; 3 | import { CollectionCard } from "../CollectionCard/CollectionCard"; 4 | import { CollectionCardLarge } from "../CollectionCard/CollectionCardLarge"; 5 | import { CollectionsGrid } from "../CollectionCard/styles"; 6 | import { NoItems } from "../Common/styles"; 7 | import { ProfileCard } from "../ProfileCard/ProfileCard"; 8 | import { ProfilesGrid } from "../ProfileCard/styles"; 9 | import { 10 | ContainerBackground, 11 | ContainerExtended, 12 | SearchGrid, 13 | SearchQuery, 14 | SectionTitle, 15 | Title, 16 | } from "./styles"; 17 | 18 | export const Search = ({ query, collections, profiles, tokens }) => { 19 | return ( 20 | 21 | 22 | 23 | Search results for <SearchQuery>{query}</SearchQuery> 24 | 25 | 26 | {collections.length > 0 || profiles.length > 0 || tokens.length > 0 ? ( 27 | 28 | {collections.length > 0 && ( 29 |
30 | Collections 31 | 32 | {collections.slice(0, 12).map((collection, index) => ( 33 | 38 | ))} 39 | 40 |
41 | )} 42 | 43 | {profiles.length > 0 && ( 44 |
45 | Profiles 46 | 47 | {profiles.slice(0, 12).map((profile, index) => ( 48 | 49 | ))} 50 | 51 |
52 | )} 53 | 54 | {tokens.length > 0 && ( 55 |
56 | NFTs 57 | 58 | {tokens.slice(0, 24).map((token, index) => ( 59 | 60 | ))} 61 | 62 |
63 | )} 64 |
65 | ) : ( 66 | 67 |

No results to display

68 |

Try updating your search query

69 |
70 | )} 71 |
72 |
73 | ); 74 | }; 75 | -------------------------------------------------------------------------------- /components/Search/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div` 5 | &.filtersVisible { 6 | position: fixed; 7 | 8 | @media (min-width: ${(props) => props.theme.small_width}) { 9 | position: initial; 10 | } 11 | } 12 | `; 13 | 14 | export const ContainerExtended = styled(Container)` 15 | max-width: none; 16 | `; 17 | 18 | export const Title = styled.div` 19 | font-size: 18px; 20 | font-weight: 600; 21 | margin-bottom: 20px; 22 | 23 | @media (min-width: ${(props) => props.theme.small_width}) { 24 | font-size: 32px; 25 | margin-bottom: 30px; 26 | } 27 | `; 28 | 29 | export const SearchGrid = styled.div` 30 | display: flex; 31 | flex-direction: column; 32 | grid-gap: 40px; 33 | `; 34 | 35 | export const SearchQuery = styled.span` 36 | color: ${(props) => props.theme.colors.network}; 37 | word-break: break-word; 38 | `; 39 | 40 | export const SectionTitle = styled.div` 41 | font-size: 20px; 42 | font-weight: 600; 43 | margin-bottom: 20px; 44 | `; 45 | -------------------------------------------------------------------------------- /components/Settings/Settings.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import React, { useState } from "react"; 3 | import { BsLightningChargeFill } from "react-icons/bs"; 4 | import { FaUser } from "react-icons/fa"; 5 | import { MdPriceChange } from "react-icons/md"; 6 | import { 7 | ContainerExtended, 8 | EditorMenu, 9 | EditorMenuRow, 10 | Title, 11 | } from "../Common/Settings/styles"; 12 | import { ProfileNotifications } from "./ProfileNotifications"; 13 | import { ProfileOffers } from "./ProfileOffers"; 14 | import { ProfileSettings } from "./ProfileSettings"; 15 | 16 | export const Settings = ({ profile }) => { 17 | const router = useRouter(); 18 | 19 | const [selectedTab, setSelectedTab] = useState( 20 | router.query.tab ? String(router.query.tab) : 0 21 | ); 22 | 23 | const updateSelectedTab = (tab) => { 24 | setSelectedTab(tab); 25 | router.query.tab = tab; 26 | router.push(router, undefined, { shallow: true, scroll: false }); 27 | }; 28 | 29 | return ( 30 | 31 |
32 | Settings 33 | 34 | updateSelectedTab(0)} 37 | > 38 | 39 | Profile 40 | 41 | updateSelectedTab(1)} 44 | > 45 | 46 | Notifications 47 | 48 | updateSelectedTab(2)} 51 | > 52 | 53 | Offers 54 | 55 | 56 |
57 | 58 | {selectedTab == 0 && } 59 | {selectedTab == 1 && } 60 | {selectedTab == 2 && } 61 |
62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /components/Settings/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const CollectionGrid = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | grid-gap: 20px; 7 | `; 8 | 9 | export const CollectionRow = styled.div` 10 | display: flex; 11 | justify-content: space-between; 12 | align-items: center; 13 | grid-gap: 20px; 14 | `; 15 | 16 | export const CollectionInfo = styled.div` 17 | display: flex; 18 | grid-gap: 12px; 19 | align-items: center; 20 | `; 21 | 22 | export const CollectionImageContainer = styled.div` 23 | width: 75px; 24 | height: 75px; 25 | border-radius: 10px; 26 | overflow: hidden; 27 | flex-shrink: 0; 28 | color: ${(props) => props.theme.colors.lightGray}; 29 | `; 30 | 31 | export const CollectionText = styled.div` 32 | display: flex; 33 | flex-direction: column; 34 | grid-gap: 3px; 35 | `; 36 | 37 | export const CollectionName = styled.div` 38 | font-weight: 800; 39 | color: ${(props) => props.theme.colors.primary}; 40 | `; 41 | 42 | export const CollectionFloor = styled.div` 43 | display: flex; 44 | align-items: center; 45 | color: ${(props) => props.theme.colors.accent}; 46 | font-size: 14px; 47 | `; 48 | 49 | export const CollectionThreshold = styled.div``; 50 | -------------------------------------------------------------------------------- /components/Terms/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "../Common/Container/styles"; 3 | 4 | export const ContainerBackground = styled.div``; 5 | 6 | export const ContainerExtended = styled(Container)` 7 | h3 { 8 | margin-top: 30px; 9 | } 10 | 11 | h4 { 12 | margin-top: 20px; 13 | } 14 | 15 | p { 16 | color: ${(props) => props.theme.colors.accent}; 17 | } 18 | 19 | li { 20 | color: ${(props) => props.theme.colors.accent}; 21 | } 22 | `; 23 | 24 | export const Title = styled.div` 25 | font-size: 26px; 26 | font-weight: 600; 27 | margin-bottom: 10px; 28 | 29 | @media (min-width: ${(props) => props.theme.small_width}) { 30 | font-size: 32px; 31 | } 32 | `; 33 | 34 | export const Subtitle = styled.div` 35 | margin-bottom: 20px; 36 | 37 | @media (min-width: ${(props) => props.theme.small_width}) { 38 | margin-bottom: 30px; 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | async redirects() { 3 | return [ 4 | { 5 | source: `/c/:path*`, 6 | destination: "/collection/:path*", 7 | permanent: true, 8 | }, 9 | { 10 | source: `/a/:path*`, 11 | destination: "/asset/:path*", 12 | permanent: true, 13 | }, 14 | { 15 | source: `/l/:path*`, 16 | destination: "/launch/:path*", 17 | permanent: true, 18 | }, 19 | ]; 20 | }, 21 | images: { 22 | domains: [ 23 | "ipfs.io", 24 | "ipfs.infura.io", 25 | "cf-ipfs.com", 26 | "cloudflare-ipfs.com", 27 | "gateway.pinata.cloud", 28 | "arweave.net", 29 | ], 30 | }, 31 | reactStrictMode: true, 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quixotic-frontend", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "@coinbase/wallet-sdk": "^3.4.0", 12 | "@eth-optimism/sdk": "^1.1.5", 13 | "@google/model-viewer": "^1.10.1", 14 | "@opensea/seaport-js": "^1.0.4", 15 | "@walletconnect/web3-provider": "^1.7.1", 16 | "add": "^2.0.6", 17 | "chart.js": "^3.7.0", 18 | "chartjs-adapter-moment": "^1.0.0", 19 | "ethers": "^5.5.1", 20 | "mixpanel-browser": "^2.45.0", 21 | "moment": "^2.29.2", 22 | "next": "12.0.4", 23 | "next-redux-wrapper": "^7.0.5", 24 | "nextjs-progressbar": "^0.0.13", 25 | "react": "17.0.2", 26 | "react-chartjs-2": "^4.0.0", 27 | "react-countup": "^6.1.1", 28 | "react-debounce-input": "^3.2.5", 29 | "react-dom": "17.0.2", 30 | "react-icons": "^4.6.0", 31 | "react-infinite-scroll-component": "^6.1.0", 32 | "react-markdown": "^7.1.1", 33 | "react-multi-carousel": "^2.8.2", 34 | "react-palette": "^1.0.2", 35 | "react-papaparse": "^4.0.2", 36 | "react-player": "^2.9.0", 37 | "react-redux": "^7.2.6", 38 | "react-timeago": "^6.2.1", 39 | "react-toastify": "^8.1.0", 40 | "react-tooltip": "^4.5.1", 41 | "react-visibility-sensor": "^5.1.1", 42 | "redux-devtools-extension": "^2.13.9", 43 | "remove-markdown": "^0.3.0", 44 | "slick-carousel": "^1.8.1", 45 | "styled-components": "^5.3.3", 46 | "styled-react-modal": "^2.1.0", 47 | "web3": "^1.7.3", 48 | "yarn": "^1.22.17" 49 | }, 50 | "devDependencies": { 51 | "@types/node": "^17.0.41", 52 | "@types/react": "17.0.35", 53 | "eslint": "7.32.0", 54 | "eslint-config-next": "12.0.4", 55 | "typescript": "4.5.2" 56 | }, 57 | "prettier": { 58 | "tabWidth": 2 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { 2 | DocumentContext, 3 | Head, 4 | Html, 5 | Main, 6 | NextScript, 7 | } from "next/document"; 8 | import { ServerStyleSheet } from "styled-components"; 9 | import { siteConfig } from "../shared/config"; 10 | 11 | export default class MainDocument extends Document { 12 | static async getInitialProps(ctx: DocumentContext) { 13 | const sheet = new ServerStyleSheet(); 14 | const originalRenderPage = ctx.renderPage; 15 | try { 16 | ctx.renderPage = () => 17 | originalRenderPage({ 18 | enhanceApp: (App) => (props) => 19 | sheet.collectStyles(), 20 | }); 21 | 22 | const initialProps = await Document.getInitialProps(ctx); 23 | 24 | return { 25 | ...initialProps, 26 | styles: ( 27 | <> 28 | {initialProps.styles} 29 | {sheet.getStyleElement()} 30 | 31 | ), 32 | }; 33 | } finally { 34 | sheet.seal(); 35 | } 36 | } 37 | 38 | render() { 39 | const fontsUrl = 40 | "https://fonts.googleapis.com/css2?family=Readex+Pro:wght@200;300;400;500;600;700&display=swap"; 41 | 42 | return ( 43 | 44 | 45 | {/*OpenGraph Metatags*/} 46 | 47 | 51 | 52 | 53 | {/*Twitter Meta Tags*/} 54 | 55 | 56 | 57 | 58 | 59 | 60 | {this.props.styles} 61 | 62 | 63 |
64 | 65 | 66 | 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /pages/brand-assets.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { BrandAssets } from "../components/BrandAssets/BrandAssets"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | const BrandAssetsPage = () => { 6 | return ( 7 | <> 8 | 9 | Brand Assets | Quix 10 | 11 | 12 | 16 | 20 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default BrandAssetsPage; 38 | -------------------------------------------------------------------------------- /pages/bridge.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { Bridge } from "../components/Bridge/Bridge"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | export const getServerSideProps = async ({ query }) => { 6 | const network = query.network ? query.network : "ethereum"; 7 | const collectionAddress = query.address ? query.address : null; 8 | const tokenId = query.token_id ? query.token_id : null; 9 | 10 | return { 11 | props: { 12 | network, 13 | collectionAddress, 14 | tokenId, 15 | }, 16 | }; 17 | }; 18 | 19 | const BridgePage = ({ network, collectionAddress, tokenId }) => { 20 | return ( 21 | <> 22 | 23 | Optimism NFT Bridge | Quix 24 | 25 | 26 | 30 | 34 | 38 | 39 | 40 | 44 | 45 | 46 | 51 | 52 | ); 53 | }; 54 | 55 | export default BridgePage; 56 | -------------------------------------------------------------------------------- /pages/cart.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { Cart } from "../components/Cart/Cart"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | export const getServerSideProps = async ({ query }) => { 6 | const collectionAddress = query.collection ? query.collection : null; 7 | 8 | return { 9 | props: { 10 | collectionAddress, 11 | key: collectionAddress, 12 | }, 13 | }; 14 | }; 15 | 16 | const CartPage = ({ collectionAddress }) => { 17 | return ( 18 | <> 19 | 20 | Cart | Quix 21 | 22 | 23 | 27 | 31 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | ); 46 | }; 47 | 48 | export default CartPage; 49 | -------------------------------------------------------------------------------- /pages/collections.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useEffect, useState } from "react"; 3 | import { useSelector } from "react-redux"; 4 | import { fetchProfileOwnedCollections } from "../api/profile"; 5 | import { Loader } from "../components/Loader/Loader"; 6 | import { MyCollections } from "../components/MyCollections/MyCollections"; 7 | import { NotLoggedIn } from "../components/NotLoggedIn/NotLoggedIn"; 8 | import { siteConfig } from "../shared/config"; 9 | import { State } from "../store"; 10 | 11 | const MyCollectionsPage = () => { 12 | const address = useSelector((state: State) => state.address); 13 | const [collections, setCollections] = useState(); 14 | 15 | useEffect(() => { 16 | async function fetchCollections() { 17 | const collections = await fetchProfileOwnedCollections(address); 18 | setCollections(collections); 19 | } 20 | 21 | if (address) { 22 | setCollections(null); 23 | fetchCollections(); 24 | } 25 | }, [address]); 26 | 27 | return ( 28 | <> 29 | 30 | Collections | Quix 31 | 32 | 33 | 37 | 41 | 45 | 46 | 47 | 51 | 52 | 53 | {!!collections ? ( 54 | 58 | ) : address ? ( 59 | 60 | ) : ( 61 | 62 | )} 63 | 64 | ); 65 | }; 66 | 67 | export default MyCollectionsPage; 68 | -------------------------------------------------------------------------------- /pages/explore.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useRouter } from "next/router"; 3 | import { fetchExploreCollections } from "../api/collection"; 4 | import { Explore } from "../components/Explore/Explore"; 5 | import { siteConfig } from "../shared/config"; 6 | 7 | export const getStaticProps = async () => { 8 | const collections = await fetchExploreCollections(); 9 | 10 | return { 11 | props: { 12 | collections, 13 | }, 14 | revalidate: 60 * 5, 15 | }; 16 | }; 17 | 18 | const ExplorePage = ({ collections }) => { 19 | const router = useRouter(); 20 | 21 | return ( 22 | <> 23 | 24 | Explore NFTs | Quix 25 | 26 | 27 | 31 | 35 | 39 | 40 | 41 | 45 | 46 | 47 | 51 | 52 | ); 53 | }; 54 | 55 | export default ExplorePage; 56 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useEffect } from "react"; 3 | import { fetchFeaturedItems } from "../api/featured"; 4 | import { fetchMarketplaceStats } from "../api/stats"; 5 | import { Home } from "../components/Home/Home"; 6 | import { siteConfig } from "../shared/config"; 7 | import { visitHomePage } from "../utils/mixpanel"; 8 | 9 | export const getStaticProps = async () => { 10 | const featuredPromise = fetchFeaturedItems(); 11 | const collectionsPromise = fetchMarketplaceStats("volume:desc", "30d", true); 12 | 13 | const [featured, collections] = await Promise.all([ 14 | featuredPromise, 15 | collectionsPromise, 16 | ]); 17 | 18 | return { 19 | props: { 20 | featured, 21 | collections, 22 | }, 23 | revalidate: 60 * 5, 24 | }; 25 | }; 26 | 27 | const HomePage = ({ featured, collections }) => { 28 | useEffect(() => { 29 | visitHomePage(); 30 | }, []); 31 | return ( 32 | <> 33 | 34 | Quix, the largest NFT marketplace on Optimism 35 | 39 | 40 | 44 | 48 | 52 | 53 | 54 | 58 | 59 | 60 | 61 | 62 | ); 63 | }; 64 | 65 | export default HomePage; 66 | -------------------------------------------------------------------------------- /pages/launch.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { fetchLaunchpadCollections } from "../api/collection"; 3 | import { Launch } from "../components/Launch/Launch"; 4 | import { siteConfig } from "../shared/config"; 5 | 6 | export const getStaticProps = async () => { 7 | const collections = await fetchLaunchpadCollections(); 8 | 9 | return { 10 | props: { 11 | collections, 12 | }, 13 | revalidate: 60 * 5, 14 | }; 15 | }; 16 | 17 | const LaunchPage = ({ collections }) => { 18 | return ( 19 | <> 20 | 21 | Launch | Quix 22 | 23 | 24 | 28 | 32 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default LaunchPage; 50 | -------------------------------------------------------------------------------- /pages/launch/deploy.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { LaunchpadDeploy } from "../../components/LaunchpadDeploy/LaunchpadDeploy"; 3 | import { siteConfig } from "../../shared/config"; 4 | 5 | const LaunchpadDeployPage = () => { 6 | return ( 7 | <> 8 | 9 | Launchpad | Quix 10 | 11 | 12 | 16 | 20 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default LaunchpadDeployPage; 38 | -------------------------------------------------------------------------------- /pages/listings.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useEffect, useState } from "react"; 3 | import { useSelector } from "react-redux"; 4 | import { 5 | fetchProfileListedTokens, 6 | fetchProfileUnlistedTokens, 7 | } from "../api/profile"; 8 | import { List } from "../components/List/List"; 9 | import { Loader } from "../components/Loader/Loader"; 10 | import { NotLoggedIn } from "../components/NotLoggedIn/NotLoggedIn"; 11 | import { siteConfig } from "../shared/config"; 12 | import { State } from "../store"; 13 | 14 | const ProfilePage = () => { 15 | const address = useSelector((state: State) => state.address); 16 | const [listed, setListed] = useState(null); 17 | const [unlisted, setUnlisted] = useState(null); 18 | 19 | useEffect(() => { 20 | async function fetchOffers() { 21 | const listedPromise = fetchProfileListedTokens(address); 22 | const unlistedPromise = fetchProfileUnlistedTokens(address); 23 | 24 | const [listedRes, unlistedRes] = await Promise.all([ 25 | listedPromise, 26 | unlistedPromise, 27 | ]); 28 | 29 | setListed(listedRes); 30 | setUnlisted(unlistedRes); 31 | } 32 | 33 | if (address) { 34 | setListed(null); 35 | setUnlisted(null); 36 | fetchOffers(); 37 | } 38 | }, [address]); 39 | 40 | return ( 41 | <> 42 | 43 | Listings | Quix 44 | 45 | 46 | 50 | 54 | 58 | 59 | 60 | 64 | 65 | 66 | {listed && unlisted ? ( 67 | 68 | ) : address ? ( 69 | 70 | ) : ( 71 | 72 | )} 73 | 74 | ); 75 | }; 76 | 77 | export default ProfilePage; 78 | -------------------------------------------------------------------------------- /pages/offers.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useEffect, useState } from "react"; 3 | import { useSelector } from "react-redux"; 4 | import { 5 | fetchProfileOffersMade, 6 | fetchProfileOffersReceived, 7 | } from "../api/profile"; 8 | import { Loader } from "../components/Loader/Loader"; 9 | import { NotLoggedIn } from "../components/NotLoggedIn/NotLoggedIn"; 10 | import { Offers } from "../components/Offers/Offers"; 11 | import { siteConfig } from "../shared/config"; 12 | import { State } from "../store"; 13 | 14 | const ProfilePage = () => { 15 | const address = useSelector((state: State) => state.address); 16 | const [offersMade, setOffersMade] = useState(null); 17 | const [offersReceived, setOffersReceived] = useState(null); 18 | 19 | useEffect(() => { 20 | async function fetchOffers() { 21 | const offersMadePromise = fetchProfileOffersMade(address); 22 | const offersReceivedPromise = fetchProfileOffersReceived(address); 23 | 24 | const [offersMadeRes, offersReceivedRes] = await Promise.all([ 25 | offersMadePromise, 26 | offersReceivedPromise, 27 | ]); 28 | 29 | setOffersMade(offersMadeRes); 30 | setOffersReceived(offersReceivedRes); 31 | } 32 | 33 | if (address) { 34 | setOffersMade(null); 35 | setOffersReceived(null); 36 | fetchOffers(); 37 | } 38 | }, [address]); 39 | 40 | return ( 41 | <> 42 | 43 | Offers | Quix 44 | 45 | 46 | 50 | 54 | 58 | 59 | 60 | 64 | 65 | 66 | {offersMade && offersReceived ? ( 67 | 68 | ) : address ? ( 69 | 70 | ) : ( 71 | 72 | )} 73 | 74 | ); 75 | }; 76 | 77 | export default ProfilePage; 78 | -------------------------------------------------------------------------------- /pages/onboard.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { Optimism } from "../components/Custom/Optimism/Optimism"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | const OptimismPage = () => { 6 | if (siteConfig.NETWORK == "opt-mainnet") { 7 | return ( 8 | <> 9 | 10 | Mint your free Optimistic Explorer NFT | Quix 11 | 15 | 16 | 20 | 24 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | ); 39 | } else { 40 | return <>; 41 | } 42 | }; 43 | 44 | export default OptimismPage; 45 | -------------------------------------------------------------------------------- /pages/privacy.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { Privacy } from "../components/Privacy/Privacy"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | const PrivacyPage = () => { 6 | return ( 7 | <> 8 | 9 | Privacy Policy | Quix 10 | 11 | 12 | 16 | 20 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default PrivacyPage; 38 | -------------------------------------------------------------------------------- /pages/profile.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useRouter } from "next/router"; 3 | import { useEffect, useState } from "react"; 4 | import { useSelector } from "react-redux"; 5 | import { fetchProfile } from "../api/profile"; 6 | import { Loader } from "../components/Loader/Loader"; 7 | import { NotLoggedIn } from "../components/NotLoggedIn/NotLoggedIn"; 8 | import { Profile } from "../components/Profile/Profile"; 9 | import { siteConfig } from "../shared/config"; 10 | import { State } from "../store"; 11 | 12 | const ProfilePage = () => { 13 | const router = useRouter(); 14 | const address = useSelector((state: State) => state.address); 15 | const [profile, setProfile] = useState(null); 16 | 17 | useEffect(() => { 18 | async function getProfile() { 19 | const profileRes = await fetchProfile(address); 20 | 21 | setProfile(profileRes); 22 | } 23 | 24 | if (address) { 25 | setProfile(null); 26 | getProfile(); 27 | } 28 | }, [address]); 29 | 30 | return ( 31 | <> 32 | 33 | My Profile | Quix 34 | 35 | 36 | 40 | 44 | 48 | 49 | 50 | 54 | 55 | 56 | {!!profile ? ( 57 | 61 | ) : address ? ( 62 | 63 | ) : ( 64 | 65 | )} 66 | 67 | ); 68 | }; 69 | 70 | export default ProfilePage; 71 | -------------------------------------------------------------------------------- /pages/rabbithole.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { RabbitHole } from "../components/Custom/RabbitHole/RabbitHole"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | const RabbitHolePage = () => { 6 | if (siteConfig.NETWORK == "opt-mainnet") { 7 | return ( 8 | <> 9 | 10 | Mint your free RabbitHole L2 Explorer NFT | Quix 11 | 15 | 16 | 20 | 24 | 28 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | ); 39 | } else { 40 | return <>; 41 | } 42 | }; 43 | 44 | export default RabbitHolePage; 45 | -------------------------------------------------------------------------------- /pages/search.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { fetchExtendedSearchResults } from "../api/search"; 3 | import { Search } from "../components/Search/Search"; 4 | import { siteConfig } from "../shared/config"; 5 | 6 | export const getServerSideProps = async ({ query }) => { 7 | const searchResults = await fetchExtendedSearchResults(query.query); 8 | 9 | return { 10 | props: { 11 | query: query.query, 12 | collections: searchResults.collections, 13 | profiles: searchResults.profiles, 14 | tokens: searchResults.tokens, 15 | }, 16 | }; 17 | }; 18 | 19 | const SearchPage = ({ query, collections, profiles, tokens }) => { 20 | return ( 21 | <> 22 | 23 | Search | Quix 24 | 25 | 26 | 30 | 34 | 38 | 39 | 40 | 44 | 45 | 51 | 52 | ); 53 | }; 54 | 55 | export default SearchPage; 56 | -------------------------------------------------------------------------------- /pages/settings.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useEffect, useState } from "react"; 3 | import { useSelector } from "react-redux"; 4 | import { fetchProfileSettings } from "../api/settings"; 5 | import { Loader } from "../components/Loader/Loader"; 6 | import { NotFound } from "../components/NotFound/NotFound"; 7 | import { NotLoggedIn } from "../components/NotLoggedIn/NotLoggedIn"; 8 | import { Settings } from "../components/Settings/Settings"; 9 | import { siteConfig } from "../shared/config"; 10 | import { State } from "../store"; 11 | 12 | const SettingsPage = () => { 13 | const address = useSelector((state: State) => state.address); 14 | const [profile, setProfile] = useState(null); 15 | const [failedSign, setFailedSign] = useState(false); 16 | 17 | useEffect(() => { 18 | const fetchProfile = async () => { 19 | setProfile(null); 20 | setFailedSign(false); 21 | const profileRes = await fetchProfileSettings(address); 22 | if (profileRes) { 23 | setProfile(profileRes); 24 | } else { 25 | setFailedSign(true); 26 | } 27 | }; 28 | 29 | if (address) fetchProfile(); 30 | }, [address]); 31 | 32 | return ( 33 | <> 34 | 35 | Settings | Quix 36 | 37 | 38 | 42 | 46 | 50 | 51 | 52 | 56 | 57 | 58 | {!!profile ? ( 59 | 60 | ) : failedSign ? ( 61 | 62 | ) : address ? ( 63 | 64 | ) : ( 65 | 66 | )} 67 | 68 | ); 69 | }; 70 | 71 | export default SettingsPage; 72 | -------------------------------------------------------------------------------- /pages/stats.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { useState } from "react"; 3 | import { fetchMarketplaceStats } from "../api/stats"; 4 | import { Stats } from "../components/Stats/Stats"; 5 | import { siteConfig } from "../shared/config"; 6 | 7 | export const getStaticProps = async () => { 8 | const collectionsPromise = fetchMarketplaceStats("volume:desc", "30d", true); 9 | 10 | const [collectionsRes] = await Promise.all([collectionsPromise]); 11 | 12 | return { 13 | props: { 14 | collectionsRes, 15 | }, 16 | revalidate: 60 * 10, 17 | }; 18 | }; 19 | 20 | const StatsPage = ({ collectionsRes }) => { 21 | const [collections, setCollections] = useState(collectionsRes); 22 | 23 | return ( 24 | <> 25 | 26 | Trending Collections | Quix 27 | 28 | 29 | 33 | 37 | 41 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default StatsPage; 55 | -------------------------------------------------------------------------------- /pages/terms.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { Terms } from "../components/Terms/Terms"; 3 | import { siteConfig } from "../shared/config"; 4 | 5 | const TermsPage = () => { 6 | return ( 7 | <> 8 | 9 | Terms of Use | Quix 10 | 11 | 12 | 16 | 20 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default TermsPage; 38 | -------------------------------------------------------------------------------- /public/Quix_Logos.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/Quix_Logos.zip -------------------------------------------------------------------------------- /public/bankless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/bankless.png -------------------------------------------------------------------------------- /public/bridge/network_eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/bridge/network_eth.png -------------------------------------------------------------------------------- /public/bridge/network_op.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/bridge/network_op.png -------------------------------------------------------------------------------- /public/display-themes/contained.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/display-themes/covered.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/display-themes/padded.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/etherscan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/hop.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/hop.jpeg -------------------------------------------------------------------------------- /public/launch/alchemy.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/alchemy.jpeg -------------------------------------------------------------------------------- /public/launch/dune.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/dune.png -------------------------------------------------------------------------------- /public/launch/ethereumbots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/ethereumbots.png -------------------------------------------------------------------------------- /public/launch/galaxy.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/galaxy.jpeg -------------------------------------------------------------------------------- /public/launch/guild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/guild.png -------------------------------------------------------------------------------- /public/launch/layer3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/layer3.jpeg -------------------------------------------------------------------------------- /public/launch/litprotocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/litprotocol.png -------------------------------------------------------------------------------- /public/launch/mintplex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/mintplex.png -------------------------------------------------------------------------------- /public/launch/niftykit.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/niftykit.jpeg -------------------------------------------------------------------------------- /public/launch/niftykit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/niftykit.png -------------------------------------------------------------------------------- /public/launch/opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/opt.png -------------------------------------------------------------------------------- /public/launch/optimism.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/optimism.webp -------------------------------------------------------------------------------- /public/launch/rainbow.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/rainbow.webp -------------------------------------------------------------------------------- /public/launch/simplehash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/launch/simplehash.png -------------------------------------------------------------------------------- /public/login/coinbase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/login/walletconnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WalletConnect 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/logos/opt_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/logos/opt_full.png -------------------------------------------------------------------------------- /public/logos/opt_full_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/logos/opt_full_dark.png -------------------------------------------------------------------------------- /public/onboard/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/onboard/0.png -------------------------------------------------------------------------------- /public/onboard/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/onboard/1.png -------------------------------------------------------------------------------- /public/onboard/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/onboard/2.png -------------------------------------------------------------------------------- /public/onboard/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/onboard/3.png -------------------------------------------------------------------------------- /public/onboard/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/onboard/4.png -------------------------------------------------------------------------------- /public/opog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/opog.png -------------------------------------------------------------------------------- /public/opt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/opt.png -------------------------------------------------------------------------------- /public/opt_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/opt_banner.png -------------------------------------------------------------------------------- /public/opt_banner_slim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/opt_banner_slim.png -------------------------------------------------------------------------------- /public/opt_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/opt_favicon.png -------------------------------------------------------------------------------- /public/opt_twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/opt_twitter.png -------------------------------------------------------------------------------- /public/optimism_gateway.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/optimism_gateway.webp -------------------------------------------------------------------------------- /public/payment_tokens/ETH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/payment_tokens/ETH.png -------------------------------------------------------------------------------- /public/payment_tokens/OP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/payment_tokens/OP.png -------------------------------------------------------------------------------- /public/payment_tokens/WETH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/payment_tokens/WETH.png -------------------------------------------------------------------------------- /public/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/profile.png -------------------------------------------------------------------------------- /public/rabbithole/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/rabbithole/0.gif -------------------------------------------------------------------------------- /public/rabbithole/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/rabbithole/1.gif -------------------------------------------------------------------------------- /public/rabbithole/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/rabbithole/2.gif -------------------------------------------------------------------------------- /public/rabbithole/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quixotic-dev/frontend/00c7c71b43d8596d3a18a886d5d483096fda0d9c/public/rabbithole/profile.png -------------------------------------------------------------------------------- /shared/config.ts: -------------------------------------------------------------------------------- 1 | export const siteConfig = 2 | process.env.NEXT_PUBLIC_NETWORK == "opt-mainnet" 3 | ? { 4 | CHAIN_NAME: "Optimism", 5 | CHAIN_ID: "0xa", 6 | NETWORK: "opt-mainnet", 7 | WEBSITE_URL: "qx.app", 8 | RPC_URL: "https://mainnet.optimism.io", 9 | EXCHANGE_V6: "0x998EF16Ea4111094EB5eE72fC2c6f4e6E8647666", 10 | PAUSABLE_ZONE: "0x5D9102D6a0734Fc6731A958a685DE18101d98357", 11 | L1_BLOCK_EXPLORER_URL: "https://etherscan.io", 12 | L2_BLOCK_EXPLORER_URL: "https://optimistic.etherscan.io", 13 | L1_BRIDGE_ADDRESS: "0x5a7749f83b81B301cAb5f48EB8516B986DAef23D", 14 | L2_BRIDGE_ADDRESS: "0x4200000000000000000000000000000000000014", 15 | REWARDS_WRAPPER: "0xC78A09D6a4badecc7614A339FD264B7290361ef1", 16 | REWARDS_DERIVER: "0xaFB71004636fCAf6fb15959A7dD19db4779c3237", 17 | CAMPAIGN_TRACKER_ADDRESS: "0x3Dadc74B465034276bE0Fa55240e1a67d7e3a266", 18 | BACKEND_URL: "", 19 | BACKEND_TOKEN: "", 20 | } 21 | : process.env.NEXT_PUBLIC_NETWORK == "opt-goerli" 22 | ? { 23 | CHAIN_NAME: "Optimism Goerli", 24 | CHAIN_ID: "0x1a4", 25 | NETWORK: "opt-goerli", 26 | WEBSITE_URL: "goerli.qx.app", 27 | RPC_URL: "https://goerli.optimism.io", 28 | EXCHANGE_V6: "0xA943370D40d2470d45CECD9093278fd8BB830e58", 29 | PAUSABLE_ZONE: "0x7305a4d8C9d01AeD3c0CBA9A9F0F359e92160833", 30 | L1_BLOCK_EXPLORER_URL: "https://goerli.etherscan.io", 31 | L2_BLOCK_EXPLORER_URL: "https://goerli-optimism.etherscan.io", 32 | L1_BRIDGE_ADDRESS: "0x8DD330DdE8D9898d43b4dc840Da27A07dF91b3c9", 33 | L2_BRIDGE_ADDRESS: "0x4200000000000000000000000000000000000014", 34 | LOGO_BADGE: "Goerli", 35 | BACKEND_URL: "", 36 | BACKEND_TOKEN: "", 37 | } 38 | : {}; 39 | -------------------------------------------------------------------------------- /shared/theme.ts: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | 3 | export const lightTheme = { 4 | fonts: { 5 | basic: "Readex Pro, sans-serif", 6 | }, 7 | colors: { 8 | background: "#ffffff", 9 | footer: "#000000", 10 | heroGradient: "#ff6c75", 11 | heroBackground: "#ff0420", 12 | heroBackgroundStart: "#340000", 13 | heroBackgroundEnd: "#ff0420", 14 | primary: "#000000", 15 | secondary: "#ffffff", 16 | accent: "#555555", 17 | network: "#ff0420", 18 | networkLight: "#ffeff0", 19 | lightGray: "#f6f6f6", 20 | gray: "#dddddd", 21 | darkGray: "#505050", 22 | }, 23 | small_width: "768px", 24 | medium_width: "968px", 25 | max_width: "1280px", 26 | }; 27 | 28 | export const darkTheme = { 29 | fonts: { 30 | basic: "Readex Pro, sans-serif", 31 | }, 32 | colors: { 33 | background: "#000000", 34 | footer: "#1C1C1D", 35 | heroGradient: "#000000", 36 | heroBackground: "#282828", 37 | heroBackgroundStart: "#282828", 38 | heroBackgroundEnd: "#282828", 39 | primary: "#ffffff", 40 | secondary: "#1C1C1D", 41 | accent: "#bbbbbb", 42 | network: "#ffffff", 43 | networkLight: "#282828", 44 | lightGray: "#282828", 45 | gray: "#606060", 46 | darkGray: "#505050", 47 | }, 48 | small_width: "768px", 49 | medium_width: "968px", 50 | max_width: "1280px", 51 | }; 52 | 53 | export const GlobalStyle = createGlobalStyle` 54 | body { 55 | margin: 0; 56 | -webkit-font-smoothing: antialiased; 57 | -moz-osx-font-smoothing: grayscale; 58 | overflow-y: overlay; 59 | color: ${({ theme }) => theme.colors.primary}; 60 | background: ${({ theme }) => theme.colors.background} 61 | } 62 | 63 | *, 64 | *::after, 65 | *::before { 66 | font-family: ${({ theme }) => theme.fonts.basic}; 67 | box-sizing: border-box; 68 | } 69 | 70 | h1, h2, h3, h4, h5, h6 { margin: 0; } 71 | 72 | a { 73 | text-decoration: none; 74 | color: ${({ theme }) => theme.colors.accent}; 75 | cursor: pointer; 76 | } 77 | 78 | a:hover { 79 | color: ${({ theme }) => theme.colors.primary}; 80 | text-decoration: none; 81 | cursor: pointer; 82 | } 83 | 84 | .main { 85 | padding: 70px 0 0; 86 | min-height: calc(100vh - 316px); 87 | 88 | @media (min-width: ${(props) => props.theme.small_width}) { 89 | min-height: calc(100vh - 247px); 90 | } 91 | } 92 | 93 | .banner { 94 | padding: 120px 0 0; 95 | } 96 | `; 97 | -------------------------------------------------------------------------------- /shared/types.ts: -------------------------------------------------------------------------------- 1 | export type UniqueString = string; 2 | export type UriString = string; 3 | export type UUID = string; 4 | export type EntityId = number | UniqueString; 5 | export type EthAddress = UniqueString; 6 | export type SlugString = UniqueString; 7 | export type Optional = TEntity | null; 8 | -------------------------------------------------------------------------------- /store/address.ts: -------------------------------------------------------------------------------- 1 | import { HYDRATE } from "next-redux-wrapper"; 2 | import { AnyAction } from "redux"; 3 | import { Optional } from "../shared/types"; 4 | 5 | export const UPDATE_ADDRESS_ACTION = "UPDATE_ADDRESS"; 6 | 7 | export const updateAddress = (address: string, dispatch) => { 8 | return dispatch({ type: UPDATE_ADDRESS_ACTION, address }); 9 | }; 10 | 11 | export interface UpdateAddressAction extends AnyAction { 12 | type: string; 13 | address: string; 14 | } 15 | 16 | export type AddressState = Optional; 17 | 18 | export const addressReducer = ( 19 | state: AddressState = null, 20 | action: UpdateAddressAction 21 | ) => { 22 | switch (action.type) { 23 | case HYDRATE: 24 | return action.payload?.address ?? state; 25 | case UPDATE_ADDRESS_ACTION: 26 | return action.address; 27 | default: 28 | return state; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /store/balance.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { Optional } from "../shared/types"; 3 | 4 | export const UPDATE_BALANCE_ACTION = "UPDATE_BALANCE"; 5 | 6 | export const updateBalance = (balance, dispatch) => { 7 | return dispatch({ type: UPDATE_BALANCE_ACTION, balance }); 8 | }; 9 | 10 | export interface UpdateBalanceAction extends AnyAction { 11 | type: string; 12 | balance: any; 13 | } 14 | 15 | export type BalanceState = Optional; 16 | 17 | export const balanceReducer = ( 18 | state: BalanceState = null, 19 | action: UpdateBalanceAction 20 | ) => { 21 | switch (action.type) { 22 | case UPDATE_BALANCE_ACTION: 23 | return action.balance; 24 | default: 25 | return state; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /store/banner.ts: -------------------------------------------------------------------------------- 1 | import { HYDRATE } from "next-redux-wrapper"; 2 | import { AnyAction } from "redux"; 3 | import { Optional } from "../shared/types"; 4 | 5 | export const UPDATE_BANNER_ACTION = "UPDATE_BANNER"; 6 | 7 | export const updateBanner = (banner: boolean, dispatch) => { 8 | return dispatch({ type: UPDATE_BANNER_ACTION, banner }); 9 | }; 10 | 11 | export interface UpdateBannerAction extends AnyAction { 12 | type: string; 13 | banner: boolean; 14 | } 15 | 16 | export type BannerState = Optional; 17 | 18 | export const bannerReducer = ( 19 | state: BannerState = false, //update based on banner visibility 20 | action: UpdateBannerAction 21 | ) => { 22 | switch (action.type) { 23 | case HYDRATE: 24 | return action.payload?.address ?? state; 25 | case UPDATE_BANNER_ACTION: 26 | return action.banner; 27 | default: 28 | return state; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /store/gridLayout.ts: -------------------------------------------------------------------------------- 1 | import { HYDRATE } from "next-redux-wrapper"; 2 | import { AnyAction } from "redux"; 3 | import { Optional } from "../shared/types"; 4 | 5 | export const UPDATE_GRIDLAYOUT_ACTION = "UPDATE_GRIDLAYOUT"; 6 | 7 | export const updateGridLayout = (gridLayout: Number, dispatch) => { 8 | return dispatch({ type: UPDATE_GRIDLAYOUT_ACTION, gridLayout }); 9 | }; 10 | 11 | export interface UpdateGridLayoutAction extends AnyAction { 12 | type: string; 13 | gridLayout: Number; 14 | } 15 | 16 | export type GridLayoutState = Optional; 17 | 18 | export const gridLayoutReducer = ( 19 | state: GridLayoutState = 1, 20 | action: UpdateGridLayoutAction 21 | ) => { 22 | switch (action.type) { 23 | case HYDRATE: 24 | return action.payload?.address ?? state; 25 | case UPDATE_GRIDLAYOUT_ACTION: 26 | return action.gridLayout; 27 | default: 28 | return state; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /store/hydrate.ts: -------------------------------------------------------------------------------- 1 | import { HYDRATE } from "next-redux-wrapper"; 2 | import { AnyAction } from "redux"; 3 | 4 | export interface HydrateAction extends AnyAction { 5 | type: typeof HYDRATE; 6 | } 7 | -------------------------------------------------------------------------------- /store/index.ts: -------------------------------------------------------------------------------- 1 | import { createWrapper, MakeStore } from "next-redux-wrapper"; 2 | import { combineReducers, createStore } from "redux"; 3 | import { composeWithDevTools } from "redux-devtools-extension"; 4 | import { addressReducer, AddressState } from "./address"; 5 | import { balanceReducer, BalanceState } from "./balance"; 6 | import { bannerReducer, BannerState } from "./banner"; 7 | import { gridLayoutReducer, GridLayoutState } from "./gridLayout"; 8 | import { loginReducer, LoginState } from "./login"; 9 | import { onboardReducer, OnboardState } from "./onboard"; 10 | import { profileReducer, ProfileState } from "./profile"; 11 | import { showUSDReducer, ShowUSDState } from "./showUSD"; 12 | 13 | export type State = { 14 | address: AddressState; 15 | balance: BalanceState; 16 | banner: BannerState; 17 | onboard: OnboardState; 18 | login: LoginState; 19 | showUSD: ShowUSDState; 20 | gridLayout: GridLayoutState; 21 | profile: ProfileState; 22 | }; 23 | 24 | const combinedReducer = combineReducers({ 25 | address: addressReducer, 26 | balance: balanceReducer, 27 | banner: bannerReducer, 28 | onboard: onboardReducer, 29 | login: loginReducer, 30 | showUSD: showUSDReducer, 31 | gridLayout: gridLayoutReducer, 32 | profile: profileReducer, 33 | }); 34 | 35 | // @ts-ignore 36 | const makeStore: MakeStore = () => 37 | createStore(combinedReducer, composeWithDevTools()); 38 | 39 | // @ts-ignore 40 | export const wrapper = createWrapper(makeStore, { 41 | debug: true, 42 | }); 43 | -------------------------------------------------------------------------------- /store/login.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { Optional } from "../shared/types"; 3 | 4 | export const UPDATE_LOGIN_ACTION = "UPDATE_LOGIN"; 5 | 6 | export const updateLogin = (login: boolean, dispatch) => { 7 | return dispatch({ type: UPDATE_LOGIN_ACTION, login }); 8 | }; 9 | 10 | export interface UpdateLoginAction extends AnyAction { 11 | type: string; 12 | login: boolean; 13 | } 14 | 15 | export type LoginState = Optional; 16 | 17 | export const loginReducer = ( 18 | state: LoginState = false, 19 | action: UpdateLoginAction 20 | ) => { 21 | switch (action.type) { 22 | case UPDATE_LOGIN_ACTION: 23 | return action.login; 24 | default: 25 | return state; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /store/onboard.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { Optional } from "../shared/types"; 3 | 4 | export const UPDATE_ONBOARD_ACTION = "UPDATE_ONBOARD"; 5 | 6 | export const updateOnboard = (onboard: boolean, dispatch) => { 7 | return dispatch({ type: UPDATE_ONBOARD_ACTION, onboard }); 8 | }; 9 | 10 | export interface UpdateOnboardAction extends AnyAction { 11 | type: string; 12 | onboard: boolean; 13 | } 14 | 15 | export type OnboardState = Optional; 16 | 17 | export const onboardReducer = ( 18 | state: OnboardState = false, 19 | action: UpdateOnboardAction 20 | ) => { 21 | switch (action.type) { 22 | case UPDATE_ONBOARD_ACTION: 23 | return action.onboard; 24 | default: 25 | return state; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /store/profile.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from "redux"; 2 | import { Optional } from "../shared/types"; 3 | 4 | export const UPDATE_PROFILE_ACTION = "UPDATE_PROFILE"; 5 | 6 | export const updateProfile = (profile, dispatch) => { 7 | return dispatch({ type: UPDATE_PROFILE_ACTION, profile }); 8 | }; 9 | 10 | export interface UpdateProfileAction extends AnyAction { 11 | type: string; 12 | profile: any; 13 | } 14 | 15 | export type ProfileState = Optional; 16 | 17 | export const profileReducer = ( 18 | state: ProfileState = null, 19 | action: UpdateProfileAction 20 | ) => { 21 | switch (action.type) { 22 | case UPDATE_PROFILE_ACTION: 23 | return action.profile; 24 | default: 25 | return state; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /store/showUSD.ts: -------------------------------------------------------------------------------- 1 | import { HYDRATE } from "next-redux-wrapper"; 2 | import { AnyAction } from "redux"; 3 | import { Optional } from "../shared/types"; 4 | 5 | export const UPDATE_SHOWUSD_ACTION = "UPDATE_SHOWUSD"; 6 | 7 | export const updateShowUSD = (showUSD: boolean, dispatch) => { 8 | return dispatch({ type: UPDATE_SHOWUSD_ACTION, showUSD }); 9 | }; 10 | 11 | export interface UpdateShowUSDAction extends AnyAction { 12 | type: string; 13 | showUSD: boolean; 14 | } 15 | 16 | export type ShowUSDState = Optional; 17 | 18 | export const showUSDReducer = ( 19 | state: ShowUSDState = false, 20 | action: UpdateShowUSDAction 21 | ) => { 22 | switch (action.type) { 23 | case HYDRATE: 24 | return action.payload?.address ?? state; 25 | case UPDATE_SHOWUSD_ACTION: 26 | return action.showUSD; 27 | default: 28 | return state; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /utils/abi/erc165ABI.ts: -------------------------------------------------------------------------------- 1 | export const erc165ABI = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: "bytes4", 6 | name: "interfaceId", 7 | type: "bytes4", 8 | }, 9 | ], 10 | name: "supportsInterface", 11 | outputs: [ 12 | { 13 | internalType: "bool", 14 | name: "", 15 | type: "bool", 16 | }, 17 | ], 18 | stateMutability: "view", 19 | type: "function", 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /utils/bridge/bridgedErc721.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { toast } from "react-toastify"; 3 | import { getSigner, switchNetworkTo } from "../wallet"; 4 | import { bridgedErc721ABI } from "./bridgedErc721ABI"; 5 | 6 | // Used to mint a testnet NFT. For demo purposes only 7 | export const mintStandardNFT = async (collectionAddress) => { 8 | await switchNetworkTo("0x5"); 9 | 10 | const signer = await getSigner(); 11 | const contract = new ethers.Contract( 12 | collectionAddress, 13 | bridgedErc721ABI, 14 | signer 15 | ); 16 | 17 | try { 18 | const txn = await contract.mint(); 19 | return txn.hash; 20 | } catch (error) { 21 | if (error.message && error.message.includes("User denied")) { 22 | toast.info("Transaction cancelled by user"); 23 | } else if ( 24 | error.data && 25 | error.data.message.includes("Sale must be active") 26 | ) { 27 | toast.warning("The sale is not active"); 28 | } else if (error.message && error.message.includes("gas too low")) { 29 | toast.error("Transaction failed, not enough gas"); 30 | } else { 31 | toast.error("Error minting NFT"); 32 | } 33 | return null; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /utils/bridge/l1ERC721Bridge.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { toast } from "react-toastify"; 3 | import { siteConfig } from "../../shared/config"; 4 | import { erc721ABI } from "../abi/erc721ABI"; 5 | import { createContract } from "../exchange/exchange"; 6 | import { assertNetwork, getSigner } from "../wallet"; 7 | import { l1ERC721BridgeABI } from "./l1ERC721BridgeABI"; 8 | 9 | export const isL1BridgeApprovedForAll = async ( 10 | sellerAddress, 11 | erc721Address 12 | ) => { 13 | if (siteConfig.NETWORK == "opt-goerli") { 14 | await assertNetwork("0x5", "Ethereum Goerli"); 15 | } else { 16 | await assertNetwork("0x1", "Ethereum"); 17 | } 18 | 19 | const contract = await createContract(erc721Address, erc721ABI); 20 | const isApproved = await contract.isApprovedForAll( 21 | sellerAddress, 22 | siteConfig.L1_BRIDGE_ADDRESS 23 | ); 24 | return isApproved; 25 | }; 26 | 27 | export const setL1BridgeApprovalForAll = async (erc721Address) => { 28 | if (siteConfig.NETWORK == "opt-goerli") { 29 | await assertNetwork("0x5", "Ethereum Goerli"); 30 | } else { 31 | await assertNetwork("0x1", "Ethereum"); 32 | } 33 | 34 | const signer = await getSigner(); 35 | const isApproved = await isL1BridgeApprovedForAll( 36 | await signer.getAddress(), 37 | erc721Address 38 | ); 39 | 40 | if (!isApproved) { 41 | const contract = await createContract(erc721Address, erc721ABI); 42 | 43 | try { 44 | const approval = await contract.setApprovalForAll( 45 | siteConfig.L1_BRIDGE_ADDRESS, 46 | true 47 | ); 48 | return approval; 49 | } catch (error) { 50 | if (error.message && error.message.includes("User denied")) { 51 | toast.info("Transaction cancelled by user"); 52 | } else { 53 | toast.error("There was an error completing this transaction"); 54 | } 55 | return null; 56 | } 57 | } 58 | }; 59 | 60 | export const depositL1NFT = async (l1TokenAddress, l2TokenAddress, tokenId) => { 61 | if (siteConfig.NETWORK == "opt-goerli") { 62 | await assertNetwork("0x5", "Ethereum Goerli"); 63 | } else { 64 | await assertNetwork("0x1", "Ethereum"); 65 | } 66 | 67 | const signer = await getSigner(); 68 | const contract = new ethers.Contract( 69 | siteConfig.L1_BRIDGE_ADDRESS, 70 | l1ERC721BridgeABI, 71 | signer 72 | ); 73 | 74 | await setL1BridgeApprovalForAll(l1TokenAddress); 75 | 76 | try { 77 | const txn = await contract.bridgeERC721( 78 | l1TokenAddress, 79 | l2TokenAddress, 80 | parseInt(tokenId), 81 | 1_200_000, 82 | 0x0 83 | ); 84 | return txn.hash; 85 | } catch (error) { 86 | if (error.message && error.message.includes("User denied")) { 87 | toast.info("Transaction cancelled by user"); 88 | } 89 | // console.log(error); 90 | return null; 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const MARKETPLACE_FEE = 0.025; 2 | export const MARKETPLACE_PAYOUT_ADDRESS = 3 | "0xeC1557A67d4980C948cD473075293204F4D280fd"; 4 | -------------------------------------------------------------------------------- /utils/errors.ts: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | 3 | export const handleError = (error) => { 4 | console.log(error); 5 | if (error.data && error.data.message) { 6 | toast.error(`Error: ${error.data.message}`); 7 | } else if (error.message) { 8 | if (error.message.includes("User denied")) { 9 | toast.info("Transaction cancelled by user"); 10 | } else if ( 11 | error.message.includes("gas required exceeds allowance") || 12 | error.message.includes("does not have the balances") 13 | ) { 14 | toast.error( 15 | "You don't have the required balance to complete this transaction" 16 | ); 17 | } else { 18 | toast.error(`Error: ${error.message}`); 19 | } 20 | } else { 21 | toast.error(`Error: ${error}`); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /utils/exchange/sendDutchAuction.ts: -------------------------------------------------------------------------------- 1 | import { siteConfig } from "../../shared/config"; 2 | 3 | const PAUSABLE_ZONE_ADDRESS = siteConfig.PAUSABLE_ZONE; 4 | const SEAPORT_ADDRESS = siteConfig.EXCHANGE_V6; 5 | 6 | export const createSeaportDutchAuction = async ({}) => { 7 | return; 8 | }; 9 | -------------------------------------------------------------------------------- /utils/mixpanel.ts: -------------------------------------------------------------------------------- 1 | import mixpanel from "mixpanel-browser"; 2 | import { addressReducer } from "../store/address"; 3 | 4 | export const initMixpanel = () => { 5 | if (typeof window !== "undefined") { 6 | mixpanel.init("d00382e5cadf4ea620a54c210b3baa97", { debug: false }); 7 | } 8 | }; 9 | 10 | export const setMixpanelProfile = (address) => { 11 | mixpanel.identify(address); 12 | mixpanel.people.set({ address }); 13 | }; 14 | 15 | export const assetVisited = (collectionId, assetId) => { 16 | initMixpanel(); 17 | if (typeof window !== "undefined") { 18 | mixpanel.track("assetVisited", { 19 | collectionId, 20 | assetId, 21 | }); 22 | } 23 | }; 24 | 25 | export const visitHomePage = () => { 26 | initMixpanel(); 27 | mixpanel.track("visitHomePage"); 28 | }; 29 | 30 | export const collectionVisited = (collectionId) => { 31 | initMixpanel(); 32 | if (typeof window !== "undefined") { 33 | mixpanel.track("collectionVisited", { 34 | collectionId, 35 | }); 36 | } 37 | }; 38 | 39 | export const purchaseFlowStarted = (collectionId, assetId) => { 40 | initMixpanel(); 41 | if (typeof window !== "undefined") { 42 | mixpanel.track("purchaseFlowStarted", { 43 | collectionId, 44 | assetId, 45 | }); 46 | } 47 | }; 48 | 49 | export const purchaseFlowFinished = (collectionId, assetId) => { 50 | initMixpanel(); 51 | if (typeof window !== "undefined") { 52 | mixpanel.track("purchaseFlowFinished", { 53 | collectionId, 54 | assetId, 55 | }); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /utils/onboard/onboard.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { toast } from "react-toastify"; 3 | import { assertOptimism } from "../exchange/exchange"; 4 | import { getSigner } from "../wallet"; 5 | import { onboardABI } from "./onboardABI"; 6 | 7 | export const mintNFT = async (selectedChip, address) => { 8 | await assertOptimism(); 9 | 10 | const signer = await getSigner(); 11 | const contract = new ethers.Contract(address, onboardABI, signer); 12 | 13 | try { 14 | const txn = await contract.mintToken(selectedChip); 15 | return txn.hash; 16 | } catch (error) { 17 | if (error.message && error.message.includes("User denied")) { 18 | toast.info("Transaction cancelled by user"); 19 | } else if ( 20 | error.data && 21 | error.data.message.includes("Sale must be active") 22 | ) { 23 | toast.warning("The sale is not active"); 24 | } else if (error.message && error.message.includes("gas too low")) { 25 | toast.error("Transaction failed, not enough gas"); 26 | } else if ( 27 | error.data.message && 28 | error.data.message.includes("exceed max supply") 29 | ) { 30 | toast.error("This collection is sold out"); 31 | } else if ( 32 | error.data && 33 | error.data.message.includes("You can only mint one") 34 | ) { 35 | toast.warning("You can only mint one NFT from this collection"); 36 | } else { 37 | toast.error("Error minting NFT"); 38 | } 39 | return null; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /utils/rewards/rewards.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { toast } from "react-toastify"; 3 | import { fetchSellOrder } from "../../api/sellOrder"; 4 | import { siteConfig } from "../../shared/config"; 5 | import { assertOptimism } from "../exchange/exchange"; 6 | import { getSigner } from "../wallet"; 7 | import { campaignTrackerABI } from "./campaignTrackerABI"; 8 | import { rewardDeriverABI } from "./rewardDeriverABI"; 9 | 10 | const getCollectionBoostCampaignId = (collectionAddress) => { 11 | return `COLLECTION_BOOST_${collectionAddress.toLowerCase()}`; 12 | }; 13 | 14 | export const toggleCampaign = async (collectionAddress) => { 15 | await assertOptimism(); 16 | 17 | const signer = await getSigner(); 18 | const contract = new ethers.Contract( 19 | siteConfig.CAMPAIGN_TRACKER_ADDRESS, 20 | campaignTrackerABI, 21 | signer 22 | ); 23 | const formattedCollectionAddress = 24 | getCollectionBoostCampaignId(collectionAddress); 25 | 26 | try { 27 | const txn = await contract.toggleRewardsForCampaign( 28 | formattedCollectionAddress 29 | ); 30 | return txn.hash; 31 | } catch (error) { 32 | toast.error("There was an error toggling the rewards campaign"); 33 | return null; 34 | } 35 | }; 36 | 37 | export const getRewardBreakdown = async (orderId, recipient) => { 38 | const sellOrder = await fetchSellOrder(orderId); 39 | const order = JSON.parse(sellOrder.order_json); 40 | 41 | const signer = await getSigner(); 42 | const contract = new ethers.Contract( 43 | siteConfig.REWARDS_DERIVER, 44 | rewardDeriverABI, 45 | signer 46 | ); 47 | const rewards = await contract.getRewardInOP(order, recipient); 48 | return rewards; 49 | }; 50 | 51 | export const getRewardBreakdownMultipleOrders = async ( 52 | orderIds: [], 53 | recipient 54 | ) => { 55 | let orders = []; 56 | for (const orderId of orderIds) { 57 | const sellOrder = await fetchSellOrder(orderId); 58 | const order = JSON.parse(sellOrder.order_json); 59 | orders.push({ order: order }); 60 | } 61 | 62 | const signer = await getSigner(); 63 | const contract = new ethers.Contract( 64 | siteConfig.REWARDS_DERIVER, 65 | rewardDeriverABI, 66 | signer 67 | ); 68 | 69 | let rewards = []; 70 | for (const orderObj of orders) { 71 | rewards.push(await contract.getRewardInOP(orderObj.order, recipient)); 72 | } 73 | 74 | const map = {}; 75 | for (const i1 in rewards) { 76 | for (const i2 in rewards[i1]) { 77 | const rewardType = rewards[i1][i2][0]; 78 | const rewardValue = Number(rewards[i1][i2][1].toString()); 79 | if (map[rewardType]) { 80 | map[rewardType] += rewardValue; 81 | } else { 82 | map[rewardType] = rewardValue; 83 | } 84 | } 85 | } 86 | rewards = Object.keys(map).map((key) => [key, map[key]]); 87 | return rewards; 88 | }; 89 | -------------------------------------------------------------------------------- /utils/signatureUtils.ts: -------------------------------------------------------------------------------- 1 | import { getSigner } from "./wallet"; 2 | 3 | const createUpdateProfileMessage = () => { 4 | const timestamp = Date.now(); 5 | const message = `Sign this message to update your settings. It won't cost you any Ether. Timestamp: ${timestamp}`; 6 | return message; 7 | }; 8 | 9 | export const createProfileUploadSignature = async () => { 10 | const signer = await getSigner(); 11 | const message = createUpdateProfileMessage(); 12 | const signature = await signer.signMessage(message); 13 | return { message, signature }; 14 | }; 15 | 16 | const createCollectionDetailsMessage = () => { 17 | const timestamp = Date.now(); 18 | const message = `Sign this message to update the collection settings. It won't cost you any Ether. Timestamp: ${timestamp}`; 19 | return message; 20 | }; 21 | 22 | export const createCollectionDetailsSignature = async () => { 23 | const signer = await getSigner(); 24 | const message = createCollectionDetailsMessage(); 25 | const signature = await signer.signMessage(message); 26 | return { message, signature }; 27 | }; 28 | --------------------------------------------------------------------------------