mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-06 07:54:21 +01:00
initial commit
This commit is contained in:
38
src/App.css
38
src/App.css
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
30
src/App.tsx
30
src/App.tsx
@@ -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;
|
||||
|
||||
51
src/Components/ClaimProject/Step1.tsx
Normal file
51
src/Components/ClaimProject/Step1.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
16
src/Components/ExplorePage/ExplorePage.tsx
Normal file
16
src/Components/ExplorePage/ExplorePage.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
20
src/Components/ExplorePage/partials/Categories.tsx
Normal file
20
src/Components/ExplorePage/partials/Categories.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
56
src/Components/ExplorePage/partials/Header.tsx
Normal file
56
src/Components/ExplorePage/partials/Header.tsx
Normal 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 November’s ‘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>
|
||||
);
|
||||
}
|
||||
56
src/Components/ExplorePage/partials/ProjectsRow.tsx
Normal file
56
src/Components/ExplorePage/partials/ProjectsRow.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
20
src/Components/ExplorePage/partials/ProjectsSection.tsx
Normal file
20
src/Components/ExplorePage/partials/ProjectsSection.tsx
Normal 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} />)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
21
src/Components/Login/LoginCard-1.tsx
Normal file
21
src/Components/Login/LoginCard-1.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
78
src/Components/Project/ProjectCard.tsx
Normal file
78
src/Components/Project/ProjectCard.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
75
src/Components/Shared/Modal/Modal copy.tsx
Normal file
75
src/Components/Shared/Modal/Modal copy.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
100
src/Components/Shared/Modal/Modal.tsx
Normal file
100
src/Components/Shared/Modal/Modal.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
83
src/Components/Shared/Navbar/NavMobile.tsx
Normal file
83
src/Components/Shared/Navbar/NavMobile.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
36
src/Components/Shared/Navbar/Navbar.tsx
Normal file
36
src/Components/Shared/Navbar/Navbar.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
17
src/Components/Shared/Portal/Portal.jsx
Normal file
17
src/Components/Shared/Portal/Portal.jsx
Normal 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
32
src/api/index.ts
Normal 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
131
src/api/mockData.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
);
|
||||
|
||||
60
src/redux/features/modals.slice.ts
Normal file
60
src/redux/features/modals.slice.ts
Normal 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
12
src/redux/store.ts
Normal 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
17
src/utils/Wrapper.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
3
src/utils/helperFunctions.ts
Normal file
3
src/utils/helperFunctions.ts
Normal 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
6
src/utils/hooks.ts
Normal 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
286
src/utils/htmlSkeleton.html
Normal 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 November’s ‘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 November’s ‘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
31
src/utils/interfaces.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user