feat: category page, some improvments for naming & project structure

This commit is contained in:
MTG2000
2022-03-12 10:01:06 +02:00
parent 31e7842551
commit 8df9d1609d
33 changed files with 287 additions and 155 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

View File

@@ -6,7 +6,7 @@ import ProjectCardMiniSkeleton from './ProjectCardMini.Skeleton';
export default {
title: 'Explore Page/Project Card Mini',
title: 'Projects/Project Card Mini',
component: ProjectCardMini,
} as ComponentMeta<typeof ProjectCardMini>;

View File

@@ -1,5 +1,5 @@
import { MdLocalFireDepartment } from "react-icons/md";
import { ProjectCard } from "../../../utils/interfaces";
import { ProjectCard } from "src/utils/interfaces";
interface Props {
@@ -9,7 +9,7 @@ interface Props {
export default function ProjectCardMini({ project, onClick }: Props) {
return (
<div className="bg-gray-25 select-none px-16 py-16 flex w-[296px] gap-16 border border-gray-200 rounded-10 hover:cursor-pointer hover:bg-gray-100" onClick={() => onClick(project.id)}>
<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>
<div className="justify-around items-start min-w-0">
<p className="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap">{project.title}</p>

View File

@@ -0,0 +1 @@
export { }

View File

@@ -0,0 +1,23 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import HeaderImage from './HeaderImage';
export default {
title: 'Pages/Category/Header Image',
component: HeaderImage,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof HeaderImage>;
const Template: ComponentStory<typeof HeaderImage> = (args) => <HeaderImage {...args} ></HeaderImage>
export const Default = Template.bind({});
Default.args = {
title: 'Art & Collectibles',
apps_count: 44,
img: '/assets/images/header-2.jfif'
}

View File

@@ -0,0 +1,24 @@
import React from 'react'
import { FiArrowLeft } from 'react-icons/fi'
interface Props {
title: string
apps_count: number
img: string
}
export default function HeaderImage({ title, apps_count, img }: 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>
<h1
className='text-white text-body1 md:text-[40px]'
>{title}</h1>
<p
className='text-white text-body4'
>{apps_count} apps</p>
</div>
)
}

View File

@@ -0,0 +1,21 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import ProjectsGrid from './ProjectsGrid';
export default {
title: 'Pages/Category/Projects Grid',
component: ProjectsGrid,
argTypes: {
backgroundColor: { control: 'color' },
},
} as ComponentMeta<typeof ProjectsGrid>;
const Template: ComponentStory<typeof ProjectsGrid> = (args) => <ProjectsGrid {...args} ></ProjectsGrid>
export const Default = Template.bind({});
Default.args = {
}

View File

@@ -0,0 +1,15 @@
import React from 'react'
import ProjectCardMini from 'src/Components/Cards/ProjectCardMini/ProjectCardMini'
import mockData from 'src/api/mockData.json'
export default function ProjectsGrid() {
return (
<div style={{
gridTemplateColumns: 'repeat(auto-fit, minmax(296px, 1fr))',
display: 'grid',
gridGap: '24px',
}}>
{Array(30).fill(0).map((_, idx) => <ProjectCardMini key={idx} project={mockData.projectsCards[0]} onClick={() => { }} />)}
</div>
)
}

View File

@@ -1,5 +1,5 @@
import ProjectCardMiniSkeleton from "../ProjectCardMini/ProjectCardMini.Skeleton";
import ProjectCardMiniSkeleton from "src/Components/Cards/ProjectCardMini/ProjectCardMini.Skeleton";
import Skeleton from "react-loading-skeleton";
export default function ProjectsRowSkeleton() {

View File

@@ -1,15 +1,19 @@
import { ReactElement, useRef, useState } from "react";
import { ProjectCard } from "../../../utils/interfaces";
import { ReactNode, useEffect, useRef, useState } from "react";
import { ProjectCard } from "src/utils/interfaces";
import Carousel from 'react-multi-carousel';
import { MdArrowRight, MdDoubleArrow, } from 'react-icons/md';
import { useAppDispatch } from "../../../utils/hooks";
import { openModal } from "../../../redux/features/modals.slice";
import ProjectCardMini from "../ProjectCardMini/ProjectCardMini";
import { MdDoubleArrow, } from 'react-icons/md';
import { useAppDispatch } from "src/utils/hooks";
import { openModal } from "src/redux/features/modals.slice";
import ProjectCardMini from "src/Components/Cards/ProjectCardMini/ProjectCardMini";
import { useResizeListener } from 'src/utils/hooks'
import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
import './style.css';
interface Props {
title: string | ReactNode,
categoryId: string,
projects: ProjectCard[]
}
const responsive = {
all: {
@@ -29,23 +33,35 @@ function calcNumItems() {
return items;
}
interface Props { title: string | ReactElement, categoryId: string, projects: ProjectCard[] }
export default function ProjectsRow({ title, categoryId, projects }: Props) {
const [carouselItmsCnt, setCarouselItmsCnt] = useState(calcNumItems);
const dispatch = useAppDispatch()
let drag = useRef(false);
responsive.all.items = carouselItmsCnt
let drag = useRef(false);
document.addEventListener('mousedown', () => drag.current = false);
document.addEventListener('mousemove', () => drag.current = true);
useEffect(() => {
const mousedownListener = () => drag.current = false
const mousemoveListener = () => drag.current = true
document.addEventListener('mousedown', mousedownListener);
document.addEventListener('mousemove', mousemoveListener);
return () => {
document.removeEventListener('mousedown', mousedownListener);
document.removeEventListener('mousemove', mousemoveListener);
}
}, [])
const handleClick = (projectId: string) => {
if (!drag.current)
dispatch(openModal({ Modal: "ProjectCard", props: { projectId } }))
dispatch(openModal({ Modal: "ProjectDetailsCard", props: { projectId } }))
}
useResizeListener(() => {
@@ -57,7 +73,7 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
return (
<div id={title.toString().toLowerCase()} className='mb-48'>
<div className='mb-48'>
<h3 className="font-bolder text-body3 mb-24 px-32">{title}
<span>
<MdDoubleArrow className='text-gray-200 ml-8 hover:cursor-pointer align-bottom transform scale-y-110 scale-x-125 origin-left' onClick={() => {
@@ -85,7 +101,9 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
}
>
{projects.map((project, idx) =>
<ProjectCardMini key={idx} project={project} onClick={handleClick} />
<div key={project.id} className='max-w-[296px]'>
<ProjectCardMini project={project} onClick={handleClick} />
</div>
)}
</Carousel>
</div>

View File

@@ -5,7 +5,7 @@ import Claim_CopySignatureCard from './Claim_CopySignatureCard';
import { ModalsDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Claim/Copy Signature Card',
title: 'Projects/Claim/Copy Signature Card',
component: Claim_CopySignatureCard,
decorators: [ModalsDecorator]

View File

@@ -5,7 +5,7 @@ import Claim_FundWithdrawCard from './Claim_FundWithdrawCard';
import { ModalsDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Claim/Fund Withdraw Card',
title: 'Projects/Claim/Fund Withdraw Card',
component: Claim_FundWithdrawCard,
decorators: [ModalsDecorator]

View File

@@ -5,7 +5,7 @@ import Claim_GenerateSignatureCard from './Claim_GenerateSignatureCard';
import { ModalsDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Claim/Generate Signature Card',
title: 'Projects/Claim/Generate Signature Card',
component: Claim_GenerateSignatureCard,
decorators: [ModalsDecorator]

View File

@@ -5,7 +5,7 @@ import Claim_SubmittedCard from './Claim_SubmittedCard';
import { ModalsDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Claim/Submitted Card',
title: 'Projects/Claim/Submitted Card',
component: Claim_SubmittedCard,
decorators: [ModalsDecorator]

View File

@@ -1,27 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import ProjectCard from './ProjectCard';
import ProjectCardSkeleton from './ProjectCard.Skeleton';
import { ModalsDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Project/Project Card',
component: ProjectCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof ProjectCard>;
const Template: ComponentStory<typeof ProjectCard> = (args) => <ProjectCard {...args} />;
export const Default = Template.bind({
});
Default.args = {
projectId: '3'
}
const LoadingTemplate: ComponentStory<typeof ProjectCardSkeleton> = (args) => <ProjectCardSkeleton {...args} />;
export const LoadingState = LoadingTemplate.bind({})

View File

@@ -9,7 +9,7 @@ import { useAppSelector } from 'src/utils/hooks';
interface Props extends ModalCard {
}
export default function ProjectCardSkeleton({ onClose, direction, ...props }: Props) {
export default function ProjectDetailsCardSkeleton({ onClose, direction, ...props }: Props) {

View File

@@ -0,0 +1,27 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import ProjectDetailsCard from './ProjectDetailsCard';
import ProjectDetailsCardSkeleton from './ProjectDetailsCard.Skeleton';
import { ModalsDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Project/Project Details Card',
component: ProjectDetailsCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof ProjectDetailsCard>;
const Template: ComponentStory<typeof ProjectDetailsCard> = (args) => <ProjectDetailsCard {...args} />;
export const Default = Template.bind({
});
Default.args = {
projectId: '3'
}
const LoadingTemplate: ComponentStory<typeof ProjectDetailsCardSkeleton> = (args) => <ProjectDetailsCardSkeleton {...args} />;
export const LoadingState = LoadingTemplate.bind({})

View File

@@ -8,8 +8,8 @@ import { setProject } from 'src/redux/features/project.slice';
import Button from 'src/Components/Button/Button';
import { PROJECT_BY_ID_QUERY, PROJECT_BY_ID_RES, PROJECT_BY_ID_VARS } from './query'
import { AiFillThunderbolt } from 'react-icons/ai';
import ProjectCardSkeleton from './ProjectCard.Skeleton'
import TipButton from 'src/Components/TipButton/TipButton';
import ProjectCardSkeleton from './ProjectDetailsCard.Skeleton'
import VoteButton from 'src/pages/ProjectPage/VoteButton/VoteButton';
import { Wallet_Service } from 'src/services'
@@ -17,7 +17,7 @@ interface Props extends ModalCard {
projectId: string
}
export default function ProjectCard({ onClose, direction, projectId, ...props }: Props) {
export default function ProjectDetailsCard({ onClose, direction, projectId, ...props }: Props) {
const dispatch = useAppDispatch();
@@ -46,15 +46,15 @@ export default function ProjectCard({ onClose, direction, projectId, ...props }:
Wallet_Service.connectWallet()
}
const onTip = (tip?: number) => {
const onVote = (votes?: number) => {
if (!isWalletConnected) {
dispatch(scheduleModal({ Modal: 'TipCard', props: { tipValue: tip, projectId: project.id } }))
dispatch(scheduleModal({ Modal: 'VoteCard', props: { initVotes: votes, projectId: project.id } }))
dispatch(openModal({
Modal: 'Login_ScanningWalletCard'
}))
} else
dispatch(openModal({ Modal: 'TipCard', props: { tipValue: tip, projectId: project.id } }))
dispatch(openModal({ Modal: 'VoteCard', props: { initVotes: votes, projectId: project.id } }))
}
@@ -98,7 +98,7 @@ export default function ProjectCard({ onClose, direction, projectId, ...props }:
<div className="flex-shrink-0 hidden md:flex ml-auto gap-16">
<Button color='primary' size='md' className=" my-16" href={project.website} newTab >Visit <BsJoystick /></Button>
{isWalletConnected ?
<TipButton onTip={onTip} />
<VoteButton onVote={onVote} />
:
<Button onClick={onConnectWallet} size='md' className="border border-gray-200 bg-gray-100 hover:bg-gray-50 active:bg-gray-100 my-16"><AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet to Vote</Button>
}
@@ -108,7 +108,7 @@ export default function ProjectCard({ onClose, direction, projectId, ...props }:
<div className="md:hidden">
<Button color='primary' size='md' fullWidth href={project.website} newTab className="w-full mt-24 mb-16">Visit <BsJoystick /></Button>
{isWalletConnected ?
<TipButton fullWidth onTip={onTip} />
<VoteButton fullWidth onVote={onVote} />
:
<Button size='md' fullWidth className="bg-gray-200 hover:bg-gray-100 mb-24" onClick={onConnectWallet}><AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet to Vote</Button>
}

View File

@@ -1,17 +1,17 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import TipButton from './TipButton';
import VoteButton from './VoteButton';
import { centerDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Shared/Tip Button',
component: TipButton,
title: 'Projects/Tip Button',
component: VoteButton,
decorators: [
centerDecorator
]
} as ComponentMeta<typeof TipButton>;
} as ComponentMeta<typeof VoteButton>;
const Template: ComponentStory<typeof TipButton> = (args) => <TipButton onTip={() => { }} />;
const Template: ComponentStory<typeof VoteButton> = (args) => <VoteButton onVote={() => { }} />;
export const Default = Template.bind({});

View File

@@ -3,7 +3,7 @@ import Button from 'src/Components/Button/Button'
import { useAppSelector, usePressHolder } from 'src/utils/hooks'
import _throttle from 'lodash.throttle'
import { ComponentProps, useRef, useState } from 'react'
import './tipbutton.style.css'
import './vote-button.style.css'
import { random, randomItem } from 'src/utils/helperFunctions'
@@ -17,12 +17,12 @@ interface Particle {
}
type Props = {
onTip: (tip: number) => void
onVote: (Vote: number) => void
} & Omit<ComponentProps<typeof Button>, 'children'>
export default function TipButton({ onTip = () => { }, ...props }: Props) {
const [tipCnt, setTipCnt] = useState(0)
const tipCntRef = useRef(0);
export default function VoteButton({ onVote = () => { }, ...props }: Props) {
const [voteCnt, setVoteCnt] = useState(0)
const voteCntRef = useRef(0);
const [sparks, setSparks] = useState<Particle[]>([]);
const [wasActive, setWasActive] = useState(false);
@@ -30,10 +30,10 @@ export default function TipButton({ onTip = () => { }, ...props }: Props) {
const { onPressDown, onPressUp } = usePressHolder(_throttle(() => {
const _incStep = (Math.ceil((tipCnt + 1) / 10) + 1) ** 2 * 10;
setTipCnt(s => {
const _incStep = (Math.ceil((voteCnt + 1) / 10) + 1) ** 2 * 10;
setVoteCnt(s => {
const newValue = s + _incStep;
tipCntRef.current = newValue;
voteCntRef.current = newValue;
return newValue;
})
@@ -70,14 +70,14 @@ export default function TipButton({ onTip = () => { }, ...props }: Props) {
setWasActive(false);
if (event?.preventDefault) event.preventDefault();
onPressUp();
if (tipCnt === 0)
onTip(10);
if (voteCnt === 0)
onVote(10);
else
setTimeout(() => {
setSparks([]);
onTip(tipCntRef.current);
setTipCnt(0);
tipCntRef.current = 0;
onVote(voteCntRef.current);
setVoteCnt(0);
voteCntRef.current = 0;
}, 500)
}
@@ -91,17 +91,17 @@ export default function TipButton({ onTip = () => { }, ...props }: Props) {
onTouchEnd={handlePressUp}
size='md'
color='none'
className="tip-button border relative 100 my-16 noselect"
className="vote-button border relative 100 my-16 noselect"
style={{
"--scale": tipCnt,
"--scale": voteCnt,
} as any}
{...props}
>
Hold To Vote !!! <MdLocalFireDepartment className='text-fire' />
<span
className='tip-counter'
>{tipCnt}</span>
className='Vote-counter'
>{voteCnt}</span>
<div
className='spark'

View File

@@ -1,17 +1,17 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import TipCard from './TipCard';
import VoteCard from './VoteCard';
import { ModalsDecorator } from 'src/utils/storybookDecorators'
export default {
title: 'Tip/Tip Card',
component: TipCard,
title: 'Components/Cards/Vote Card',
component: VoteCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof TipCard>;
} as ComponentMeta<typeof VoteCard>;
const Template: ComponentStory<typeof TipCard> = (args) => <TipCard {...args} />;
const Template: ComponentStory<typeof VoteCard> = (args) => <VoteCard {...args} />;
export const Default = Template.bind({});

View File

@@ -9,6 +9,7 @@ import useWindowSize from "react-use/lib/useWindowSize";
import Confetti from "react-confetti";
import { Wallet_Service } from 'src/services';
import styles from './style.module.css'
import { CONFIRM_VOTE_QUERY, CONFIRM_VOTE_QUERY_RES_TYPE, VOTE_QUERY, VOTE_QUERY_RES_TYPE } from './query';
const defaultOptions = [
{ text: '100 sat', value: 100 },
@@ -27,36 +28,13 @@ enum PaymentStatus {
CANCELED
}
const VOTE = gql`
mutation Mutation($projectId: Int!, $amountInSat: Int!) {
vote(project_id: $projectId, amount_in_sat: $amountInSat) {
id
amount_in_sat
payment_request
payment_hash
paid
}
}
`;
const CONFIRM_VOTE = gql`
mutation Mutation($paymentRequest: String!, $preimage: String!) {
confirmVote(payment_request: $paymentRequest, preimage: $preimage) {
id
amount_in_sat
paid
payment_hash
payment_request
}
}
`;
interface Props extends ModalCard {
tipValue?: number;
initVotes?: number;
projectId: string
}
export default function TipCard({ onClose, direction, tipValue, projectId, ...props }: Props) {
export default function VoteCard({ onClose, direction, initVotes, projectId, ...props }: Props) {
const { width, height } = useWindowSize()
const { isWalletConnected } = useAppSelector(state => ({
@@ -65,10 +43,10 @@ export default function TipCard({ onClose, direction, tipValue, projectId, ...pr
const [selectedOption, setSelectedOption] = useState(10);
const [voteAmount, setVoteAmount] = useState<number>(tipValue ?? 10);
const [voteAmount, setVoteAmount] = useState<number>(initVotes ?? 10);
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.DEFAULT);
const [vote, { data }] = useMutation(VOTE, {
const [vote, { data }] = useMutation<VOTE_QUERY_RES_TYPE>(VOTE_QUERY, {
onCompleted: async (votingData) => {
try {
setPaymentStatus(PaymentStatus.AWAITING_PAYMENT);
@@ -97,7 +75,7 @@ export default function TipCard({ onClose, direction, tipValue, projectId, ...pr
}
});
const [confirmVote, { data: confirmedVoteData }] = useMutation(CONFIRM_VOTE, {
const [confirmVote, { data: confirmedVoteData }] = useMutation<CONFIRM_VOTE_QUERY_RES_TYPE>(CONFIRM_VOTE_QUERY, {
refetchQueries: [
'Project',
'AllCategoriesProjects'

View File

@@ -0,0 +1,45 @@
import { gql } from "@apollo/client";
export const VOTE_QUERY = gql`
mutation Mutation($projectId: Int!, $amountInSat: Int!) {
vote(project_id: $projectId, amount_in_sat: $amountInSat) {
id
amount_in_sat
payment_request
payment_hash
paid
}
}
`;
export type VOTE_QUERY_RES_TYPE = {
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!) {
confirmVote(payment_request: $paymentRequest, preimage: $preimage) {
id
amount_in_sat
paid
payment_hash
payment_request
}
}
`;
export type CONFIRM_VOTE_QUERY_RES_TYPE = {
confirmVote: {
id: number;
amount_in_sat: number;
paid: boolean;
payment_hash: string;
payment_request: string;
}
}

View File

@@ -5,8 +5,8 @@ import Login_ExternalWalletCard from "src/Components/Modals/Login/Login_External
import Login_NativeWalletCard from "src/Components/Modals/Login/Login_NativeWalletCard";
import Login_SuccessCard from "src/Components/Modals/Login/Login_SuccessCard";
import Login_ScanningWalletCard from "src/Components/Modals/Login/Login_ScanningWalletCard";
import ProjectCard from "src/pages/ProjectPage/ProjectCard/ProjectCard";
import TipCard from "src/pages/ProjectPage/Tip/TipCard";
import ProjectDetailsCard from "src/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard";
import VoteCard from "src/pages/ProjectPage/VoteCard/VoteCard";
import Claim_SubmittedCard from "src/pages/ProjectPage/ClaimProject/Claim_SubmittedCard";
import Claim_FundWithdrawCard from "src/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard";
import { ModalCard } from "src/Components/Modals/ModalsContainer/ModalsContainer";
@@ -22,12 +22,12 @@ export enum Direction {
export const ALL_MODALS = {
ProjectCard,
ProjectDetailsCard,
Login_ScanningWalletCard,
Login_NativeWalletCard,
Login_SuccessCard,
Login_ExternalWalletCard,
TipCard,
VoteCard,
Claim_GenerateSignatureCard,
Claim_CopySignatureCard,
Claim_SubmittedCard,
@@ -117,7 +117,7 @@ export const modalSlice = createSlice({
let props: any = {};
if ('props' in action.payload) props = { ...action.payload.props }
props.isPageModal = action.payload.Modal === 'ProjectCard';
props.isPageModal = action.payload.Modal === 'ProjectDetailsCard';
state.openModals.push({
Modal: action.payload.Modal,
@@ -136,7 +136,7 @@ export const modalSlice = createSlice({
let props: any = {};
if ('props' in action.payload) props = { ...action.payload.props }
props.isPageModal = action.payload.Modal === 'ProjectCard';
props.isPageModal = action.payload.Modal === 'ProjectDetailsCard';
state.openModals.push({
Modal: action.payload.Modal,

View File

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

View File

@@ -0,0 +1,4 @@
export * from './misc.interfaces'
export * from './project.interfaces'

View File

@@ -0,0 +1,8 @@
export interface Tag {
id: string;
title: string;
}
export type Image = string;

View File

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