initial commit

This commit is contained in:
=Mtg_Dev
2021-11-16 22:04:55 +02:00
parent 5534632d5b
commit eaad4f2ffe
35 changed files with 3220 additions and 98 deletions

View File

@@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -1,9 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@@ -1,26 +1,14 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import Navbar from "./Components/Shared/Navbar/Navbar";
import ExplorePage from "./Components/ExplorePage/ExplorePage";
import Modal from "./Components/Shared/Modal/Modal";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
return <div id="app" className='w-screen overflow-hidden'>
<Navbar />
<ExplorePage />
<Modal />
</div>;
}
export default App;

View File

@@ -0,0 +1,51 @@
import { motion } from "framer-motion";
import { ModalCard, modalCardVariants } from "../Shared/Modal/Modal";
interface Props extends ModalCard {
}
export default function Step1({ onClose, direction }: Props) {
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial={'initial'}
animate="animate"
exit='exit'
className="rounded-[40px] bg-gray-50 overflow-hidden w-full"
>
<div className="p-24">
<div className="flex gap-24 items-center">
<div className="flex-shrink-0 w-[93px] h-[93px] rounded-md overflow-hidden">
<img className="w-full h-full object-cover" src="https://s3-alpha-sig.figma.com/img/be1b/cd75/1baa911b3875134c0889d6755c4ba2cb?Expires=1637539200&Signature=QExmgJCGGSES~zIwM-2G8yd7aPR-j5eFnV3tOg6BkSdXVB9AMhHQPbRpbfOv~rD3hdMdSPMkS9kfjyFbAuonltV2zrf5GOwGxrF2GVdhpIGc6RiqGLWVVY8mXysEm6~0fVj~2SK8hec~YnV1h0oHDQiZF5YjGi143pImGmcVERPpB7MiksSoD0Vki6RXamySopj~f-~lUGy2uKRbQKxQ4LCFTz-H9O8vpkZpCVq274FYsqsEtUihwVjniNXV8ukLxdL~rfgf8L9MeiR7gDYYQ9MSLMZKEa~TnQ-JadlngQz78a2T801WaG2xp5hGHYQMtIi1ES-N4FOg5PwEjtIetA__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA" alt="" />
</div>
<div>
<h3 className="text-h3 font-regular">Project Name</h3>
<a className="text-blue-400 font-regular text-body4" href="/">www.project.com</a>
</div>
<div className="flex ml-auto gap-16">
<button className="btn btn-primary py-12 px-24 rounded-lg my-16">Play 🕹</button>
<button className="btn py-12 px-24 rounded-lg my-16">Vote 🔥</button>
</div>
</div>
<p className="mt-40 text-body4 leading-normal">Lorem ipsum dolor, sit amet consectetur adipisicing elit. At natus perferendis sunt suscipit libero amet praesentium? Magni minus libero maxime aspernatur eius, repudiandae distinctio aut perferendis laboriosam impedit reiciendis blanditiis cum alias hic, ipsam facere obcaecati, amet atque numquam doloribus in explicabo possimus autem! Ipsum consequatur, dignissimos minima esse illum obcaecati aliquid eligendi delectus architecto beatae perferendis. Ipsam, non maxime.</p>
<div className="flex gap-24 mt-24 flex-wrap">
<span className="chip-small bg-red-100 text-red-800 font-regular"> payments </span>
<span className="chip-small bg-primary-100 text-primary-800 font-regular"> lightining </span>
</div>
<div className="mt-40">
<h3 className="text-h5 font-bold">Screen Shots</h3>
<div className="flex gap-x-24 gap-y-20">
<div className="flex-none w-[164px] h-[120px] md:w-[300px] md:h-[136px] bg-gray-300 rounded-xl"></div>
<div className="flex-none w-[164px] h-[120px] md:w-[300px] md:h-[136px] bg-gray-300 rounded-xl"></div>
<div className="flex-none w-[164px] h-[120px] md:w-[300px] md:h-[136px] bg-gray-300 rounded-xl"></div>
</div>
</div>
</div>
</motion.div>
)
}

View File

@@ -0,0 +1,16 @@
import Categories from "./partials/Categories";
import Header from "./partials/Header";
import ProjectsSection from "./partials/ProjectsSection";
export default function ExplorePage() {
return (
<div className='px-32'>
<Header />
<div className="my-40">
<Categories />
</div>
<ProjectsSection />
</div>
)
}

View File

@@ -0,0 +1,20 @@
import { getAllCategories } from "../../../api"
import { useQuery } from 'react-query'
export default function Categories() {
const { data, isLoading } = useQuery("categories", getAllCategories);
const handleClick = (categoryId: string) => {
}
if (isLoading)
return null;
return (
<div className="flex gap-12 flex-wrap">
{data?.map(category => <span key={category.id} className="chip hover:cursor-pointer hover:bg-gray-200" onClick={() => handleClick(category.id)}>{category.title}</span>)}
</div>
)
}

View File

