├── .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 |
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 {query}
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 |
11 |
--------------------------------------------------------------------------------
/public/display-themes/covered.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/display-themes/padded.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/public/etherscan.svg:
--------------------------------------------------------------------------------
1 |
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 |
5 |
--------------------------------------------------------------------------------
/public/login/walletconnect.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------