Skip to content

Commit

Permalink
Merge pull request #75 from Cardano-Forge/feat/MET-47-project-summary
Browse files Browse the repository at this point in the history
Export + Summary UI
  • Loading branch information
JFKFred authored Oct 18, 2024
2 parents 98a6a99 + d72d658 commit 7d59b8c
Show file tree
Hide file tree
Showing 20 changed files with 472 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default function JSONEditor({
(doc) => doc.toJSON() as RulesCollection,
)[0];

const project = activeProject?._data;
const project = activeProject?.toJSON() as ProjectCollection;

if (!metadatas || !project || !rules) return null;

Expand Down
2 changes: 1 addition & 1 deletion client/src/app/(asset)/metadata/[id]/header/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function HeaderActions({
const projectCollection = useRxCollection<ProjectCollection>("project");
const metadataCollection = useRxCollection<MetadataCollection>("metadata");

const project = activeProject?._data;
const project = activeProject?.toJSON() as ProjectCollection;

if (!metadata || !project) return <div>No metadata found</div>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default function Actions({
return (
<div className={cn("flex flex-row items-center gap-2", className)}>
<Button
title="Mark as flagged"
disabled={isUnchecked}
variant={isWarning ? "warning" : "warningOutilne"}
size={"icon"}
Expand All @@ -96,6 +97,7 @@ export default function Actions({
<FlagIcon className="h-4 w-4" />
</Button>
<Button
title="Mark as valid"
disabled={isUnchecked}
variant={isSuccess ? "success" : "successOutline"}
size={"icon"}
Expand All @@ -107,7 +109,11 @@ export default function Actions({
</Button>
<Dialog>
<DialogTrigger asChild>
<Button size={"icon"} variant={"destructiveOutilne"}>
<Button
title="Delete asset"
size={"icon"}
variant={"destructiveOutilne"}
>
<TrashIcon className="h-4 w-4" />
</Button>
</DialogTrigger>
Expand All @@ -132,6 +138,7 @@ export default function Actions({
</DialogContent>
</Dialog>
<Button
title="Detail asset"
size={"icon"}
variant={"outline"}
className="border-white/50"
Expand Down
4 changes: 3 additions & 1 deletion client/src/app/(validator)/data-validation/validator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function Validator({
(doc) => doc.toJSON() as RulesCollection,
)[0];

const project = activeProject?._data;
const project = activeProject?.toJSON() as ProjectCollection;

if (!metadata || !project || !rules) return null;

Expand All @@ -64,6 +64,8 @@ export default function Validator({
})),
);

// TODO - delete validation when they change to success

// Set the status in metadata
const metadataWithStatus = setMetadataStatusFromValidations(
metadata,
Expand Down
17 changes: 11 additions & 6 deletions client/src/app/(validator)/metadata-structure/how-to.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,21 @@ export default function HowToCreateMetadataSchema() {
>
<AccordionTrigger>
<Typography as="code" className="text-lg">
How to use string value ?
How to use string (text) value ?
</Typography>
</AccordionTrigger>
<AccordionContent>
<ColInnerContent>
<Typography as="code">{`Simply enter "string" as the value for any key that you want to be treated as a string.`}</Typography>
<Typography
as="code"
className="text-sm text-white/50"
>{`A string is represented by double quotes: ""`}</Typography>

<Typography
as="code"
className="ml-4 text-white/50"
>{`Example: { name: "string" }`}</Typography>
>{`Example: { name: "text" }`}</Typography>
</ColInnerContent>
</AccordionContent>
</AccordionItem>
Expand Down Expand Up @@ -119,7 +124,7 @@ export default function HowToCreateMetadataSchema() {
<Typography
as="code"
className="ml-4 text-white/50"
>{`Example: { "name": "string", "age": 0, child: { name: "string", age: 0 }, position: [ [ 0, 1 ] ] }`}</Typography>
>{`Example: { "name": "text", "age": 0, child: { name: "text", age: 0 }, position: [ [ 0, 1 ] ] }`}</Typography>
</ColInnerContent>
</AccordionContent>
</AccordionItem>
Expand Down Expand Up @@ -152,7 +157,7 @@ export default function HowToCreateMetadataSchema() {
<Typography
as="code"
className="ml-4 text-white/50"
>{`Usage: [ "string" ]`}</Typography>
>{`Usage: [ "text" ]`}</Typography>
<Typography as="code">
<strong>number[] –</strong>
{` A list of numbers.`}
Expand All @@ -172,11 +177,11 @@ export default function HowToCreateMetadataSchema() {
<Typography
as="code"
className="ml-4 text-white/50"
>{`Example: [ { src: "string", mediaType: "string" } ]`}</Typography>
>{`Example: [ { src: "text", mediaType: "text" } ]`}</Typography>
<Typography
as="code"
className="ml-4 text-white/50"
>{`Usage: [ { src: "string", mediaType: "string" } ]`}</Typography>
>{`Usage: [ { src: "text", mediaType: "text" } ]`}</Typography>
<Typography as="code">
<strong>{`<type>`}[][] –</strong>
{` A list of arrays of <type> (arrays within arrays)`}
Expand Down
52 changes: 52 additions & 0 deletions client/src/app/(validator)/summary/components/data-validation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { ProjectCollection } from "~/lib/types";
import { useActiveProject } from "~/providers/active-project.provider";

import { StepComponent, StepHeader } from "./step-components";
import Stat from "~/components/stat";
import MessageBox from "~/components/message-box";
import ValidatorStats from "./validator-stats";

export default function DataValidation() {
const activeProject = useActiveProject();
const project = activeProject?.toJSON() as ProjectCollection;

const hasError = !!project.errorsDetected;
const hasWarning = !!project.errorsFlagged;
const status = hasError ? "error" : hasWarning ? "warning" : "success";

return (
<StepComponent>
<StepHeader title="NFTs data validation" step={3} status={status} />
<div className="flex flex-row gap-10 p-4">
<Stat icon="database" stat={project.nfts}>
NFTs in this collection
</Stat>
<Stat icon="clock" stat={project.unchecked}>
NFTs unchecked
</Stat>
<Stat icon="exclamation" stat={project.errorsDetected} variant="error">
Errors detected
</Stat>
<Stat icon="flag" stat={project.errorsFlagged} variant="warning">
Errors flagged
</Stat>
<Stat icon="check" stat={project.valids} variant="success">
Marked as valid
</Stat>
</div>
{hasError && (
<MessageBox variant="error">
We still detect some errors in this step. Make sure you correct them
before exporting your project.
</MessageBox>
)}
{hasWarning && (
<MessageBox>
We still see some flagged errors in this step. Make sure you check
them before exporting your project.
</MessageBox>
)}
<ValidatorStats />
</StepComponent>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import { JsonEditor } from "json-edit-react";

import { StepComponent, StepHeader } from "./step-components";
import type { MetadataSchemaCollection, MetadataCollection } from "~/lib/types";
import { useRxData } from "rxdb-hooks";
import LoaderComponent from "~/components/loader-component";
import { getAttributesDistributions } from "~/lib/get/get-attributes-distributions";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "~/components/ui/accordion";
import { jerTheme } from "~/lib/json-editor";
import ValuesIcon from "~/icons/values.icon";

export default function MetadataStructure() {
const { result, isFetching } = useRxData<MetadataCollection>(
"metadata",
(collection) => collection.find(),
);

const { result: schemaResult, isFetching: isFetchingSchema } =
useRxData<MetadataSchemaCollection>("metadataSchema", (collection) =>
collection.find(),
);

if (isFetching || isFetchingSchema) return <LoaderComponent />;

const metadatas: MetadataCollection[] = result.map(
(doc) => doc.toJSON() as MetadataCollection,
);

const schema: MetadataSchemaCollection | undefined = schemaResult.map(
(doc) => doc.toJSON() as MetadataSchemaCollection,
)[0];

if (!schema || !metadatas) return <div>No data found.</div>;

const distribution = getAttributesDistributions(metadatas, schema);

return (
<StepComponent>
<StepHeader title="Metadata strucutre" />
<Accordion type="single" collapsible>
<AccordionItem value="distribution">
<AccordionTrigger className="hover:no-underline">
<div className="ml-2 flex flex-row items-center gap-4 text-white/60">
<div className="items-center justify-center rounded-full border border-white/60 p-2">
<ValuesIcon className="h-4 w-4" />
</div>
{`Value's distribution`}
</div>
</AccordionTrigger>
<AccordionContent className="rounded-xl bg-card p-1">
<JsonEditor
data={distribution}
theme={jerTheme}
rootFontSize={18}
minWidth={"100%"}
collapse={1}
enableClipboard={false} // Disabled copy to clipboard
restrictEdit={() => true} // Disabled edit
restrictAdd={() => true} // Disabled add
restrictDelete={() => true} // Disabled delete
/>
</AccordionContent>
</AccordionItem>
</Accordion>
</StepComponent>
);
}
61 changes: 61 additions & 0 deletions client/src/app/(validator)/summary/components/rules-selection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from "react";
import { StepComponent, StepHeader } from "./step-components";
import { Typography } from "~/components/typography";
import { useRxData } from "rxdb-hooks";
import { type RulesCollection } from "~/lib/types";
import { useActiveProject } from "~/providers/active-project.provider";
import LoaderComponent from "~/components/loader-component";
import { hyphenToTitleCase } from "~/lib/hyphen-to-title-case";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "~/components/ui/accordion";
import { chunk } from "~/lib/chunk";
import CodeIcon from "~/icons/code.icon";

export default function RulesSelection() {
const activeProject = useActiveProject();
const { result, isFetching } = useRxData<RulesCollection>(
"rules",
(collection) => collection.findByIds([activeProject?.id ?? ""]),
);

if (isFetching) return <LoaderComponent />;

const rules: RulesCollection | undefined = result.map(
(doc) => doc.toJSON() as RulesCollection,
)[0];

if (!rules) return null;

return (
<StepComponent>
<StepHeader title="Rules selection" step={2} />
<Accordion type="single" collapsible>
<AccordionItem value="rules">
<AccordionTrigger className="hover:no-underline">
<div className="ml-2 flex flex-row items-center gap-4 text-white/60">
<div className="items-center justify-center rounded-full border border-white/60 p-2">
<CodeIcon className="h-4 w-4" />
</div>
{`${rules.rules.length} Rule(s) selected for validation`}
</div>
</AccordionTrigger>
<AccordionContent className="flex flex-row gap-10 rounded-xl bg-card p-4">
{chunk(rules.rules, 4).map((rules, i) => (
<ul key={`${i}`}>
{rules.map((rule) => (
<li key={rule}>
<Typography>{hyphenToTitleCase(rule)}</Typography>
</li>
))}
</ul>
))}
</AccordionContent>
</AccordionItem>
</Accordion>
</StepComponent>
);
}
72 changes: 72 additions & 0 deletions client/src/app/(validator)/summary/components/step-components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Typography } from "~/components/typography";
import CheckCircleIcon from "~/icons/check-circle.icon";
import ExclamationIcon from "~/icons/exclamation.icon";
import InformationCircle from "~/icons/information-circle";
import SummaryIcon from "~/icons/summary.icon";
import { type Status } from "~/lib/types";
import { cn } from "~/lib/utils";

export function StepComponent({ children }: { children: React.ReactNode }) {
return (
<div className="flex flex-col gap-4 rounded-xl bg-background p-4">
{children}
</div>
);
}

export function StepHeader({
step = 1,
title,
status = "success",
}: {
step?: number;
title: string;
status?: Status;
}) {
return (
<>
<div className="flex flex-row items-center justify-between px-2">
<div className="flex flex-col">
<Typography className="text-white/50">{`Step ${step}`}</Typography>
<Typography as="h3">{title}</Typography>
</div>
<StepStatus status={status} />
</div>
<hr className="border-white/20" />
</>
);
}

const statusClassName: Record<Status, string> = {
success: "text-success",
warning: "text-warning",
error: "text-destructive",
unchecked: "text-white/50",
};

const icons: Record<Status, React.ReactNode> = {
success: <CheckCircleIcon className="h-8 w-8" />,
warning: <InformationCircle className="h-8 w-8" />,
error: <ExclamationIcon className="h-8 w-8" />,
unchecked: <SummaryIcon className="h-8 w-8" />,
};

const text: Record<Status, string> = {
success: "Validated",
warning: "Recommandation",
error: "Error detected",
unchecked: "Unchecked",
};

export function StepStatus({ status }: { status: Status }) {
return (
<Typography
className={cn(
"flex flex-row items-center gap-4 tracking-wide",
statusClassName[status],
)}
>
{text[status]} {icons[status]}
</Typography>
);
}
Loading

0 comments on commit 7d59b8c

Please sign in to comment.