feat: added mocks using msw

This commit is contained in:
MTG2000
2022-03-12 16:11:11 +02:00
parent 8df9d1609d
commit b3ed2ad280
36 changed files with 2086 additions and 332 deletions

1043
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@
"react-query": "^3.32.3",
"react-redux": "^7.2.6",
"react-responsive-carousel": "^3.2.22",
"react-router-dom": "^6.0.2",
"react-router-dom": "^6.2.2",
"react-scripts": "4.0.3",
"react-use": "^17.3.1",
"typescript": "^4.4.4",
@@ -107,8 +107,12 @@
"@types/react-copy-to-clipboard": "^5.0.2",
"autoprefixer": "^9.8.8",
"gh-pages": "^3.2.3",
"msw": "^0.39.1",
"netlify-cli": "^8.15.0",
"postcss": "^7.0.39",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17"
},
"msw": {
"workerDirectory": "public"
}
}
}

338
public/mockServiceWorker.js Normal file
View File

@@ -0,0 +1,338 @@
/* eslint-disable */
/* tslint:disable */
/**
* Mock Service Worker (0.39.1).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
const INTEGRITY_CHECKSUM = '02f4ad4a2797f85668baf196e553d929'
const bypassHeaderName = 'x-msw-bypass'
const activeClientIds = new Set()
self.addEventListener('install', function () {
return self.skipWaiting()
})
self.addEventListener('activate', async function (event) {
return self.clients.claim()
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll()
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: true,
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
// Resolve the "main" client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)
if (client.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll()
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const clonedResponse = response.clone()
sendToClient(client, {
type: 'RESPONSE',
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body:
clonedResponse.body === null ? null : await clonedResponse.text(),
headers: serializeHeaders(clonedResponse.headers),
redirected: clonedResponse.redirected,
},
})
})()
}
return response
}
async function getResponse(event, client, requestId) {
const { request } = event
const requestClone = request.clone()
const getOriginalResponse = () => fetch(requestClone)
// Bypass mocking when the request client is not active.
if (!client) {
return getOriginalResponse()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return await getOriginalResponse()
}
// Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === 'true') {
const cleanRequestHeaders = serializeHeaders(requestClone.headers)
// Remove the bypass header to comply with the CORS preflight check.
delete cleanRequestHeaders[bypassHeaderName]
const originalRequest = new Request(requestClone, {
headers: new Headers(cleanRequestHeaders),
})
return fetch(originalRequest)
}
// Send the request to the client-side MSW.
const reqHeaders = serializeHeaders(request.headers)
const body = await request.text()
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: reqHeaders,
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
})
switch (clientMessage.type) {
case 'MOCK_SUCCESS': {
return delayPromise(
() => respondWithMock(clientMessage),
clientMessage.payload.delay,
)
}
case 'MOCK_NOT_FOUND': {
return getOriginalResponse()
}
case 'NETWORK_ERROR': {
const { name, message } = clientMessage.payload
const networkError = new Error(message)
networkError.name = name
// Rejecting a request Promise emulates a network error.
throw networkError
}
case 'INTERNAL_ERROR': {
const parsedBody = JSON.parse(clientMessage.payload.body)
console.error(
`\
[MSW] Uncaught exception in the request handler for "%s %s":
${parsedBody.location}
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
request.method,
request.url,
)
return respondWithMock(clientMessage)
}
}
return getOriginalResponse()
}
self.addEventListener('fetch', function (event) {
const { request } = event
const accept = request.headers.get('accept') || ''
// Bypass server-sent events.
if (accept.includes('text/event-stream')) {
return
}
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
const requestId = uuidv4()
return event.respondWith(
handleRequest(event, requestId).catch((error) => {
if (error.name === 'NetworkError') {
console.warn(
'[MSW] Successfully emulated a network error for the "%s %s" request.',
request.method,
request.url,
)
return
}
// At this point, any exception indicates an issue with the original request/response.
console.error(
`\
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
request.method,
request.url,
`${error.name}: ${error.message}`,
)
}),
)
})
function serializeHeaders(headers) {
const reqHeaders = {}
headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name]
? [].concat(reqHeaders[name]).concat(value)
: value
})
return reqHeaders
}
function sendToClient(client, message) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(JSON.stringify(message), [channel.port2])
})
}
function delayPromise(cb, duration) {
return new Promise((resolve) => {
setTimeout(() => resolve(cb()), duration)
})
}
function respondWithMock(clientMessage) {
return new Response(clientMessage.payload.body, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
})
}
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0
const v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}

View File

@@ -4,6 +4,8 @@ import ExplorePage from "src/pages/ExplorePage";
import ModalsContainer from "src/Components/Modals/ModalsContainer/ModalsContainer";
import { useAppDispatch, useAppSelector, useResizeListener } from './utils/hooks';
import { Wallet_Service } from "./services";
import { Route, Routes } from "react-router-dom";
import CategoryPage from "./pages/CategoryPage/CategoryPage";
function App() {
const { isWalletConnected } = useAppSelector(state => ({
@@ -34,7 +36,10 @@ function App() {
return <div id="app" className='w-screen overflow-hidden'>
<Navbar />
<ExplorePage />
<Routes>
<Route path="/category/:id" element={<CategoryPage />} />
<Route path="/" element={<ExplorePage />} />
</Routes>
<ModalsContainer />
</div>;
}

View File

@@ -3,7 +3,7 @@ import Skeleton from 'react-loading-skeleton'
export default function ProjectCardMiniSkeleton() {
return (
<div className="bg-gray-25 select-none px-16 py-16 flex w-[296px] gap-16 border border-gray-200 rounded-10 items-center" >
<div className="bg-gray-25 select-none px-16 py-16 flex min-w-[296px] gap-16 border border-gray-200 rounded-10 items-center" >
<Skeleton width={80} height={80} containerClassName='flex-shrink-0' />
<div className="justify-around items-start min-w-0">
<p className="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap"><Skeleton width="15ch" /></p>

View File

@@ -1,5 +1,5 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import mockData from 'src/api/mockData.json'
import { MOCK_DATA } from 'src/mocks/data';
import ProjectCardMini from './ProjectCardMini';
import ProjectCardMiniSkeleton from './ProjectCardMini.Skeleton';
@@ -15,7 +15,7 @@ const Template: ComponentStory<typeof ProjectCardMini> = (args) => <ProjectCardM
export const Default = Template.bind({});
Default.args = {
project: mockData.projectsCards[0]
project: MOCK_DATA.projects[0]
}

View File

@@ -1,13 +1,15 @@
import { MdLocalFireDepartment } from "react-icons/md";
import { useAppDispatch } from "src/utils/hooks";
import { ProjectCard } from "src/utils/interfaces";
interface Props {
project: ProjectCard
onClick: (projectId: string) => void
onClick: (projectId: number) => void
}
export default function ProjectCardMini({ project, onClick }: Props) {
return (
<div className="bg-gray-25 select-none px-16 py-16 flex min-w-[296px] gap-16 border border-gray-200 rounded-10 hover:cursor-pointer hover:bg-gray-100" onClick={() => onClick(project.id)}>
<img src={project.thumbnail_image} alt={project.title} draggable="false" className="flex-shrink-0 w-80 h-80 bg-gray-200 border-0 rounded-8"></img>

View File

@@ -1,76 +0,0 @@
import { Project, ProjectCard, ProjectCategory } from "../utils/interfaces";
import { gql, useQuery } from "@apollo/client";
import data from "./mockData.json";
export async function getAllCategories(): Promise<ProjectCategory[]> {
// let QUERY = gql`
// query GetCategories {
// allCategories {
// id
// title
// }
// }
// `;
return data.categories;
}
export async function getHottestProjects(): Promise<ProjectCard[]> {
// let QUERY = gql`
// query {
// allProject {
// id
// cover_image
// thumbnail_image
// title
// website
// votes_count
// }
// }
// `;
return data.projectsCards;
}
export async function getProjectsByCategory(
categoryId: string
): Promise<ProjectCard[]> {
// let QUERY = gql`
// query Categories($categoryId: Int!){
// projectsByCategory(category_id: ${categoryId}) {
// id
// cover_image
// thumbnail_image
// title
// website
// lightning_address
// votes_count
// }
// }
// `;
return data.projectsCards;
}
// // returns the latest bunch of projects in each ( or some ) categories, and returns the hottest projects
// export async function getLatestProjects(): Promise<
// { category: ProjectCategory; projects: ProjectCard[] }[]
// > {
// return data.categories.slice(0, 3).map((cat) => ({
// category: cat,
// projects: data.projectsCards,
// })) as any;
// }
export async function getProjectById(projectId: string): Promise<Project> {
// let QUERY = gql`
// query Project(projectId: String!) {
// getProject(id: $projectId) {
// id
// cover_image
// thumbnail_image
// title
// website
// votes_count
// }
// }
// `;
return data.project;
}

View File

@@ -1,139 +0,0 @@
{
"categories": [
{
"id": "111",
"title": "Hottest"
},
{
"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": "232",
"title": "Social"
},
{
"id": "512",
"title": "Wallet"
},
{
"id": "132",
"title": "Other"
}
],
"projectsCards": [
{
"id": "123",
"title": "First App",
"thumbnail_image": "https://via.placeholder.com/150",
"category": {
"id": "512",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "765",
"title": "Second App",
"thumbnail_image": "https://via.placeholder.com/150",
"category": {
"id": "512",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "55",
"title": "Third App",
"thumbnail_image": "https://via.placeholder.com/150",
"category": {
"id": "512",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "12344123",
"title": "Fourth App",
"thumbnail_image": "https://via.placeholder.com/150",
"category": {
"id": "512",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "56745",
"title": "Fifth App",
"thumbnail_image": "https://via.placeholder.com/150",
"category": {
"id": "512",
"title": "{app.category}"
},
"votes_count": 123
},
{
"id": "3312431",
"title": "Sixth App",
"thumbnail_image": "https://via.placeholder.com/150",
"category": {
"id": "512",
"title": "{app.category}"
},
"votes_count": 123
}
],
"project": {
"id": "1233",
"cover_image": "https://picsum.photos/id/10/1024/1024",
"thumbnail_image": "https://s3-alpha-sig.figma.com/img/be1b/cd75/1baa911b3875134c0889d6755c4ba2cb?Expires=1638748800&Signature=DOiLciAA95w8gOvAowjiiR-ZPbmV1oGSRRK8YpE4ALMoe47pL7DifQxZvL1LQn~NRa0aLMoMk61521fbbGJeDAwk~j6fIm~iZAlMzQn7DdWy0wFR0uLQagOgpIiIXO-w8CeC14VoW-hrjIX5mDmOonJzkfoftGqIF1WCOmP2EuswyJpIngFdLb15gCex4Necs3vH2cuD9iSgWG2za97KfdXZP79ROyk2EN9Q3~a7RT4FTBBIlgKDLuFGSVRxReXVNajn~XSxBJh2de9dFVa3tOXkwJXu3jb0G4x-wRCaG-KmBhUOemuGtu5Fumh6goktGh~bIDwoHeUBVKFHAzaYgw__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA",
"title": "█████████",
"website": "████████",
"description": "██████████████████",
"category": {
"id": "333",
"title": "█████"
},
"tags": [
{
"id": "123",
"title": "█████"
},
{
"id": "313",
"title": "██████"
},
{
"id": "451",
"title": "█████"
}
],
"screenShots": [
"",
"",
"",
""
],
"votes_count": 0
}
}

View File

@@ -5,6 +5,11 @@ import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
if (process.env.NODE_ENV === 'development') {
const { worker } = require('./mocks/browser')
worker.start()
}
ReactDOM.render(

4
src/mocks/browser.ts Normal file
View File

@@ -0,0 +1,4 @@
import { setupWorker } from 'msw'
import { handlers } from './handlers'
// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...handlers)

428
src/mocks/data.ts Normal file
View File

@@ -0,0 +1,428 @@
import ASSETS from "src/assets";
import { Project, ProjectCategory } from "src/utils/interfaces";
import { projectsByCategory } from "./resolvers";
let categories = [
{
"id": 10,
"title": "Video",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 8,
"title": "Shopping",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 5,
"title": "Music",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 9,
"title": "Miscellaneous",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 4,
"title": "Gaming",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 1,
"title": "Finance",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 2,
"title": "Digital Print",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 7,
"title": "Digital Content",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 3,
"title": "Art",
cover_image: ASSETS.Images_ExploreHeader1
},
{
"id": 6,
"title": "Analytics",
cover_image: ASSETS.Images_ExploreHeader1
},
]
let projects = [
{
"id": 1,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/kollider_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/kollider_thumbnail.png",
"title": "Kollider",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://kollider.xyz/",
"lightning_address": "johns@getalby.com",
"votes_count": 120,
"category": {
"id": 1,
"title": "Finance"
}
},
{
"id": 16,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/alby_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/alby_thumbnail.png",
"title": "Alby",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://getalby.com/",
"lightning_address": "hello@getalby.com",
"votes_count": 100,
"category": {
"id": 9,
"title": "Miscellaneous"
}
},
{
"id": 12,
"cover_image": "http://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/bitrefill_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/bitrefill_thumbnail.png",
"title": "Bitrefill",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://www.bitrefill.com/buy",
"lightning_address": "moritz@getalby.com",
"votes_count": 25,
"category": {
"id": 8,
"title": "Shopping"
}
},
{
"id": 20,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-video_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-video_thumbnail.png",
"title": "Lightning.Video",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lightning.video/",
"lightning_address": "moritz@getalby.com",
"votes_count": 15,
"category": {
"id": 7,
"title": "Digital Content"
}
},
{
"id": 3,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/sparkshot_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/sparkshot_thumbnail.png",
"title": "Sparkshot",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://sparkshot.io/",
"lightning_address": "johns@getalby.com",
"votes_count": 11,
"category": {
"id": 3,
"title": "Art"
}
},
{
"id": 17,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-gifts_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-gifts_thumbnail.png",
"title": "Lightning Gifts",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lightning.gifts/",
"lightning_address": "moritz@getalby.com",
"votes_count": 10,
"category": {
"id": 8,
"title": "Shopping"
}
},
{
"id": 18,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/scarce-city_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/scarce-city_thumbnail.png",
"title": "Scarce City",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://scarce.city/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 3,
"title": "Art"
}
},
{
"id": 15,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lnshort-it_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lnshort-it_thumbnail.png",
"title": "lnshort.it",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lnshort.it/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 9,
"title": "Miscellaneous"
}
},
{
"id": 11,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/stacker-news_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/stacker-news_thumbnail.png",
"title": "Stacker News",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://stacker.news/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 7,
"title": "Digital Content"
}
},
{
"id": 21,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/starbackr_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/starbackr_thumbail.png",
"title": "Starbackr",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://www.starbackr.com/",
"lightning_address": "moritz@geralby.com",
"votes_count": 0,
"category": {
"id": 7,
"title": "Digital Content"
}
},
{
"id": 2,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lnmarkets_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lnmarkets_thumbnail.png",
"title": "LN Markets",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lnmarkets.com/",
"lightning_address": "johns@getalby.com",
"votes_count": 0,
"category": {
"id": 1,
"title": "Finance"
}
},
{
"id": 5,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lnblackjack_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lnblackjack_thumbnail.png",
"title": "LN Blackjack",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://www.lnblackjack.com/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 4,
"title": "Gaming"
}
},
{
"id": 19,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/yalls_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/yalls_thumbnail.png",
"title": "Y'alls ",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://yalls.org/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 2,
"title": "Digital Print"
}
},
{
"id": 13,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-network-stores_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-network-stores_thumbnail.png",
"title": "Lightning Network Stores",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lightningnetworkstores.com/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 8,
"title": "Shopping"
}
},
{
"id": 9,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-poker_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-poker_thumbnail.png",
"title": "Lightning Poker",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lightning-poker.com/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 4,
"title": "Gaming"
}
},
{
"id": 6,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lngames_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lngames_thumbnail.png",
"title": "LNGames",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lngames.net/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 4,
"title": "Gaming"
}
},
{
"id": 4,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/amboss-space_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/amboss-space_thumbnail.png",
"title": "Amboss Space",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://amboss.space/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 6,
"title": "Analytics"
}
},
{
"id": 8,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/loft-trade_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/loft-trade_thumbnail.png",
"title": "LOFT",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://loft.trade/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 1,
"title": "Finance"
}
},
{
"id": 10,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-roulette_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/lightning-roulette_thumbnail.png",
"title": "Lightning Roulette",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://lightning-roulette.com/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 4,
"title": "Gaming"
}
},
{
"id": 14,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/sats-4-likes-cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/sats-4-likes_thumbnail.png",
"title": "Sats 4 Likes",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://kriptode.com/satsforlikes/index.html",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 9,
"title": "Miscellaneous"
}
},
{
"id": 7,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/wavlake_cover.png",
"thumbnail_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/wavlake_thumbnail.png",
"title": "Wavlake",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://www.wavlake.com/",
"lightning_address": "moritz@getalby.com",
"votes_count": 0,
"category": {
"id": 7,
"title": "Digital Content"
}
},
{
"id": 22,
"cover_image": "https://fra1.digitaloceanspaces.com/alby-storage/makers-bolt-fun/bolt.fund_thumbnails_covers/geyser-fund_cover.png",
"thumbnail_image": "https://user-images.githubusercontent.com/36778205/157433567-4b1e41db-23d4-4a80-b48f-ee248ee87f1e.jpg",
"title": "Geyser Fund",
"description": "Project description",
screenShots: [],
tags: [],
"website": "https://geyser.fund/",
"lightning_address": "0272e8731c6feda7fb7e2b8dbe0fbf1322f9e3b60cc2727f4ee4ca0f820b9cd169@35.206.90.207:9735",
"votes_count": 0,
"category": {
"id": 9,
"title": "Miscellaneous"
}
}
]
// Process Data
// ------------
// 1- Add Typenames
categories = categories.map(c => ({ ...c, __typename: "Category" }))
projects = projects.map(p => ({ ...p, __typename: "Project" }))
// 2- Computed Fields
categories = categories.map(c => ({ ...c, apps_count: projects.reduce((acc, p) => acc + (p.category.id === c.id ? 1 : 0), 0) }))
export const MOCK_DATA = {
projects: projects as Project[],
categories: categories as ProjectCategory[]
}

45
src/mocks/handlers.ts Normal file
View File

@@ -0,0 +1,45 @@
import { graphql } from 'msw'
import { allCategories, getCategory, getProject, newProjects, projectsByCategory } from './resolvers'
export const handlers = [
graphql.query('PROJECTS_IN_CATEGORY_QUERY', (req, res, ctx) => {
const { categoryId } = req.variables
return res(
ctx.data({
projectsByCategory: projectsByCategory(categoryId),
getCategory: getCategory(categoryId)
})
)
}),
graphql.query('AllCategoriesProjects', (req, res, ctx) => {
return res(
ctx.data({
allCategories: allCategories(),
newProjects: newProjects()
})
)
}),
graphql.query('AllCategories', (req, res, ctx) => {
return res(
ctx.data({
allCategories: allCategories()
})
)
}),
graphql.query('Project', (req, res, ctx) => {
const { projectId } = req.variables
return res(
ctx.data({
getProject: getProject(projectId)
})
)
}),
]

28
src/mocks/resolvers.ts Normal file
View File

@@ -0,0 +1,28 @@
import { MOCK_DATA } from "./data";
export function getCategory(id: number) {
return {
...MOCK_DATA.categories.find(c => c.id === id),
project: MOCK_DATA.projects.filter(p => p.category.id === id)
}
}
export function projectsByCategory(id: number) {
return MOCK_DATA.projects.filter(p => p.category.id === id)
}
export function allCategories() {
return MOCK_DATA.categories.map(c => ({
...c,
project: projectsByCategory(c.id)
}))
}
export function newProjects() {
return MOCK_DATA.projects;
}
export function getProject(projectId: number) {
return MOCK_DATA.projects.find(p => p.id === projectId)
}

View File

@@ -1 +1,46 @@
export { }
import { useQuery } from '@apollo/client';
import { useParams, Navigate } from 'react-router-dom'
import HeaderImage from './HeaderImage/HeaderImage';
import ProjectsGrid from './ProjectsGrid/ProjectsGrid';
import { PROJECTS_IN_CATEGORY_QUERY, PROJECTS_IN_CATEGORY_QUERY_RES_TYPE, PROJECTS_IN_CATEGORY_QUERY_VARS } from './query';
export default function CategoryPage() {
const { id } = useParams();
const { data, loading } = useQuery<PROJECTS_IN_CATEGORY_QUERY_RES_TYPE, PROJECTS_IN_CATEGORY_QUERY_VARS>(PROJECTS_IN_CATEGORY_QUERY, {
skip: !id,
variables: {
categoryId: Number(id)
},
onCompleted: data => {
console.log('--------------');
console.log(data);
}
});
if (!id || (!loading && !data))
<Navigate to='/' />
return (
<div className='px-32'>
<HeaderImage
isLoading={loading}
title={data?.getCategory.title!}
img={data?.getCategory.cover_image!}
apps_count={data?.getCategory.apps_count!}
/>
<div className="mt-40">
<ProjectsGrid
isLoading={loading}
projects={data?.projectsByCategory!}
/>
</div>
</div>
)
}

View File

@@ -1,20 +1,41 @@
import React from 'react'
import { FiArrowLeft } from 'react-icons/fi'
import Skeleton from 'react-loading-skeleton'
import { Link } from 'react-router-dom'
interface Props {
type Props = {
isLoading: boolean
title: string
apps_count: number
img: string
}
export default function HeaderImage({ title, apps_count, img }: Props) {
export default function HeaderImage(props: Props) {
if (props.isLoading)
return (
<div className='h-[280px] rounded-20 overflow-hidden relative '>
{/* <div className='absolute inset-0 w-full h-full bg-gray-100' /> */}
<Skeleton height={'100%'} />
</div>
)
const { title, img, apps_count } = props;
return (
<div className='h-[280px] rounded-20 overflow-hidden relative flex flex-col justify-center items-center gap-8'>
<img src={img} alt={`${title} cover`} className='absolute inset-0 w-full h-full object-cover z-[-1]' />
<div className='absolute inset-0 w-full h-full bg-black bg-opacity-50 z-[-1]' />
<button className="w-[48px] h-[48px] bg-white absolute top-24 left-24 md:top-1/2 md:left-40 md:-translate-y-1/2 rounded-full hover:bg-gray-200 text-center" onClick={() => { }}><FiArrowLeft className=' inline-block text-body2 lg:text-body1' /></button>
<Link
to='/'
className="
w-[48px] h-[48px] bg-white hover:bg-gray-200
absolute top-24 left-24 md:top-1/2 md:left-40 md:-translate-y-1/2
rounded-full text-center flex justify-center items-center">
<FiArrowLeft className=' inline-block text-body2 lg:text-body1' /></Link>
<h1
className='text-white text-body1 md:text-[40px]'
className='text-white text-[24px] md:text-[40px] '
>{title}</h1>
<p
className='text-white text-body4'

View File

@@ -1,4 +1,5 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { MOCK_DATA } from 'src/mocks/data';
import ProjectsGrid from './ProjectsGrid';
@@ -15,7 +16,8 @@ const Template: ComponentStory<typeof ProjectsGrid> = (args) => <ProjectsGrid {.
export const Default = Template.bind({});
Default.args = {
isLoading: false,
projects: MOCK_DATA.projects
}

View File

@@ -1,15 +1,30 @@
import React from 'react'
import ProjectCardMini from 'src/Components/Cards/ProjectCardMini/ProjectCardMini'
import mockData from 'src/api/mockData.json'
import ProjectCardMiniSkeleton from 'src/Components/Cards/ProjectCardMini/ProjectCardMini.Skeleton';
import { openModal } from 'src/redux/features/modals.slice';
import { useAppDispatch } from 'src/utils/hooks';
import { ProjectCard } from 'src/utils/interfaces';
interface Props {
isLoading?: boolean;
projects: ProjectCard[]
}
export default function ProjectsGrid({ isLoading, projects }: Props) {
const dispatch = useAppDispatch();
const handleClick = (projectId: number) => {
dispatch(openModal({ Modal: "ProjectDetailsCard", props: { projectId } }))
}
export default function ProjectsGrid() {
return (
<div style={{
gridTemplateColumns: 'repeat(auto-fit, minmax(296px, 1fr))',
gridTemplateColumns: 'repeat(auto-fill, minmax(296px, 1fr) )',
display: 'grid',
gridGap: '24px',
}}>
{Array(30).fill(0).map((_, idx) => <ProjectCardMini key={idx} project={mockData.projectsCards[0]} onClick={() => { }} />)}
{isLoading && Array(12).fill(0).map((_, idx) => <ProjectCardMiniSkeleton key={idx} />)}
{!isLoading && projects.map((project) => <ProjectCardMini key={project.id} project={project} onClick={handleClick} />)}
</div>
)
}

View File

@@ -0,0 +1,40 @@
import { gql } from "@apollo/client";
import { ProjectCard, ProjectCategory } from "src/utils/interfaces";
export const PROJECTS_IN_CATEGORY_QUERY = gql`
query PROJECTS_IN_CATEGORY_QUERY($categoryId: Int!) {
projectsByCategory(category_id: $categoryId) {
id
thumbnail_image
title
votes_count
category {
title
id
}
}
getCategory(id: $categoryId) {
id
title
cover_image
apps_count
}
}
`
export type PROJECTS_IN_CATEGORY_QUERY_VARS = {
categoryId: number;
}
export type PROJECTS_IN_CATEGORY_QUERY_RES_TYPE = {
projectsByCategory: ProjectCard[],
getCategory: {
id: ProjectCategory['id']
title: ProjectCategory['title']
cover_image: ProjectCategory['cover_image']
apps_count: ProjectCategory['apps_count']
}
}

View File

@@ -2,12 +2,13 @@ import { useQuery } from '@apollo/client';
import { ALL_CATEGORIES_QUERY, ALL_CATEGORIES_QUERY_RES } from './query';
import Badge from 'src/Components/Badge/Badge'
import Slider from 'src/Components/Slider/Slider'
import { useNavigate } from 'react-router-dom';
export default function Categories() {
const { data, loading } = useQuery<ALL_CATEGORIES_QUERY_RES>(ALL_CATEGORIES_QUERY);
const navigate = useNavigate();
if (loading || !data)
return <div className="flex gap-12">
@@ -20,7 +21,7 @@ export default function Categories() {
return (
<Slider>
{data?.allCategories.map(category =>
<Badge key={category.id} onClick={() => document.getElementById(category.title.toLowerCase())?.scrollIntoView({ behavior: 'smooth', block: 'center' })} >{category.title}</Badge>
<Badge key={category.id} onClick={() => navigate(`/category/${category.id}`)}>{category.title}</Badge>
)}
</Slider>
)

View File

@@ -9,9 +9,9 @@ export default function ExplorePage() {
<div className="px-32">
<Header />
</div>
{/* <div className="my-40 px-32">
<div className="my-40 px-32">
<Categories />
</div> */}
</div>
<ProjectsSection />
</>
)

View File

@@ -31,7 +31,7 @@ export default function Header() {
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<h3 className="text-white text-h3 max-w-[80%]">
<h3 className="text-white text-h4 lg:text-h3 max-w-[80%]">
{headerLinks[0].title}
</h3>
@@ -46,7 +46,7 @@ export default function Header() {
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<h3 className="text-white text-h3 max-w-[80%]">
<h3 className="text-white text-h4 lg:text-h3 max-w-[80%]">
{headerLinks[1].title}
</h3>
<Button href={headerLinks[1].link.url} newTab className="font-regular mt-36">

View File

@@ -1,8 +1,8 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import mockData from 'src/api/mockData.json'
import ProjectsRow from './ProjectsRow';
import { MdLocalFireDepartment } from 'react-icons/md';
import { MOCK_DATA } from 'src/mocks/data';
export default {
@@ -20,14 +20,14 @@ Hottest.args = {
<MdLocalFireDepartment
className='inline-block text-fire align-bottom scale-125 ml-4 origin-bottom'
/></>,
categoryId: '2',
projects: mockData.projectsCards
categoryId: 0,
projects: MOCK_DATA.projects
}
export const Defi = Template.bind({});
Defi.args = {
title: 'DeFi',
categoryId: '33',
projects: mockData.projectsCards
categoryId: 8,
projects: MOCK_DATA.projects
}

View File

@@ -8,10 +8,11 @@ import ProjectCardMini from "src/Components/Cards/ProjectCardMini/ProjectCardMin
import { useResizeListener } from 'src/utils/hooks'
import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
import './style.css';
import { Link } from "react-router-dom";
interface Props {
title: string | ReactNode,
categoryId: string,
categoryId: number,
projects: ProjectCard[]
}
@@ -59,7 +60,7 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
const handleClick = (projectId: string) => {
const handleClick = (projectId: number) => {
if (!drag.current)
dispatch(openModal({ Modal: "ProjectDetailsCard", props: { projectId } }))
}
@@ -75,11 +76,11 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
return (
<div className='mb-48'>
<h3 className="font-bolder text-body3 mb-24 px-32">{title}
<span>
{categoryId > 0 && <Link to={`/category/${categoryId}`}>
<MdDoubleArrow className='text-gray-200 ml-8 hover:cursor-pointer align-bottom transform scale-y-110 scale-x-125 origin-left' onClick={() => {
console.log(categoryId);
}} />
</span>
</Link>}
</h3>
<div className="px-32">
<Carousel

View File

@@ -19,7 +19,7 @@ export default function ProjectsSection() {
return (
<div className='mt-32 lg:mt-48'>
<ProjectsRow title={<>Newest <MdLocalFireDepartment className='inline-block text-fire align-bottom scale-125 origin-bottom' /></>}
categoryId="133123"
categoryId={0}
projects={data.newProjects} />
{data.allCategories.map(({ id, title, project, }) => {
if (project)

View File

@@ -1,5 +1,5 @@
import { gql } from "@apollo/client";
import { ProjectCard } from "src/utils/interfaces";
import { ProjectCard, ProjectCategory } from "src/utils/interfaces";
export const ALL_CATEGORIES_PROJECTS_QUERY = gql`
query AllCategoriesProjects {
@@ -27,9 +27,10 @@ export const ALL_CATEGORIES_PROJECTS_QUERY = gql`
`;
export type ALL_CATEGORIES_PROJECTS_RES = {
newProjects: ProjectCard[];
newProjects: ProjectCard[],
allCategories: {
id: string;
id: number;
title: string;
project: ProjectCard[];
}[];

View File

@@ -18,7 +18,7 @@ export const Default = Template.bind({
});
Default.args = {
projectId: '3'
projectId: 3
}

View File

@@ -14,7 +14,7 @@ import { Wallet_Service } from 'src/services'
interface Props extends ModalCard {
projectId: string
projectId: number
}
export default function ProjectDetailsCard({ onClose, direction, projectId, ...props }: Props) {
@@ -24,7 +24,7 @@ export default function ProjectDetailsCard({ onClose, direction, projectId, ...p
const { loading } = useQuery<PROJECT_BY_ID_RES, PROJECT_BY_ID_VARS>(
PROJECT_BY_ID_QUERY,
{
variables: { projectId: parseInt(projectId) },
variables: { projectId: projectId },
onCompleted: data => {
dispatch(setProject(data.getProject))
},

View File

@@ -100,7 +100,7 @@ export default function VoteButton({ onVote = () => { }, ...props }: Props) {
Hold To Vote !!! <MdLocalFireDepartment className='text-fire' />
<span
className='Vote-counter'
className='vote-counter'
>{voteCnt}</span>
<div

View File

@@ -1,10 +1,10 @@
.tip-button {
.vote-button {
--scale: 0;
transition: background-color 1s;
background-color: hsl(25, 100%, max(calc((95 - var(--scale) / 4) * 1%), 63%));
}
.tip-counter {
.vote-counter {
position: absolute;
left: 50%;
bottom: 110%;

View File

@@ -31,7 +31,7 @@ enum PaymentStatus {
interface Props extends ModalCard {
initVotes?: number;
projectId: string
projectId: number
}
export default function VoteCard({ onClose, direction, initVotes, projectId, ...props }: Props) {
@@ -103,7 +103,7 @@ export default function VoteCard({ onClose, direction, initVotes, projectId, ...
const requestPayment = () => {
setPaymentStatus(PaymentStatus.FETCHING_PAYMENT_DETAILS);
vote({ variables: { "amountInSat": voteAmount, "projectId": parseInt(projectId) } });
vote({ variables: { "amountInSat": voteAmount, "projectId": projectId } });
}
return (

View File

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client";
export const VOTE_QUERY = gql`
mutation Mutation($projectId: Int!, $amountInSat: Int!) {
mutation Vote($projectId: Int!, $amountInSat: Int!) {
vote(project_id: $projectId, amount_in_sat: $amountInSat) {
id
amount_in_sat
@@ -13,17 +13,17 @@ mutation Mutation($projectId: Int!, $amountInSat: Int!) {
`;
export type VOTE_QUERY_RES_TYPE = {
vote: {
id: number;
amount_in_sat: number;
payment_request: string;
payment_hash: string;
paid: boolean;
}
vote: {
id: number;
amount_in_sat: number;
payment_request: string;
payment_hash: string;
paid: boolean;
}
}
export const CONFIRM_VOTE_QUERY = gql`
mutation Mutation($paymentRequest: String!, $preimage: String!) {
mutation ConfirmVote($paymentRequest: String!, $preimage: String!) {
confirmVote(payment_request: $paymentRequest, preimage: $preimage) {
id
amount_in_sat
@@ -35,11 +35,11 @@ mutation Mutation($paymentRequest: String!, $preimage: String!) {
`;
export type CONFIRM_VOTE_QUERY_RES_TYPE = {
confirmVote: {
id: number;
amount_in_sat: number;
paid: boolean;
payment_hash: string;
payment_request: string;
}
confirmVote: {
id: number;
amount_in_sat: number;
paid: boolean;
payment_hash: string;
payment_request: string;
}
}

View File

@@ -1,7 +1,7 @@
import { store } from '../redux/store';
import { QueryClient, QueryClientProvider } from 'react-query'
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom'
import 'react-multi-carousel/lib/styles.css';
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
@@ -18,9 +18,9 @@ const client = new ApolloClient({
uri: 'https://makers.bolt.fun/.netlify/functions/graphql',
cache: new InMemoryCache()
});
const queryClient = new QueryClient()
const parsedData = window.location.pathname.split("/");
let domain = parsedData[1];
@@ -34,7 +34,7 @@ export default function Wrapper(props: any) {
<ApolloProvider client={client}>
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<BrowserRouter basename={"/" + domain}>
<BrowserRouter >
{props.children}
</BrowserRouter>
</Provider>

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect } from "react";
import { useEffect } from "react";
import _throttle from "lodash.throttle";
export const useResizeListener = (
@@ -7,14 +7,13 @@ export const useResizeListener = (
options: { throttleValue?: number } = {}
) => {
options.throttleValue = options.throttleValue ?? 250;
const cb = useCallback(listener, depsArray);
useEffect(() => {
const func = _throttle(cb, 250);
const func = _throttle(listener, 250);
func();
window.addEventListener("resize", func);
return () => {
window.removeEventListener("resize", func);
};
}, [cb]);
}, [listener]);
};

View File

@@ -1,31 +1,31 @@
import { Image, Tag } from ".";
export interface Project {
id: string;
id: number;
title: string;
category: ProjectCategory;
category: Pick<ProjectCategory, 'id' | 'title'>;
website?: string;
description: string;
tags: Tag[];
cover_image: Image;
thumbnail_image: Image;
lightning_address: string,
screenShots: Image[];
votes_count: number;
}
export interface ProjectCategory {
id: string;
id: number;
title: string;
cover_image: string
apps_count: number
}
export interface ProjectCard {
id: string;
id: number;
title: string;
thumbnail_image: string;
category: ProjectCategory;
category: Pick<ProjectCategory, 'id' | 'title'>;
votes_count: number;
}
interface AllCategoriesData {
allCategories: ProjectCategory[];
}

View File

@@ -72,17 +72,17 @@ module.exports = {
none: "none",
},
fontSize: {
h1: ["36px", "30px"],
h2: ["28px", "30px"],
h3: ["24px", "30px"],
h4: ["21px", "30px"],
h5: ["18px", "30px"],
body1: ["24px", "20px"],
body2: ["21px", "20px"],
body3: ["18px", "20px"],
body4: ["16px", "20px"],
body5: ["14px", "20px"],
body6: ["12px", "20px"],
h1: ["48px", "54px"],
h2: ["36px", "50px"],
h3: ["29px", "40px"],
h4: ["22px", "31px"],
h5: ["19px", "26px"],
body1: ["24px", "30px"],
body2: ["20px", "28px"],
body3: ["18px", "25px"],
body4: ["16px", "22px"],
body5: ["14px", "19px"],
body6: ["12px", "18px"],
},
fontFamily: {
sans: ["Inter", "sans-serif"],