This commit is contained in:
2023-07-24 23:46:18 +03:00
commit 6051ed0b82
121 changed files with 14058 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
import React from "react";
import { Showcase } from "../../../utils/apiTypes";
import { mapUrl } from "../../../utils/map";
import { CardTags } from "./CardTags";
export interface CardProps {
network: Showcase;
}
export const Card = React.memo(({ network }: CardProps) => (
<div className="card">
<div className="card__image">
<div style={{ height: "140px" }}>
<img src={mapUrl(network.nodes ?? [])} alt={network.title} />
</div>
</div>
<div className="card__body">
<h4>{network.title}</h4>
<small>{network.summary}</small>
</div>
<div className="card__footer">
<a
href={`?id=${network.id}`}
className="button button--primary button--block"
style={{ marginBottom: "0.5rem" }}
>
Read more
</a>
<CardTags tags={network.tags} />
</div>
</div>
));
export const PlaceholderCard = (): JSX.Element => (
<div
className="card"
style={{
animation: "pulse 2s infinite",
transform: "scale(1)",
}}
>
<div className="card__image">
<div
style={{
height: "140px",
}}
/>
</div>
<div className="card__body">
<div
style={{
width: "30%",
height: "2rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
marginBottom: "1rem",
}}
/>
<div
style={{
width: "100%",
height: "1rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
marginBottom: "0.5rem",
}}
/>
<div
style={{
width: "100%",
height: "1rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
</div>
<div className="card__footer">
<button
className="button disabled button--primary button--block"
style={{ marginBottom: "0.5rem" }}
>
&nbsp;
</button>
<div
style={{
display: "flex",
gap: "0.5rem",
}}
>
<div
style={{
width: "4rem",
height: "1.5rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
<div
style={{
width: "4rem",
height: "1.5rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,29 @@
import React from "react";
import { ShowcaseTag } from "../../../utils/apiTypes";
export interface CardTagsProps {
tags: ShowcaseTag[];
}
export const CardTags = ({ tags }: CardTagsProps) => {
return (
<div>
{tags.map(({ color, label }) => {
return (
<span
className="badge"
key={label}
style={{
backgroundColor: color,
marginRight: "0.3rem",
userSelect: "none",
}}
>
{label}
</span>
);
})}
</div>
);
};

View File

@@ -0,0 +1,90 @@
import React from "react";
import { FiHeart } from "react-icons/fi";
import useSWR from "swr";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { fetcher } from "../../../utils/swr";
import { ShowcaseTag } from "../../../utils/apiTypes";
// import { TagList, Tags } from '../../../utils/showcase';
import { PlaceholderTagSelect, TagSelect } from "./TagSelect";
export const Filters = (): JSX.Element => {
const { siteConfig } = useDocusaurusContext();
const { data, error } = useSWR<ShowcaseTag[]>(
`${siteConfig.customFields.API_URL}/showcase/tags`,
fetcher,
);
return (
<section className="margin-top--l margin-bottom--lg container">
{data && !error ? (
<ul
style={{
padding: "0",
display: "flex",
alignItems: "center",
flexWrap: "wrap",
}}
>
{data.map((tag) => {
const { label, color } = tag;
const id = `showcase_checkbox_id_${tag};`;
return (
<div
key={tag.id}
style={{
boxSizing: "border-box",
position: "relative",
display: "inline-flex",
alignItems: "center",
height: "2rem",
marginTop: "0.5rem",
marginRight: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
verticalAlign: "middle",
userSelect: "none",
}}
>
<TagSelect
tag={tag}
id={id}
label={label}
icon={
tag.label === "Favorite" ? (
<span
style={{
display: "flex",
marginLeft: "0.5rem",
color: "rgb(190 24 93)",
}}
>
<FiHeart />
</span>
) : (
<span
style={{
backgroundColor: color,
width: 10,
height: 10,
borderRadius: "50%",
marginLeft: 8,
}}
/>
)
}
/>
</div>
);
})}
</ul>
) : (
<PlaceholderTagSelect />
)}
</section>
);
};

View File

@@ -0,0 +1,318 @@
import React from "react";
import useSWR from "swr";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { Showcase } from "../../../utils/apiTypes";
import { fetcher } from "../../../utils/swr";
interface NetworkProps {
id: string;
}
export const Network = ({ id }: NetworkProps): JSX.Element => {
const { siteConfig } = useDocusaurusContext();
const { data, error } = useSWR<Showcase>(
`${siteConfig.customFields.API_URL}/showcase/${id}`,
fetcher,
);
const githubData = useSWR(
`https://api.github.com/users/${data?.author?.githubUsername}`,
fetcher,
).data;
return (
<div>
{data && !error ? (
<div className="container">
<h1>{data.title}</h1>
<p>{data.summary}</p>
{githubData && (
<div className="avatar">
<img
src={githubData.avatar_url}
alt={githubData.name}
className="avatar__photo"
/>
<div className="avatar__intro">
<div className="avatar__name">{githubData.name}</div>
<div className="avatar__subtitle">{githubData.bio}</div>
</div>
</div>
)}
<div className="markdown">{data.body}</div>
<div
className="card"
style={{
marginLeft: "auto",
marginRight: "auto",
maxWidth: "900px",
}}
>
<div
className="card__header"
style={{
margin: "8px",
}}
>
<h2>Bill of Materials</h2>
</div>
<div className="card__body">
{data.materials?.map((material) => (
<div
key={material.id}
style={{
borderTop: "2px solid gray",
display: "flex",
}}
>
<div
style={{
width: "4rem",
display: "flex",
}}
>
<img
src={material.image}
height="auto"
width="100%"
style={{
margin: "auto",
padding: "4px",
display: "block",
maxWidth: "60px",
maxHeight: "60px",
width: "auto",
height: "auto",
}}
/>
</div>
<div className="avatar__intro">
<div className="avatar__name">{material.name}</div>
<small className="avatar__subtitle">
{material.details}
</small>
</div>
<a
target="_blank"
rel="noreferrer"
href={material.url}
className="button button--outline button--secondary"
style={{
marginTop: "auto",
marginBottom: "auto",
}}
>
View
</a>
</div>
))}
</div>
</div>
</div>
) : (
<div>
{error && <div>{JSON.stringify(error)}</div>}
{!data && <PlaceholderNetwork />}
</div>
)}
</div>
);
};
export const PlaceholderNetwork = (): JSX.Element => {
return (
<div
className="container"
style={{
display: "flex",
flexDirection: window.innerWidth > 768 ? "row" : "column",
gap: "2rem",
}}
>
<div
style={{
width: window.innerWidth > 768 ? "60%" : "100%",
}}
>
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
flexDirection: "column",
gap: "2rem",
padding: "2rem",
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "4rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "12rem",
}}
/>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "999px",
backgroundColor: "gray",
height: "4rem",
width: "4rem",
minWidth: "4rem",
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
width: "100%",
}}
>
<div
style={{
width: "100%",
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "1rem",
}}
/>
<div
style={{
width: "100%",
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
}}
/>
</div>
</div>
</div>
</div>
<div
style={{
width: window.innerWidth > 768 ? "40%" : "100%",
}}
>
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
flexDirection: "column",
gap: "2rem",
padding: "2rem",
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "12rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
}}
/>
<div style={{ display: "flex", gap: "0.5rem" }}>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
</div>
<div
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "20%",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "60%",
}}
/>
<a
className="button disabled button--primary button--block"
style={{ width: "20%" }}
>
&nbsp;
</a>
</div>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "20%",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "60%",
}}
/>
<a
className="button disabled button--primary button--block"
style={{ width: "20%" }}
>
&nbsp;
</a>
</div>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,67 @@
import React from "react";
import { Showcase } from "../../../utils/apiTypes";
import { Card, PlaceholderCard } from "./Card";
interface NetworkSectionProps {
title: string;
icon?: JSX.Element;
iconColor?: string;
networks?: Showcase[];
}
export const NetworkSection = ({
title,
icon,
iconColor,
networks,
}: NetworkSectionProps): JSX.Element => {
return (
<div className="margin-top--lg container">
<div
className="margin-bottom--sm"
style={{
display: "flex",
alignItems: "center",
}}
>
<h2>{title}</h2>
{icon && (
<span
style={{
marginBottom: "0.5rem",
marginLeft: "0.5rem",
fontSize: "1.25rem",
lineHeight: "1.75rem",
color: iconColor,
}}
>
{icon}
</span>
)}
</div>
<ul
style={{
position: "relative",
display: "grid",
gap: "1.5rem",
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
paddingLeft: "0",
}}
>
{networks ? (
<>
{networks.map((network) => (
<Card key={network.title} network={network} />
))}
{networks.length === 0 && <h2>No result</h2>}
</>
) : (
<div>
<PlaceholderCard />
</div>
)}
</ul>
</div>
);
};