@@ -0,0 +1,56 @@
const headerLinks = [
{
title: "A fun directory of Lightning Enabled Applications on the Open Web",
img:
"https://s3-alpha-sig.figma.com/img/07b8/5d84/145942255afd215b3da26dbbf1dd03bd?Expires=1638144000&Signature=Cl1DUQJIUsrrFi48M~qU1r3Z0agGdy-uiNUao5g8-nu34QtoyWTFPXvaH3naSZBYqcPyKFq1jaXF6Mw1uj1hdWwGhXhMPLnKNJFFrGViVXhXu-3YeCPY9p4-IcieFJBZPVA~zDY8zxY5b06loWsINAVx4eMHRAhSWl~~Mca5PjlSXloiYrT00W-6c9m8gevfMMX~PsHQedzwYzg0j2DlnhPX8LbRkli1G2OxtCaFwo3~HGHXIlFGuGU1uXRvi1qBWrdjdsuWgIly1ekcFfJWAKmwYXk06EtCmfWRgGYbD7cBK~lwOkFofbf1LW0yqLv0hr4svwToH~3FiHenrCF-1g__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA",
link: {
content: "Submit App",
url: "#",
},
},
{
title:
"Join the next wave of the Lightning Network in Novembers Shock the Web hackathon",
img: 'https://s3-alpha-sig.figma.com/img/be1b/cd75/1baa911b3875134c0889d6755c4ba2cb?Expires=1637539200&Signature=QExmgJCGGSES~zIwM-2G8yd7aPR-j5eFnV3tOg6BkSdXVB9AMhHQPbRpbfOv~rD3hdMdSPMkS9kfjyFbAuonltV2zrf5GOwGxrF2GVdhpIGc6RiqGLWVVY8mXysEm6~0fVj~2SK8hec~YnV1h0oHDQiZF5YjGi143pImGmcVERPpB7MiksSoD0Vki6RXamySopj~f-~lUGy2uKRbQKxQ4LCFTz-H9O8vpkZpCVq274FYsqsEtUihwVjniNXV8ukLxdL~rfgf8L9MeiR7gDYYQ9MSLMZKEa~TnQ-JadlngQz78a2T801WaG2xp5hGHYQMtIi1ES-N4FOg5PwEjtIetA__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA', link: {
content: "Register Now",
url: "#",
},
},
];
export default function Header() {
return (
<div className="flex gap-40 justify-center md:justify-between">
<div className="flex-grow rounded-20 h-[280px] relative overflow-hidden p-24 flex flex-col items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[0].img}
alt=""
/>
<div className="w-full h-full object-cover bg-black opacity-40 absolute top-0 left-0 z-[-1]"></div>
<h3 className="text-white text-h3 max-w-md">{headerLinks[0].title}</h3>
<a
href={headerLinks[0].link.url}
className="btn btn-primary font-regular mt-36"
>
{headerLinks[0].link.content}
</a>
</div>
<div className="hidden flex-grow rounded-20 h-[280px] relative overflow-hidden p-24 md:flex flex-col items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[1].img}
alt=""
/>
<div className="w-full h-full object-cover bg-black opacity-40 absolute top-0 left-0 z-[-1]"></div>
<h3 className="text-white text-h3 max-w-md">
{headerLinks[1].title}
</h3>
<a href={headerLinks[1].link.url} className="btn font-regular mt-36">
{headerLinks[1].link.content}
</a>
</div>
</div>
);
}

View File

@@ -0,0 +1,56 @@
import { ReactElement, useRef } from "react";
import { ProjectCard } from "../../../utils/interfaces";
import Carousel from 'react-multi-carousel';
import { MdLocalFireDepartment } from 'react-icons/md';
import { useAppDispatch } from "../../../utils/hooks";
import { ModalId, openModal } from "../../../redux/features/modals.slice";
const responsive = {
all: {
breakpoint: { max: 5000, min: 0 },
items: (((window.innerWidth - 64) / (296 + 24))),
}
}
interface Props { title: string | ReactElement, projects: ProjectCard[] }
export default function ProjectsRow({ title, projects }: Props) {
const dispatch = useAppDispatch()
let drag = useRef(false);
document.addEventListener('mousedown', () => drag.current = false);
document.addEventListener('mousemove', () => drag.current = true);
const handleClick = (projectId: string) => {
projectId = '123123123';
if (!drag.current)
dispatch(openModal({ modalId: ModalId.Project, initialModalProps: { projectId } }))
}
return (
<div className='mb-40 overflow-hidden'>
<h3 className="font-bolder text-body3 mb-20">{title}</h3>
<Carousel
className='py-10'
showDots={false}
arrows={false}
responsive={responsive}
>
{projects.map((project, idx) => <div key={idx} className="select-none px-16 py-16 flex w-[296px] gap-16 border-gray-100 shadow-md border-2 rounded-10 transform transition-transform hover:cursor-pointer hover:scale-105" onClick={() => handleClick(project.id)}>
<img src={project.img} draggable="false" className="flex-shrink-0 w-80 h-80 bg-gray-200 border-0 rounded-8"></img>
<div className="justify-around items-start min-w-0">
<p className="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap">{project.title}</p>
<p className="text-body5 text-gray-600 font-light my-[5px]">{project.category.title}</p>
<span className="chip-small bg-yellow-100 text-yellow-700 font-light text-body5 py-[3px] px-10"> <MdLocalFireDepartment className='inline-block text-fire transform text-body4 align-middle' /> {project.votes_count} </span>
</div>
</div>)}
</Carousel>
</div>
)
}

View File

@@ -0,0 +1,20 @@
import { useQuery } from "react-query";
import { getLatestProjects } from "../../../api"
import ProjectsRow from "./ProjectsRow";
import { AiFillThunderbolt } from 'react-icons/ai';
export default function ProjectsSection() {
const { data, isLoading } = useQuery("latest-projects", getLatestProjects)
if (isLoading || !data) return null;
return (
<>
<ProjectsRow title={<h3 className="font-bolder text-body3 mb-20">Hottest <AiFillThunderbolt className='inline-block text-yellow-400 transform scale-125' /> apps</h3>
} projects={data[0].projects} />
{data.slice(1).map(({ title, projects }) => <ProjectsRow key={title} title={title} projects={projects} />)}
</>
)
}

View File

