mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-31 12:14:30 +01:00
feat: added mocks using msw
This commit is contained in:
1043
package-lock.json
generated
1043
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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
338
public/mockServiceWorker.js
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
4
src/mocks/browser.ts
Normal 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
428
src/mocks/data.ts
Normal 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
45
src/mocks/handlers.ts
Normal 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
28
src/mocks/resolvers.ts
Normal 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)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
40
src/pages/CategoryPage/query.ts
Normal file
40
src/pages/CategoryPage/query.ts
Normal 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']
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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[];
|
||||
}[];
|
||||
|
||||
@@ -18,7 +18,7 @@ export const Default = Template.bind({
|
||||
});
|
||||
|
||||
Default.args = {
|
||||
projectId: '3'
|
||||
projectId: 3
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user