diff --git a/api/contract/abi.ts b/api/contract/abi.ts new file mode 100644 index 0000000..20a613a --- /dev/null +++ b/api/contract/abi.ts @@ -0,0 +1,365 @@ +import { AbiItem } from 'caver-js' + +const abi: AbiItem[] = [ + { + inputs: [], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'approved', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'player', + type: 'address', + }, + { + internalType: 'string', + name: 'tokenURI', + type: 'string', + }, + ], + name: 'ensureAttackAmount', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getApproved', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '_data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'tokenURI', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +export default abi diff --git a/api/contract/controllers.ts b/api/contract/controllers.ts new file mode 100644 index 0000000..bd008a7 --- /dev/null +++ b/api/contract/controllers.ts @@ -0,0 +1,69 @@ +import Caver from 'caver-js' +import type { NextApiRequest, NextApiResponse } from 'next' +import { Blob } from 'nft.storage' +import abi from './abi' +import * as contractService from './services' + +export type PostPayload = { + attackCount: number + imageBlob: Blob +} + +export async function setMetadataUri( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === 'POST') { + try { + const { imageBlob, attackCount } = req.body + + const buf = new Buffer(imageBlob, 'base64') + + const blob = new Blob([buf], { type: 'image/png' }) + + const metadata = await contractService.getNFTMetadata(attackCount, blob) + + res.status(200).json(metadata) + } catch (error) { + console.error(error) + res.status(500).json({ error: error }) + } + } +} + +export async function ensureAttackAmount( + req: NextApiRequest, + res: NextApiResponse, +) { + if (req.method === 'POST') { + const { address, uri } = req.body + + try { + const caver = new Caver(`${process.env.RPC_URL}/${process.env.API_KEY}`) + const key = caver.wallet.keyring.createFromPrivateKey( + process.env.KEY_RING, + ) + caver.wallet.add(key) + + const contract = new caver.contract(abi, process.env.CONTRACT_ADDRESS) + + const tx = await contract.methods + .ensureAttackAmount( + address, + // key.address, + uri, + ) + .send({ + // from: address, + from: key.address, + gasPrice: '75000000000', + gas: 1000000, + }) + + res.status(200).json(tx) + } catch (error: any) { + console.log(error) + res.status(500).json({ error: error.message }) + } + } +} \ No newline at end of file diff --git a/api/contract/services.ts b/api/contract/services.ts new file mode 100644 index 0000000..28039af --- /dev/null +++ b/api/contract/services.ts @@ -0,0 +1,40 @@ +import { NFTStorage, Blob } from 'nft.storage' + +type NFT = { + image: Blob + name: string + description: string + properties: { + trait_type: string + value: number + } + authors: { + name: string + }[] +} + +type MetaDataURIs = { + ipnft: string + url: string +} + +export async function getNFTMetadata(attackCount: number, imageBlob: Blob) { + const API_KEY = process.env.NFT_STORAGE_API_KEY + + const nft: NFT = { + image: imageBlob, + name: 'Sipping a KUP of metaverse', + description: 'KUP helps developers to build a metaverse easily', + properties: { + trait_type: 'attack counter', + value: attackCount, + }, + authors: [{ name: 'KUP TEAM' }], + } + + const client = new NFTStorage({ token: API_KEY }) + + const metadata: MetaDataURIs = await client.store(nft) + + return metadata +} diff --git a/api/health/controllers.ts b/api/health/controllers.ts new file mode 100644 index 0000000..2588f1a --- /dev/null +++ b/api/health/controllers.ts @@ -0,0 +1,11 @@ +import type { NextApiRequest, NextApiResponse } from 'next' + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, +) { + // health check + if (req.method === 'GET') { + res.status(200).json({ status: 'ok' }) + } +} diff --git a/components/Button.tsx b/components/Button.tsx index 3397a82..54aeb6f 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -2,19 +2,127 @@ import { ComponentProps, useCallback } from 'react' import clsx from 'clsx' interface ButtonProps extends ComponentProps<'button'> { - variant?: 'primary' | 'secondary' | 'ghost' + variant?: 'primary' | 'secondary' | 'ghost' | 'gray' loading?: boolean } + +const LoadingIcon = () => ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+) + const Button: React.FC = ({ variant = 'primary', loading = false, onClick, children, + className, + disabled, ...rest }) => { const variantClasses = { - primary: 'bg-blue-600 hover:bg-blue-500 text-white', - secondary: 'bg-gray-300 text-gray-500', + primary: 'bg-orange text-white', + secondary: 'bg-main-default text-white', + gray: 'bg-main-lightest', ghost: 'bg-white hover:bg-gray-100 text-gray-400', } @@ -33,14 +141,18 @@ const Button: React.FC = ({ return ( ) } diff --git a/components/Navbar.tsx b/components/Navbar.tsx index b3c3499..613fa55 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -1,58 +1,73 @@ /* eslint-disable @next/next/no-img-element */ +import LoginForm from '@/features/Auth/components/LoginForm' +import useModalStore from '@/store/useModalStore' +import Link from 'next/link' import React from 'react' -import useModal from '@/hooks/useModal' -import Modal from './Modal' -import ModalPortal from './ModalPortal' import { NoSSR } from './NoSSR' - const NavBar: React.FC = () => { - const { modalOpen, openModal, closeModal } = useModal(); + const { openModal } = useModalStore() - return ( + return ( - + - ) + ) } export default NavBar \ No newline at end of file diff --git a/components/TextInput.tsx b/components/TextInput.tsx index bb3f2aa..656985a 100644 --- a/components/TextInput.tsx +++ b/components/TextInput.tsx @@ -9,7 +9,7 @@ const TextInput = React.forwardRef( ) diff --git a/features/Auth/components/LoginForm.tsx b/features/Auth/components/LoginForm.tsx index 18141e9..81351ed 100644 --- a/features/Auth/components/LoginForm.tsx +++ b/features/Auth/components/LoginForm.tsx @@ -1,70 +1,103 @@ -import { ComponentProps, Dispatch, SetStateAction, useState, useCallback } from 'react' +import { + ComponentProps, + Dispatch, + SetStateAction, + useCallback, + useState, +} from 'react' import { useForm } from 'react-hook-form' import { yupResolver } from '@hookform/resolvers/yup' import loginSchema from '@/features/Auth/utils/loginFormSchema' import TextInput from '@/components/TextInput' import Label from '@/components/Label' import Button from '@/components/Button' -import Box from '@/components/Box' -import { useRouter } from 'next/router' +import useModalStore from '@/store/useModalStore' +import SignupForm from './SignupForm' +import useEnv from '@/hooks/useEnv' +import useAuthService from '../service/useAuthService' +import WalletConnect from '@/features/Contract/components/WalletConnect' export interface LoginFormProps extends ComponentProps<'div'> { - onSubmitForm: ( - formData: Auth.Login.FormData, - setIsSubmitting: Dispatch>, - ) => void + onSubmitForm?: ( + formData: Auth.Login.FormData, + setIsSubmitting: Dispatch>, + ) => void } - + const LoginForm: React.FC = ({ onSubmitForm }) => { - const router = useRouter() + const { openModal } = useModalStore() + const { titleId } = useEnv() + const { loginWithEmailAddress } = useAuthService() - const [isSubmitting, setIsSubmitting] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false) const { register, handleSubmit } = useForm({ resolver: yupResolver(loginSchema), }) - const onSubmit = handleSubmit((data) => { + const onSubmit = handleSubmit(async (formData) => { setIsSubmitting(true) - onSubmitForm(data, setIsSubmitting) + try { + const { data } = await loginWithEmailAddress({ ...formData, titleId }) + + /** + * save the auth token in the local storage + */ + localStorage.setItem('SessionTicket', data.SessionTicket) + + /** + * go to wallet connect + */ + openModal({ + title: 'Wallet', + component: , + }) + } catch (error) { + // TODO: handle error + console.log(error) + } finally { + setIsSubmitting(false) + } }) const onClickSignUp = useCallback(() => { - router.push('/signup') + openModal({ + title: 'Sign Up', + component: , + }) }, []) return ( - -
-
- - -
-
- - -
-
- + +
+ + +
+
+ + +
+
+ - -
- - + +
+ ) } + export default LoginForm \ No newline at end of file diff --git a/features/Auth/components/SignupForm.tsx b/features/Auth/components/SignupForm.tsx index 8a8f11a..7078ae1 100644 --- a/features/Auth/components/SignupForm.tsx +++ b/features/Auth/components/SignupForm.tsx @@ -1,50 +1,82 @@ -import Box from '@/components/Box' import Button from '@/components/Button' import Label from '@/components/Label' import TextInput from '@/components/TextInput' +import useEnv from '@/hooks/useEnv' +import useModalStore from '@/store/useModalStore' import { yupResolver } from '@hookform/resolvers/yup' -import { ComponentProps, Dispatch, SetStateAction, useState } from 'react' +import { useState } from 'react' import { useForm } from 'react-hook-form' +import useAuthService from '../service/useAuthService' import signupSchema from '../utils/signupFormSchema' +import LoginForm from './LoginForm' -export interface SignupFormProps extends ComponentProps<'div'> { - onSubmitForm: ( - formData: Auth.Signup.FormData, - setIsSubmitting: Dispatch>, - ) => void -} +const SignupForm: React.FC = () => { + const { openModal } = useModalStore() + const { titleId } = useEnv() + const { registerPlayFabUser } = useAuthService() -const SignupForm: React.FC = ({ onSubmitForm }) => { const [isSubmitting, setIsSubmitting] = useState(false) const { register, handleSubmit } = useForm({ resolver: yupResolver(signupSchema), }) - const onSubmit = handleSubmit((data) => { + const handleClickSignin = () => { + openModal({ + title: 'Sign In', + component: , + }) + } + + const onSubmit = handleSubmit(async (formData) => { setIsSubmitting(true) - onSubmitForm(data, setIsSubmitting) + try { + const { data } = await registerPlayFabUser({ + ...formData, + Username: formData.DisplayName, + titleId, + }) + + if (data.SessionTicket) { + localStorage.setItem('SessionTicket', data.SessionTicket) + } + + handleClickSignin() + } catch (error) { + console.log(error) + } finally { + setIsSubmitting(false) + } }) return ( - -
-
- - -
-
- - -
-
- - -
- - -
-
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
) } export default SignupForm \ No newline at end of file diff --git a/features/Contract/components/ContractIntract.tsx b/features/Contract/components/ContractIntract.tsx index 80e9aa4..db0f933 100644 --- a/features/Contract/components/ContractIntract.tsx +++ b/features/Contract/components/ContractIntract.tsx @@ -1,4 +1,4 @@ -import Button from '@/components/Buttton' +import Button from '@/components/Button' import useModalStore from '@/store/useModalStore' import axios from 'axios' import { useEffect, useState } from 'react' diff --git a/features/Contract/components/WalletConnect.tsx b/features/Contract/components/WalletConnect.tsx index c10e5ba..53c6ef7 100644 --- a/features/Contract/components/WalletConnect.tsx +++ b/features/Contract/components/WalletConnect.tsx @@ -1,6 +1,6 @@ /* eslint-disable @next/next/no-img-element */ import { CheckCircleIcon, WalletIcon } from '@heroicons/react/24/solid' -import Button from '@/components/Buttton' +import Button from '@/components/Button' import React, { useCallback, useEffect, useState } from 'react' import useKaikas from '../hooks/useKaikas' import useKlip from '../hooks/useKlip' diff --git a/features/Landing/components/sections/Architecture.tsx b/features/Landing/components/Architecture.tsx similarity index 54% rename from features/Landing/components/sections/Architecture.tsx rename to features/Landing/components/Architecture.tsx index 6d6bc30..2a05732 100644 --- a/features/Landing/components/sections/Architecture.tsx +++ b/features/Landing/components/Architecture.tsx @@ -1,10 +1,18 @@ /* eslint-disable @next/next/no-img-element */ +import clsx from 'clsx' import { ComponentProps } from 'react' const Architecture: React.FC> = () => { return ( -
- +
+ > = () => { height={232} /> -
+
contract-path -
-

+

+

Do it!

-

+

Contract

cup
-
+
) } export default Architecture \ No newline at end of file diff --git a/features/Landing/components/Hero.tsx b/features/Landing/components/Hero.tsx new file mode 100644 index 0000000..c4afe4f --- /dev/null +++ b/features/Landing/components/Hero.tsx @@ -0,0 +1,36 @@ +/* eslint-disable @next/next/no-img-element */ +import clsx from 'clsx' +import React, { ComponentProps } from 'react' + +const Hero: React.FC> = () => { + return ( +
+
+

+ It's not my cup of tea. +

+ +

+ I want + KUP +

+
+
+ cup +
+
+ ) +} + +export default React.memo(Hero) \ No newline at end of file diff --git a/features/Landing/components/sections/WhatIsKup.tsx b/features/Landing/components/WhatIsKup.tsx similarity index 63% rename from features/Landing/components/sections/WhatIsKup.tsx rename to features/Landing/components/WhatIsKup.tsx index e4e2624..115e5ef 100644 --- a/features/Landing/components/sections/WhatIsKup.tsx +++ b/features/Landing/components/WhatIsKup.tsx @@ -1,10 +1,11 @@ /* eslint-disable @next/next/no-img-element */ +import clsx from 'clsx' import React, { ComponentProps } from 'react' const VerticalLine: React.FC = React.memo( () => { return ( -
+

> = () => { return ( -
-
+
+
Klaytn
@@ -37,6 +50,7 @@ const WhatIsKup: React.FC> = () => { alt="Unity" width={164} height={60} + className="h-6 w-auto lg:h-[60px]" /> @@ -48,16 +62,19 @@ const WhatIsKup: React.FC> = () => { alt="Unity" width={206} height={60} + className="h-6 w-auto lg:h-[60px]" />
-
-

+
+

What is KUP?

-

+

KUP stands for Klaytn, Unity, and Playfab.
+ Klaytn is a metaverse platform solution. Unity is a 3D/2D game engine. And Playfab is a backend platform for live games, also a part of Microsoft. +
it is a connection between Unity, Playfab and Klaytn to manage data while interacting within the metaverse. The partnership secures the users data ownership and preservation by processing important data diff --git a/features/Landing/components/sections/WhatWeHave.tsx b/features/Landing/components/WhatWeHave.tsx similarity index 71% rename from features/Landing/components/sections/WhatWeHave.tsx rename to features/Landing/components/WhatWeHave.tsx index cc1507a..71f718a 100644 --- a/features/Landing/components/sections/WhatWeHave.tsx +++ b/features/Landing/components/WhatWeHave.tsx @@ -1,24 +1,32 @@ /* eslint-disable @next/next/no-img-element */ +import clsx from 'clsx' import { ComponentProps } from 'react' const WhatWeHave: React.FC> = () => { return ( -

+ ) } diff --git a/features/Landing/components/sections/WhyKup.tsx b/features/Landing/components/WhyKup.tsx similarity index 58% rename from features/Landing/components/sections/WhyKup.tsx rename to features/Landing/components/WhyKup.tsx index b4c430d..6f4345c 100644 --- a/features/Landing/components/sections/WhyKup.tsx +++ b/features/Landing/components/WhyKup.tsx @@ -1,14 +1,21 @@ /* eslint-disable @next/next/no-img-element */ +import clsx from 'clsx' import { ComponentProps } from 'react' -const WhyKup: React.FC> = () => { +const WhyKup: React.FC> = () => { return ( -
-
-

+
+
+

Why KUP?

-

+

For a metaverse to succeed, it must provide as much content as possible so that users can enjoy content seamlessly and without much repetition. @@ -22,15 +29,14 @@ const WhyKup: React.FC> = () => {

pulley > = () => { alt="Klaytn" width={140} height={42} - className="absolute top-40 left-32" + className="absolute top-20 left-16 lg:top-40 lg:left-32 w-[70px] lg:w-[140px] lg:h-[42px]" /> Klaytn> = () => { alt="cloud" width={380} height={148} - className="absolute bottom-0 right-0 z-0 animate-buoyancy-1" + className="absolute bottom-0 right-0 z-0 animate-buoyancy-1 w-[190px] lg:w-[380px] lg:h-[148px]" />
-

+

) } diff --git a/features/Landing/components/sections/Hero.tsx b/features/Landing/components/sections/Hero.tsx deleted file mode 100644 index 97ca1ff..0000000 --- a/features/Landing/components/sections/Hero.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ -import React, { ComponentProps } from 'react' - -const Hero: React.FC> = () => { - return ( -
-
-

It's not my cup of tea.

- -

- I want - KUP -

-
-
- cup -
-
- ) -} - -export default React.memo(Hero) \ No newline at end of file diff --git a/features/Landing/views/Landing.tsx b/features/Landing/views/Landing.tsx index ed91ff9..1d0d3b8 100644 --- a/features/Landing/views/Landing.tsx +++ b/features/Landing/views/Landing.tsx @@ -1,24 +1,42 @@ +import Head from 'next/head' + import Layout from '@/components/Layout' -import React, { useEffect } from 'react' -import FullPageScroll from '../components/FullPageScroll' +import Modal from '@/components/Modal' +import ModalPortal from '@/components/ModalPortal' +import { NoSSR } from '@/components/NoSSR' +import useModalStore from '@/store/useModalStore' +import React from 'react' -import Architecture from '../components/sections/Architecture' -import Hero from '../components/sections/Hero' -import WhatIsKup from '../components/sections/WhatIsKup' -import WhatWeHave from '../components/sections/WhatWeHave' -import WhyKup from '../components/sections/WhyKup' +import Architecture from '../components/Architecture' +import Hero from '../components/Hero' +import WhatIsKup from '../components/WhatIsKup' +import WhatWeHave from '../components/WhatWeHave' +import WhyKup from '../components/WhyKup' const Landing: React.FC = () => { - useEffect(() => {}, []) + const { modal, closeModal } = useModalStore() return ( - - - - - - - + + + KUP + + + + + + + + + {modal.active && ( + + + {modal?.component} + + + )} + + ) } diff --git a/hooks/useModal.ts b/hooks/useModal.ts deleted file mode 100644 index c9327cb..0000000 --- a/hooks/useModal.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useCallback, useState } from 'react' - -export default function useModal() { - const [modalOpen, setModalOpen] = useState(false) - - const openModal = useCallback(() => { - setModalOpen(true) - }, []) - - const closeModal = useCallback(() => { - setModalOpen(false) - }, []) - - return { - modalOpen, - openModal, - closeModal, - } -} \ No newline at end of file diff --git a/next.config.js b/next.config.js index ae88795..491e9ba 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true, + reactStrictMode: false, swcMinify: true, } diff --git a/pages/_app.tsx b/pages/_app.tsx index 6b20a44..2e976fa 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -7,7 +7,6 @@ function App({ Component, pageProps }: AppProps) { return ( - ) } diff --git a/pages/api/contract/metadata.ts b/pages/api/contract/metadata.ts new file mode 100644 index 0000000..df44e3c --- /dev/null +++ b/pages/api/contract/metadata.ts @@ -0,0 +1,3 @@ +import { setMetadataUri } from '@/api/contract/controllers' + +export default setMetadataUri diff --git a/public/wallets/coinbase.png b/public/wallets/coinbase.png new file mode 100644 index 0000000..8e057ea Binary files /dev/null and b/public/wallets/coinbase.png differ diff --git a/public/wallets/kaikas.png b/public/wallets/kaikas.png new file mode 100644 index 0000000..0b998e1 Binary files /dev/null and b/public/wallets/kaikas.png differ diff --git a/public/wallets/klip.png b/public/wallets/klip.png new file mode 100644 index 0000000..ea23c10 Binary files /dev/null and b/public/wallets/klip.png differ diff --git a/store/useModalStore.ts b/store/useModalStore.ts new file mode 100644 index 0000000..d2a825c --- /dev/null +++ b/store/useModalStore.ts @@ -0,0 +1,57 @@ +import React from 'react' +import { atom, useRecoilState, useRecoilValue } from 'recoil' + +export interface ModalAtom { + active: boolean + title?: string + component?: React.ReactNode +} + +const modalAtom = atom({ + key: 'modalAtom', + default: { + active: false, + }, +}) + +function useModalStore() { + const [modal, setModal] = useRecoilState(modalAtom) + + const toggleModal = () => { + setModal({ + ...modal, + active: !modal.active, + }) + } + + const openModal = ({ + title, + component, + }: { + title?: string + component?: React.ReactNode + }) => { + setModal({ + ...modal, + ...(title && { title }), + ...(component && { component }), + active: true, + }) + } + + const closeModal = () => { + setModal({ + ...modal, + active: false, + }) + } + + return { + modal: useRecoilValue(modalAtom), + openModal, + closeModal, + toggleModal, + } +} + +export default useModalStore diff --git a/styles/globals.css b/styles/globals.css index 4d427c6..e5a6cac 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -4,6 +4,10 @@ @tailwind components; @tailwind utilities; +html { + scroll-behavior: smooth; +} + @layer utilities { .flex-center { display: flex; @@ -13,7 +17,7 @@ .full-screen { width: 100vw; - height: 100vh; + min-height: 100vh; overflow: hidden; padding-top: 30px; padding-bottom: 30px; diff --git a/tailwind.config.js b/tailwind.config.js index ac29521..9408ce3 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,9 +1,9 @@ /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - "./pages/**/*.{js,ts,jsx,tsx}", - "./components/**/*.{js,ts,jsx,tsx}", - "./features/**/*.{js,ts,jsx,tsx}", + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + './features/**/*.{js,ts,jsx,tsx}', ], theme: { fontFamily: { @@ -12,18 +12,18 @@ module.exports = { extend: { height: { 'nav-h': '100px', - 'nav-mobile': '50px', + 'nav-mobile': '80px', }, padding: { 'nav-h': '100px', - 'nav-mobile': '50px', + 'nav-mobile': '80px', }, colors: { main: { default: '#4F473B', dark: '#27231D', light: '#A7A39D', - ligtest: '#EDECEB', + lightest: '#EDECEB', }, tequila: { light: '#E35500', @@ -52,4 +52,4 @@ module.exports = { }, }, plugins: [], -}; +} \ No newline at end of file diff --git a/types/env.d.ts b/types/env.d.ts index 7b069dc..b33486a 100644 --- a/types/env.d.ts +++ b/types/env.d.ts @@ -1,5 +1,10 @@ declare namespace NodeJS { interface ProcessEnv { NEXT_PUBLIC_PLAYFAB_TITLEID: string + NFT_STORAGE_API_KEY: string + CONTRACT_ADDRESS: string + RPC_URL: string + API_KEY: string + KEY_RING: string } } \ No newline at end of file