@@ -0,0 +1,21 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from '../Shared/Modal/Modal'
export default function LoginCard_1({ onClose, direction, ...props }: ModalCard) {
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[400px] h-[400px]"
>
<div className="p-24 h-full flex flex-col items-center justify-center">
<h3 className="text-h2 font-bold">Login Modal</h3>
<p className="text-body2 mt-40">WIP</p>
</div>
</motion.div>
)
}

View File

@@ -0,0 +1,78 @@
import { motion } from 'framer-motion'
import { BiArrowBack } from 'react-icons/bi'
import { BsJoystick } from 'react-icons/bs'
import { MdLocalFireDepartment } from 'react-icons/md';
import { ModalCard, modalCardVariants } from '../Shared/Modal/Modal';
import { useQuery } from 'react-query';
import { getProjectById } from '../../api';
import { useAppDispatch } from '../../utils/hooks';
import { Direction, ModalId, openModal, setDirection } from '../../redux/features/modals.slice';
export default function ProjectCard({ onClose, direction, ...props }: ModalCard) {
const { data: project, isLoading } = useQuery(['get-project', props.projectId], () => getProjectById(props.projectId))
const dispatch = useAppDispatch();
if (isLoading || !project) return <></>;
const onClaim = () => {
dispatch(setDirection(Direction.NEXT));
dispatch(openModal({ modalId: ModalId.Login1, initialModalProps: { projectId: props.projectId } }))
}
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card"
>
<div className="relative h-[152px]">
<img className="w-full h-full object-cover" src={project.cover_image} alt="" />
<button className="w-[48px] h-[48px] bg-white absolute top-1/2 left-32 -translate-y-1/2 rounded-full hover:bg-gray-200 text-center" onClick={onClose}><BiArrowBack className=' inline-block text-body1' /></button>
</div>
<div className="p-24">
<div className="flex gap-24 items-center">
<div className="flex-shrink-0 w-[93px] h-[93px] rounded-md overflow-hidden">
<img className="w-full h-full object-cover" src={project.thumbnail_image} alt="" />
</div>
<div>
<h3 className="text-h3 font-regular">{project.title}</h3>
<a className="text-blue-400 font-regular text-body4" target='_blank' rel="noreferrer" href={project.website}>{project.website}</a>
</div>
<div className="flex ml-auto gap-16">
<button className="btn btn-primary py-12 px-24 rounded-lg my-16">Play <BsJoystick /></button>
<button className="btn bg-yellow-100 hover:bg-yellow-200 py-12 px-24 rounded-lg my-16">Vote <MdLocalFireDepartment className='text-fire' /></button>
</div>
</div>
<p className="mt-40 text-body4 leading-normal">{project.description}</p>
<div className="flex gap-24 mt-24 flex-wrap">
<span className="chip-small bg-red-100 text-red-800 font-regular"> payments </span>
<span className="chip-small bg-primary-100 text-primary-800 font-regular"> lightining </span>
</div>
<div className="mt-40">
<h3 className="text-h5 font-bold">Screen Shots</h3>
<div className="grid grid-cols-1 justify-items-center md:grid-cols-2 gap-x-24 gap-y-20">
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
</div>
</div>
<hr className="my-40" />
<div className="text-center">
<h3 className="text-body4 font-regular">Are you the creator of this project?</h3>
<button className="btn py-12 px-24 rounded-lg my-16" onClick={onClaim}>Claim 🖐</button>
</div>
</div>
</motion.div>
)
}

View File