View File

@@ -0,0 +1,52 @@
import React from "react";
import { FiHeart, FiSearch } from "react-icons/fi";
import useSWR from "swr";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { useSelectedTags } from "../../../hooks/useSelectedTags";
import { useFilteredNetworks } from "../../../hooks/useFilteredNetworks";
import { Showcase } from "../../../utils/apiTypes";
import { fetcher } from "../../../utils/swr";
import { NetworkSection } from "./NetworkSection";
export const Networks = (): JSX.Element => {
const { siteConfig } = useDocusaurusContext();
const { data, error } = useSWR<Showcase[]>(
`${siteConfig.customFields.API_URL}/showcase`,
fetcher,
);
const selectedTags = useSelectedTags();
const filteredNetworks = useFilteredNetworks(data ?? []);
return (
<section className="margin-top--lg margin-bottom--xl">
{!error ? (
selectedTags.length === 0 ? (
<>
<NetworkSection
title="Our favorites"
icon={<FiHeart />}
iconColor="rgb(190 24 93)"
networks={data?.filter((network) =>
network.tags.find((tag) => tag.label === "Favorite"),
)}
/>
<NetworkSection title="All networks" networks={data} />
</>
) : (
<NetworkSection
title="Results"
icon={<FiSearch />}
networks={filteredNetworks}
/>
)
) : (
<div>{JSON.stringify(error)}</div>
)}
</section>
);
};

View File

@@ -0,0 +1,111 @@
import "url-search-params-polyfill";
import React from "react";
import { useHistory, useLocation } from "@docusaurus/router";
import { ShowcaseTag } from "../../../utils/apiTypes";
import { toggleListItem } from "../../../utils/showcase";
interface Props extends React.ComponentProps<"input"> {
icon: React.ReactElement<React.ComponentProps<"svg">>;
label: React.ReactNode;
tag: ShowcaseTag;
}
export function readSearchTags(search: string): string[] {
return new URLSearchParams(search).getAll("tags");
}
function replaceSearchTags(search: string, newTags: string[]) {
const searchParams = new URLSearchParams(search);
searchParams.delete("tags");
newTags.forEach((tag) => searchParams.append("tags", tag));
return searchParams.toString();
}
export const TagSelect = React.forwardRef<HTMLLabelElement, Props>(
({ icon, label, tag }) => {
const location = useLocation();
const history = useHistory();
const [selected, setSelected] = React.useState(false);
React.useEffect(() => {
const tags = readSearchTags(location.search);
setSelected(tags.includes(tag.label));
}, [tag, location]);
const toggleTag = React.useCallback(() => {
const tags = readSearchTags(location.search);
const newTags = toggleListItem(tags, tag.label);
const newSearch = replaceSearchTags(location.search, newTags);
history.push({ ...location, search: newSearch });
}, [tag, location, history]);
return (
<button
style={{
display: "flex",
alignItems: "center",
}}
className={`button button--sm button--outline button--secondary ${
selected ? "button--active" : ""
}`}
onClick={() => {
toggleTag();
}}
>
{label}
{icon}
</button>
);
},
);
export const PlaceholderTagSelect = (): JSX.Element => (
<div
style={{
boxSizing: "border-box",
position: "relative",
display: "inline-flex",
alignItems: "center",
height: "2rem",
marginTop: "0.5rem",
marginRight: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
verticalAlign: "middle",
userSelect: "none",
}}
>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
marginLeft: 8,
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
marginLeft: 8,
}}
/>
</div>
);

View File

@@ -0,0 +1,35 @@
import "url-search-params-polyfill";
import React from "react";
import { useLocation } from "@docusaurus/router";
import Layout from "@theme/Layout";
import { Filters } from "./_components/Filters";
import { Network } from "./_components/Network";
import { Networks } from "./_components/Networks";
const Showcase = (): JSX.Element => {
const location = useLocation();
const id = new URLSearchParams(location.search).get("id");
return (
<Layout
title="Showcase"
description="Portfolio of projects from the Dead community"
>
<main className="margin-vert--lg">
{id ? (
<Network id={id} />
) : (
<>
<Filters />
<Networks />
</>
)}
</main>
</Layout>
);
};
export default Showcase;