@@ -0,0 +1,75 @@
import { AnimatePresence, motion } from "framer-motion";
import { ReactElement, useEffect, useRef } from "react";
import { Direction } from "../../../redux/features/modals.slice";
import { Portal } from "../Portal/Portal";
export interface ModalCard {
onClose?: () => void;
direction: number;
modalCard: ReactElement
}
export const modalCardVariants = {
initial: (direction: any) => {
if (direction === Direction.START) return { opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { opacity: 0, x: 300 };
else if (direction === Direction.PREVIOUS) return { opacity: 0, x: -300 };
return {}
},
animate: {
x: 0, y: 0, opacity: 1, transition: { type: "spring" }
},
exit: (direction: Direction) => {
const transition = { ease: "easeIn" }
if (direction === Direction.EXIT) return { transition, opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { transition, opacity: 0, x: -300 };
else if (direction === Direction.PREVIOUS) return { transition, opacity: 0, x: 300 };
return {}
},
}
interface Props {
isOpen?: boolean;
onClose?: () => void,
maxWidth?: string;
children: ReactElement
}
export default function Modal({ children, isOpen = true, onClose = () => { }, maxWidth = "max-w-[600px]" }: Props) {
const ref = useRef<HTMLDivElement>(null);
const handleOutsideClick = (e: any) => {
if (e.target === ref.current) onClose();
}
useEffect(() => {
if (isOpen) document.body.style.overflowY = "hidden";
else document.body.style.overflowY = "initial";
}, [isOpen])
return (
<Portal>
<AnimatePresence exitBeforeEnter>
{isOpen && <motion.div
ref={ref}
onClick={handleOutsideClick}
className={`fixed overscroll-y-none inset-0 bg-gray-300 bg-opacity-70 overflow-y-auto h-full w-full`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
transition: { ease: "easeInOut" },
}}
>
<div
className={`w-full ${maxWidth} mx-auto py-64`}
>
{children}
</div>
</motion.div>}
</AnimatePresence>
</Portal>
)
}

View File

@@ -0,0 +1,100 @@
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useRef } from "react";
import { closeModal, Direction, ModalId } from "../../../redux/features/modals.slice";
import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
import LoginCard_1 from "../../Login/LoginCard-1";
import ProjectCard from "../../Project/ProjectCard";
import { Portal } from "../Portal/Portal";
export interface ModalCard {
onClose?: () => void;
direction: number;
[key: string]: any;
}
export const modalCardVariants = {
initial: (direction: any) => {
if (direction === Direction.START) return { opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { opacity: 0, x: 300 };
else if (direction === Direction.PREVIOUS) return { opacity: 0, x: -300 };
return {}
},
animate: {
x: 0, y: 0, opacity: 1, transition: { type: "spring" }
},
exit: (direction: Direction) => {
const transition = { ease: "easeIn" }
if (direction === Direction.EXIT) return { transition, opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { transition, opacity: 0, x: -300 };
else if (direction === Direction.PREVIOUS) return { transition, opacity: 0, x: 300 };
return {}
},
}
const ModalsMap = (modalId: ModalId) => {
switch (modalId) {
case ModalId.Project:
return ProjectCard;
case ModalId.Login1:
return LoginCard_1;
default:
return () => <></>
}
}
export default function Modal() {
const { isOpen, modalId, direction, initialModalProps } = useAppSelector(state => ({ isOpen: state.modals.isOpen, modalId: state.modals.openModalId, direction: state.modals.direction, initialModalProps: state.modals.initialModalProps }))
const ref = useRef<HTMLDivElement>(null);
const dispatch = useAppDispatch();
const onClose = () => dispatch(closeModal());
const handleOutsideClick = (e: any) => {
console.log(e.target);
if (e.target === ref.current) onClose();
}
useEffect(() => {
if (isOpen) document.body.style.overflowY = "hidden";
else document.body.style.overflowY = "initial";
}, [isOpen]);
const Child = ModalsMap(modalId);
return (
<Portal>
<AnimatePresence exitBeforeEnter>
{isOpen && <motion.div
onClick={handleOutsideClick}
className={`fixed overscroll-y-none overflow-x-hidden inset-0 bg-gray-300 bg-opacity-70 overflow-y-auto h-full w-full`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
transition: { ease: "easeInOut" },
}}
>
<div
ref={ref}
className={`w-screen min-h-screen flex justify-center items-center py-64`}
>
<AnimatePresence exitBeforeEnter>
<Child onClose={onClose} direction={direction} {...initialModalProps} />
</AnimatePresence>
</div>
</motion.div>}
</AnimatePresence>
</Portal>
)
}

View File

@@ -0,0 +1,83 @@
import { motion } from "framer-motion";
import { useState } from "react";
import { FiMenu } from 'react-icons/fi';
import { GrClose } from 'react-icons/gr';
import { BsSearch } from 'react-icons/bs'
import { navLinks } from "./Navbar";
import { AiFillThunderbolt } from 'react-icons/ai';
const navBtnVariant = {
menuHide: { rotate: 90, opacity: 0 },
menuShow: { rotate: 0, opacity: 1 },
closeHide: { rotate: -90, opacity: 0 },
closeShow: { rotate: 0, opacity: 1 },
}
const navListVariants = {
init: { x: 0 },
show: { x: "-100%" },
hide: { x: 0 }
}
export default function NavMobile() {
const [open, setOpen] = useState(false)
const handleClick = () => {
if (open)
document.body.classList.remove('overflow-y-hidden')
else
document.body.classList.add('overflow-y-hidden')
setOpen(open => !open);
}
return (
<nav className='block lg:hidden overflow-hidden z-[2010]'>
<div className="p-16 w-screen flex items-center">
<div className="w-40 h-40 bg-gray-100 rounded-8 mr-16 overflow-hidden">
<img className="w-full h-full object-cover" src="https://www.figma.com/file/OFowr5RJk9YZCW35KT7D5K/image/07b85d84145942255afd215b3da26dbbf1dd03bd?fuid=772401335362859303" alt="" />
</div>
<h2 className="text-h5 font-bold mr-64">makers.bolt.fun</h2>
<button className='rounded-full ml-auto mr-16 text-2xl w-[50px] h-[50px] hover:bg-gray-200' onClick={handleClick}>
{!open ? (<motion.div key={open ? 1 : 0} variants={navBtnVariant} initial='menuHide' animate='menuShow'><FiMenu /></motion.div>)
: (<motion.div key={open ? 1 : 0} variants={navBtnVariant} initial='closeHide' animate='closeShow'><GrClose /></motion.div>)}
</button>
</div>
<div className="fixed overflow-hidden left-0 pointer-events-none z-[2010] w-full min-h-[calc(100vh-76px)]">
{open && <div onClick={handleClick} className='pointer-events-auto absolute left-0 w-full min-h-full bg-gray-400 opacity-20'>
</div>}
<motion.div
className="pointer-events-auto bg-white w-full sm:max-w-[400px] min-h-full absolute left-full border shadow-2xl sm:p-32 flex flex-col"
variants={navListVariants}
animate={open ? "show" : "hide"}
>
<div className="px-16">
<div className='relative'>
<BsSearch className='absolute top-1/2 left-20 transform -translate-x-1/2 -translatey-1/2 text-gray-500' />
<input className="btn bg-gray-100 w-full py-12 px-40 rounded-24 mt-16 placeholder-gray-500" placeholder="Search" />
</div>
<button className="btn btn-primary w-full py-12 px-40 rounded-24 my-16">Submit <AiFillThunderbolt className='inline-block text-yellow-400 transform scale-125' /> app</button>
</div>
<ul className="py-16 gap-64 border-t">
{navLinks.map((link, idx) => <li key={idx} className="text-body3 p-16 hover:bg-gray-200">
<a href={link.url}><link.icon className={`text-body2 inline-block mr-12 ${link.color}`} /> {link.text} </a></li>
)}
</ul>
<ul className="px-16 py-16 pb-32 flex flex-wrap gap-y-12 border-t mt-auto">
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">About Us</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Support</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Press</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Contacts</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Careers</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Sitemap</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Legal</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Cookies Settings</a></li>
</ul>
</motion.div>
</div>
</nav>
)
}

View File

@@ -0,0 +1,36 @@
import NavMobile from "./NavMobile";
import { FaHome } from 'react-icons/fa';
import { MdLocalFireDepartment } from 'react-icons/md';
import { IoExtensionPuzzle } from 'react-icons/io5';
import { AiFillThunderbolt } from 'react-icons/ai';
export const navLinks = [
{ text: "Explore", url: "/", icon: FaHome, color: 'text-primary-600' },
{ text: "Hottest", url: "/", icon: MdLocalFireDepartment, color: 'text-primary-600' },
{ text: "Categories", url: "/", icon: IoExtensionPuzzle, color: 'text-primary-600' },
]
export default function Navbar() {
return (
<>
<NavMobile />
{/* Desktop Nav */}
<nav className="hidden lg:flex py-36 px-32 items-center">
<h2 className="text-h5 font-bold mr-40 lg:mr-64">makers.bolt.fun</h2>
<ul className="flex gap-32 lg:gap-64">
{navLinks.map((link, idx) => <li key={idx} className="text-body4 hover:text-primary-600">
<a href={link.url}><link.icon className={`text-body2 align-middle inline-block mr-8 ${link.color}`} /> {link.text}</a></li>
)}
</ul>
<div className="ml-auto">
<button className="btn btn-primary py-12 px-32 lg:px-40">Submit <AiFillThunderbolt className='inline-block text-yellow-400 transform scale-125' /> app</button>
<span className="chip mx-12 h-full mr-24 p-12"><AiFillThunderbolt className='inline-block text-yellow-400 transform scale-125' /> 2.2k sats </span>
</div>
</nav>
</>
)
}

View File

@@ -0,0 +1,17 @@
import React from "react";
import ReactDOM from "react-dom";
export const Portal = ({ children, className = "root-portal", el = "div" }) => {
const [container] = React.useState(document.createElement(el));
container.classList.add(className);
React.useEffect(() => {
document.body.appendChild(container);
return () => {
document.body.removeChild(container);
};
}, [container]);
return ReactDOM.createPortal(children, container);
};

32
src/api/index.ts Normal file
View File

@@ -0,0 +1,32 @@
import { Project, ProjectCard, ProjectCategory } from "../utils/interfaces";
import data from "./mockData.json";
export async function getAllCategories(): Promise<ProjectCategory[]> {
return data.categories;
}
export async function getHottestProjects(): Promise<ProjectCard[]> {
return data.projectsCards;
}
export async function getProjectsByCategory(
categoryId: string
): Promise<ProjectCard[]> {
return data.projectsCards;
}
// returns the latest bunch of projects in each ( or some ) categories, and returns the hottest projects
export async function getLatestProjects(): Promise<
{ title: string; projects: ProjectCard[] }[]
> {
return [{ title: "hottest_apps", projects: data.projectsCards }].concat(
data.categories.slice(0, 2).map((cat) => ({
title: cat.title,
projects: data.projectsCards,
}))
);
}
export async function getProjectById(projectId: string): Promise<Project> {
return data.project;
}

131
src/api/mockData.json Normal file
View File

@@ -0,0 +1,131 @@
{
"categories": [
{
"id": "123",
"title": "Art & Collectibles"
},
{
"id": "124",
"title": "DeFi"
},
{
"id": "311",
"title": "Entertainment"
},
{
"id": "333",
"title": "Exchange"
},
{
"id": "223",
"title": "News"
},
{
"id": "451",
"title": "Shop"
},
{
"id": "2321",
"title": "Social"
},
{
"id": "51231",
"title": "Wallet"
},
{
"id": "1321",
"title": "Other"
}
],
"projectsCards": [
{
"id": "123123",
"title": "First App",
"img": "https://via.placeholder.com/150",
"category": {
"id": "51231",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "765454",
"title": "Second App",
"img": "https://via.placeholder.com/150",
"category": {
"id": "51231",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "2111134",
"title": "Third App",
"img": "https://via.placeholder.com/150",
"category": {
"id": "51231",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "12344123",
"title": "Fourth App",
"img": "https://via.placeholder.com/150",
"category": {
"id": "51231",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "56745",
"title": "Fifth App",
"img": "https://via.placeholder.com/150",
"category": {
"id": "51231",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "3312431",
"title": "Sixth App",
"img": "https://via.placeholder.com/150",
"category": {
"id": "51231",
"title": "{app.category}"
},
"votes_count": 123
}
],
"project": {
"id": "123123123",
"cover_image": "https://s3-alpha-sig.figma.com/img/07b8/5d84/145942255afd215b3da26dbbf1dd03bd?Expires=1638144000&Signature=Cl1DUQJIUsrrFi48M~qU1r3Z0agGdy-uiNUao5g8-nu34QtoyWTFPXvaH3naSZBYqcPyKFq1jaXF6Mw1uj1hdWwGhXhMPLnKNJFFrGViVXhXu-3YeCPY9p4-IcieFJBZPVA~zDY8zxY5b06loWsINAVx4eMHRAhSWl~~Mca5PjlSXloiYrT00W-6c9m8gevfMMX~PsHQedzwYzg0j2DlnhPX8LbRkli1G2OxtCaFwo3~HGHXIlFGuGU1uXRvi1qBWrdjdsuWgIly1ekcFfJWAKmwYXk06EtCmfWRgGYbD7cBK~lwOkFofbf1LW0yqLv0hr4svwToH~3FiHenrCF-1g__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA",
"thumbnail_image": "https://s3-alpha-sig.figma.com/img/be1b/cd75/1baa911b3875134c0889d6755c4ba2cb?Expires=1637539200&Signature=QExmgJCGGSES~zIwM-2G8yd7aPR-j5eFnV3tOg6BkSdXVB9AMhHQPbRpbfOv~rD3hdMdSPMkS9kfjyFbAuonltV2zrf5GOwGxrF2GVdhpIGc6RiqGLWVVY8mXysEm6~0fVj~2SK8hec~YnV1h0oHDQiZF5YjGi143pImGmcVERPpB7MiksSoD0Vki6RXamySopj~f-~lUGy2uKRbQKxQ4LCFTz-H9O8vpkZpCVq274FYsqsEtUihwVjniNXV8ukLxdL~rfgf8L9MeiR7gDYYQ9MSLMZKEa~TnQ-JadlngQz78a2T801WaG2xp5hGHYQMtIi1ES-N4FOg5PwEjtIetA__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA",
"title": "Project Name",
"website": "www.project.com",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio nobis aliquam, minima assumenda earum sapiente pariatur cupiditate error nihil, eius corporis ratione unde perspiciatis tenetur ipsa aut ex consequatur maiores eligendi quidem exercitationem suscipit. Ex hic reprehenderit deleniti possimus culpa animi velit? Dolores, nemo quis minima sapiente sed laborum ipsam?",
"tags": [
{
"id": "123",
"title": "lightning"
},
{
"id": "333",
"title": "payments"
},
{
"id": "444",
"title": "lnurl-auth"
}
],
"screenShots": [
"",
"",
"",
""
],
"votes_count": 4432
}
}

View File

@@ -1,13 +1,54 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600;700&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
@apply text-gray-900 px-42 bg-gray-25 hover:bg-gray-100 py-12 font-sans rounded-8 shadow-md font-regular;
}
.btn-primary {
@apply bg-primary-600 hover:bg-primary-500 text-white;
}
.chip {
@apply bg-gray-100 text-body4 px-16 py-8 rounded-24 font-regular;
}
.chip-small {
@apply bg-gray-100 text-body5 px-12 py-8 rounded-16 font-regular;
}
.modal-card {
@apply rounded-[40px] bg-gray-50 overflow-hidden w-full max-w-[600px] shadow-xl;
}
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
body {
overflow-x: hidden;
}
svg {
display: inline-block;
}
/* width */
::-webkit-scrollbar {
width: 12px;
}
/* Track */
::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: #aaa;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background-color: #999;
}

View File

@@ -3,10 +3,13 @@ import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import Wrapper from './utils/Wrapper';
ReactDOM.render(
<React.StrictMode>
<App />
<Wrapper>
<App />
</Wrapper>
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -0,0 +1,60 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export enum Direction {
START,
NEXT,
PREVIOUS,
EXIT,
}
export enum ModalId {
None,
Project,
Login1,
Login2,
}
interface StoreState {
isOpen: boolean;
isLoading: boolean;
direction: Direction;
openModalId: ModalId;
initialModalProps: any;
}
const initialState = {
isOpen: false,
isLoading: false,
direction: Direction.START,
openModalId: ModalId.None,
} as StoreState;
export const modalSlice = createSlice({
name: "modals",
initialState,
reducers: {
setDirection(state, action: PayloadAction<Direction>) {
state.direction = action.payload;
},
openModal(
state,
action: PayloadAction<{ modalId: ModalId; initialModalProps: any }>
) {
if (!state.isOpen) state.direction = Direction.START;
state.isOpen = true;
state.openModalId = action.payload.modalId;
state.initialModalProps = action.payload.initialModalProps;
},
closeModal(state) {
state.direction = Direction.EXIT;
state.isOpen = false;
state.openModalId = ModalId.None;
},
},
});
export const { closeModal, openModal, setDirection } = modalSlice.actions;
export default modalSlice.reducer;

12
src/redux/store.ts Normal file
View File

@@ -0,0 +1,12 @@
import { configureStore } from "@reduxjs/toolkit";
import modalsSlice from "./features/modals.slice";
export const store = configureStore({
reducer: {
modals: modalsSlice,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

17
src/utils/Wrapper.tsx Normal file
View File

@@ -0,0 +1,17 @@
import { QueryClient, QueryClientProvider } from 'react-query'
import 'react-multi-carousel/lib/styles.css';
import { Provider } from 'react-redux';
import { store } from '../redux/store';
const queryClient = new QueryClient()
export default function Wrapper(props: any) {
return (
<QueryClientProvider client={queryClient}>
<Provider store={store}>
{props.children}
</Provider>
</QueryClientProvider>
)
}

View File

@@ -0,0 +1,3 @@
export function random(min: number, max: number) {
return Math.random() * (max - min) + min;
}

6
src/utils/hooks.ts Normal file
View File

@@ -0,0 +1,6 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../redux/store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

286
src/utils/htmlSkeleton.html Normal file
View File

@@ -0,0 +1,286 @@
<nav>
<div class="p-16 bg-gray-100 flex items-center">
<div class="w-40 h-40 bg-gray-300 rounded-8 mr-16 overflow-hidden">
<img
class="w-full h-full object-cover"
src="https://www.figma.com/file/OFowr5RJk9YZCW35KT7D5K/image/07b85d84145942255afd215b3da26dbbf1dd03bd?fuid=772401335362859303"
alt=""
/>
</div>
<h2 class="text-h5 font-bold mr-64">makers.bolt.fun</h2>
</div>
<div class="sm:max-w-[400px] border shadow-lg sm:p-32">
<div class="px-16">
<input
class="btn bg-gray-100 w-full py-12 px-40 rounded-24 mt-16 placeholder-gray-500"
placeholder="Search"
/>
<button class="btn btn-primary w-full py-12 px-40 rounded-24 my-16">
Submit ⚡ app
</button>
</div>
<ul class="py-16 gap-64 border-t border-b">
<li class="text-body4 p-16 hover:bg-gray-200">
<a href="#">Exlore 🏡</a>
</li>
<li class="text-body4 p-16 hover:bg-gray-200">
<a href="#">Hottest 🔥</a>
</li>
<li class="text-body4 p-16 hover:bg-gray-200">
<a href="#">Categories 🕹</a>
</li>
</ul>
<ul class="px-16 py-16 flex flex-wrap gap-y-12">
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">About Us</a>
</li>
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">Support</a>
</li>
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">Press</a>
</li>
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">Contacts</a>
</li>
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">Careers</a>
</li>
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">Sitemap</a>
</li>
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">Legal</a>
</li>
<li class="text-body4 text-gray-500 hover:text-gray-700 w-1/2">
<a href="#">Cookies Settings</a>
</li>
</ul>
</div>
</nav>
<nav class="hidden lg:flex py-36 px-32 bg-gray-100 items-center">
<h2 class="text-h5 font-bold mr-40 lg:mr-64">makers.bolt.fun</h2>
<ul class="flex gap-32 lg:gap-64">
<li class="text-body4"><a href="#">Exlore 🏡</a></li>
<li class="text-body4"><a href="#">Hottest 🔥</a></li>
<li class="text-body4"><a href="#">Categories 🕹</a></li>
</ul>
<div class="ml-auto">
<button class="btn btn-primary py-12 px-32 lg:px-40">Submit ⚡ app</button>
<span class="chip mx-12 h-full mr-24 p-12">⚡ 2.2k sats </span>
</div>
</nav>
<div class="py-[100px]">
<hr class="my-40 mb-96" />
<div class="flex gap-40 justify-center md:justify-between">
<div
class="flex-grow rounded-20 h-[280px] relative overflow-hidden p-24 flex flex-col items-start justify-end"
>
<img
class="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src="https://www.figma.com/file/OFowr5RJk9YZCW35KT7D5K/image/07b85d84145942255afd215b3da26dbbf1dd03bd?fuid=772401335362859303"
alt=""
/>
<div
class="w-full h-full object-cover bg-black opacity-40 absolute top-0 left-0 z-[-1]"
></div>
<h3 class="text-white text-h3 max-w-md">
Join the next wave of the Lightning Network in Novembers Shock the
Web hackathon
</h3>
<a class="btn btn-primary font-regular mt-36">Register Now</a>
</div>
<div
class="hidden flex-grow rounded-20 h-[280px] relative overflow-hidden p-24 md:flex flex-col items-start justify-end"
>
<img
class="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src="https://s3-alpha-sig.figma.com/img/be1b/cd75/1baa911b3875134c0889d6755c4ba2cb?Expires=1637539200&Signature=QExmgJCGGSES~zIwM-2G8yd7aPR-j5eFnV3tOg6BkSdXVB9AMhHQPbRpbfOv~rD3hdMdSPMkS9kfjyFbAuonltV2zrf5GOwGxrF2GVdhpIGc6RiqGLWVVY8mXysEm6~0fVj~2SK8hec~YnV1h0oHDQiZF5YjGi143pImGmcVERPpB7MiksSoD0Vki6RXamySopj~f-~lUGy2uKRbQKxQ4LCFTz-H9O8vpkZpCVq274FYsqsEtUihwVjniNXV8ukLxdL~rfgf8L9MeiR7gDYYQ9MSLMZKEa~TnQ-JadlngQz78a2T801WaG2xp5hGHYQMtIi1ES-N4FOg5PwEjtIetA__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA"
alt=""
/>
<div
class="w-full h-full object-cover bg-black opacity-30 absolute top-0 left-0 z-[-1]"
></div>
<h3 class="text-white text-h3 max-w-md">
Join the next wave of the Lightning Network in Novembers Shock the
Web hackathon
</h3>
<button class="btn font-regular mt-36">Register Now</button>
</div>
</div>
<hr class="my-40" />
<button class="btn btn-primary">Submit ⚡ app</button>
<hr class="my-40" />
<span class="chip mx-12"> Wallet </span>
<span class="chip-small mx-12 bg-red-100 text-red-800 font-regular">
lightining
</span>
<span class="chip-small mx-12 bg-primary-100 text-primary-800 font-regular">
lightining
</span>
<hr class="my-40" />
<div class="flex gap-12 flex-wrap">
<span class="chip"> Art & Collectibles </span
><span class="chip"> DeFi </span><span class="chip"> Entertainment </span
><span class="chip"> Exchange </span>
</div>
<hr class="my-40" />
<h3 class="font-bolder text-body3 mb-20">Hottest ⚡apps</h3>
<div class="flex gap-24">
<div
class="flex-shrink-0 px-16 py-16 flex gap-16 border-gray-100 shadow-md border-2 rounded-10 w-[269px] transition-colors hover:shadow-lg hover:bg-gray-200 hover:cursor-pointer"
>
<div
src=""
class="flex-shrink-0 w-80 h-80 bg-gray-200 border-0 rounded-8"
></div>
<div class="justify-around items-start min-w-0">
<p
class="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap"
>
{app.name}
</p>
<p class="text-body5 text-gray-600 font-light my-[5px]">
{app.category}
</p>
<span
class="chip-small bg-yellow-100 text-yellow-700 font-light text-body5 py-[3px] px-10"
>
🔥 123
</span>
</div>
</div>
<div
class="flex-shrink-0 px-16 py-16 flex gap-16 border-gray-100 shadow-md border-2 rounded-10 w-[269px] transition-colors hover:shadow-lg hover:bg-gray-200 hover:cursor-pointer"
>
<div
src=""
class="flex-shrink-0 w-80 h-80 bg-gray-200 border-0 rounded-8"
></div>
<div class="justify-around items-start min-w-0">
<p
class="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap"
>
{app.name}
</p>
<p class="text-body5 text-gray-600 font-light my-[5px]">
{app.category}
</p>
<span
class="chip-small bg-yellow-100 text-yellow-700 font-light text-body5 py-[3px] px-10"
>
🔥 123
</span>
</div>
</div>
<div
class="flex-shrink-0 px-16 py-16 flex gap-16 border-gray-100 shadow-md border-2 rounded-10 w-[269px] transition-colors hover:shadow-lg hover:bg-gray-200 hover:cursor-pointer"
>
<div
src=""
class="flex-shrink-0 w-80 h-80 bg-gray-200 border-0 rounded-8"
></div>
<div class="justify-around items-start min-w-0">
<p
class="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap"
>
{app.name}
</p>
<p class="text-body5 text-gray-600 font-light my-[5px]">
{app.category}
</p>
<span
class="chip-small bg-yellow-100 text-yellow-700 font-light text-body5 py-[3px] px-10"
>
🔥 123
</span>
</div>
</div>
</div>
<hr class="my-40" />
<div class="px-32">
<div class="rounded-[40px] bg-gray-50 overflow-hidden">
<div class="relative h-[152px] border">
<img
class="w-full h-full object-cover"
src="https://www.figma.com/file/OFowr5RJk9YZCW35KT7D5K/image/07b85d84145942255afd215b3da26dbbf1dd03bd?fuid=772401335362859303"
alt=""
/>
<button
class="w-[48px] h-[48px] bg-white absolute top-1/2 left-32 -translate-y-1/2 rounded-full hover:bg-gray-200"
>
Back
</button>
</div>
<div class="p-24">
<div class="flex gap-24 items-center">
<div
class="flex-shrink-0 w-[93px] h-[93px] rounded-md overflow-hidden"
>
<img
class="w-full h-full object-cover"
src="https://s3-alpha-sig.figma.com/img/be1b/cd75/1baa911b3875134c0889d6755c4ba2cb?Expires=1637539200&Signature=QExmgJCGGSES~zIwM-2G8yd7aPR-j5eFnV3tOg6BkSdXVB9AMhHQPbRpbfOv~rD3hdMdSPMkS9kfjyFbAuonltV2zrf5GOwGxrF2GVdhpIGc6RiqGLWVVY8mXysEm6~0fVj~2SK8hec~YnV1h0oHDQiZF5YjGi143pImGmcVERPpB7MiksSoD0Vki6RXamySopj~f-~lUGy2uKRbQKxQ4LCFTz-H9O8vpkZpCVq274FYsqsEtUihwVjniNXV8ukLxdL~rfgf8L9MeiR7gDYYQ9MSLMZKEa~TnQ-JadlngQz78a2T801WaG2xp5hGHYQMtIi1ES-N4FOg5PwEjtIetA__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA"
alt=""
/>
</div>
<div>
<h3 class="text-h3 font-regular">{Project Name}</h3>
<a class="text-blue-400 font-regular text-body4" href="#"
>www.project.com</a
>
</div>
<div class="flex ml-auto gap-16">
<button class="btn btn-primary py-12 px-24 rounded-lg my-16">
Play 🕹
</button>
<button class="btn py-12 px-24 rounded-lg my-16">Vote 🔥</button>
</div>
</div>
<p class="mt-40 text-body4 leading-normal">
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eos error ad
voluptas. Nostrum in non quia, obcaecati ipsam aut accusantium sunt
minus ea similique. Dolor sint minima veritatis delectus, corrupti
quod, expedita officia molestias nobis sequi perspiciatis. Laudantium
illo quasi a modi blanditiis iusto sequi laboriosam, harum rem beatae
eum commodi quo tempora minus asperiores quod libero? Blanditiis,
eligendi adipisci.
</p>
<div class="flex gap-24 mt-24 flex-wrap">
<span class="chip-small bg-red-100 text-red-800 font-regular">
payments
</span>
<span class="chip-small bg-primary-100 text-primary-800 font-regular">
lightining
</span>
</div>
<div class="mt-40">
<h3 class="text-h5 font-bold">Screen Shots</h3>
<div class="flex gap-x-24 gap-y-20">
<div
class="flex-none w-[164px] h-[120px] md:w-[300px] md:h-[136px] bg-gray-300 rounded-xl"
></div>
<div
class="flex-none w-[164px] h-[120px] md:w-[300px] md:h-[136px] bg-gray-300 rounded-xl"
></div>
<div
class="flex-none w-[164px] h-[120px] md:w-[300px] md:h-[136px] bg-gray-300 rounded-xl"
></div>
</div>
</div>
<hr class="my-40" />
<div class="text-center">
<h3 class="text-body4 font-regular">
Are you the creator of this project?
</h3>
<button class="btn py-12 px-24 rounded-lg my-16">Claim 🖐</button>
</div>
</div>
</div>
</div>
</div>

31
src/utils/interfaces.ts Normal file
View File

@@ -0,0 +1,31 @@
export interface ProjectCategory {
id: string;
title: string;
}
export interface ProjectCard {
id: string;
title: string;
img: string;
category: ProjectCategory;
votes_count: number;
}
export interface Tag {
id: string;
title: string;
}
export type Image = string;
export interface Project {
id: string;
title: string;
website?: string;
description: string;
tags: Tag[];
cover_image: Image;
thumbnail_image: Image;
screenShots: Image[];
votes_count: number;
}