diff --git a/README.md b/README.md index 2c6187a..5b57fde 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TradeTrust Decentralized Renderer (React Template) -This template serves as a quick way to start building your own decentralized renderer using OpenAttestation stack. +This template serves as a quick way to start building your own decentralized renderer using TradeTrust stack. You can follow our tutorial over at (https://docs.tradetrust.io/docs/tutorial/decentralised-renderer/). ## How and what? @@ -29,38 +29,59 @@ To stay as simple and less opinionated as possible, these are omitted: ### Prerequisite - Node (optionally, use [nvm](https://github.com/nvm-sh/nvm) to manage node version) - - Node version 14 onwards. +- Node version 18 onwards. + +## Install + +The easiest way to use **tradetrust-decentralized-renderer-template** is through github by clicking on `Use this template` button in the repository page. + +You can also download or `git clone` this repo + +```sh +git clone https://github.com/TradeTrust/tradetrust-decentralized-renderer.git +cd tradetrust-decentralized-renderer +rm -rf .git +npm install +``` + +Make sure to edit the following files according to your module's info: + +- package.json (module name and version) +- README.md +- LICENSE +- add your own template (in `src/templates` folder) and configure correctly the template registry (in `src/templates/index.tsx` file) ### Development ```sh -npm i -npm run start +npm run storybook ``` -Head off to `http://localhost:6006/` to see storybook, while `http://127.0.0.1:8080/` to see your actual document rendered in a dummy application. +Head off to `http://localhost:6006/` to see storybook. ### Core Components -Core components, located in the `src/core directory`, are reusable React components that offer enhanced functionalities for renderer templates. +Core components, located in the `src/core directory`, are reusable React components that offer enhanced functionalities for renderer templates. When you run `npm run storybook`, the example templates with core components will be displayed. This repository contains a collection of example templates along with demonstrations of how to use core components. You can find these examples in the `/src/templates` directory. These templates serve as references and guides to help you set up your own templates to meet your unique requirements. #### DocumentQrCode + It allows users to share documents across devices using a QR code. For detailed information on how to use the QR Code Component, please refer to the official documentation [here](https://docs.tradetrust.io/docs/reference/tradetrust-website/qr-code/). -#### Wrapper/ Error Boundary -The Wrapper/Error Boundary Component is designed to handle scenarios where a template cannot be rendered correctly. In such cases, this component acts as a fallback, displaying a user-friendly error message and stack. +#### Wrapper/ ErrorBoundary + +The Wrapper/ErrorBoundary Component is designed to handle scenarios where a template cannot be rendered correctly. In such cases, this component acts as a fallback, displaying a user-friendly error message and stack. -#### PrivacyFilter +#### SelectiveRedaction -The Privacy Filter Component is a powerful tool for safeguarding sensitive information within a document. To use the Privacy Filter in the decentralized renderer, follow these steps +The SelectiveRedaction Component is a powerful tool for safeguarding sensitive information within a document. To use the SelectiveRedaction in the decentralized renderer, follow these steps -- Click the "Edit Document" button within the PrivacyFilter component. +- Click the "Edit Document" button within the SelectiveRedaction component. - Click "Remove" on the redactable values to specify the information you want to remove. -- Click "Done" on the Privacy Filter Component to complete the process. +- Click "Done" on the Component to complete the process. - Download the document with hidden values #### PrintWatermark diff --git a/package.json b/package.json index 507cad3..5b74da3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "run-s check-types build:app", "build:app": "cross-env NODE_ENV=production webpack --progress --mode production", "check-types": "tsc --sourceMap false --noEmit", - "dev": "run-s build:css:renderer dev:server", + "dev": "run-s dev:server", "dev:server": "cross-env NODE_ENV=development webpack-dev-server", "lint": "eslint . --ext .ts,.tsx --max-warnings 0", "lint:fix": "npm run lint -- --fix", diff --git a/src/core/ErrorBoundary/ErrorBoundary.tsx b/src/core/ErrorBoundary/ErrorBoundary.tsx index 0fb2f01..f12db41 100644 --- a/src/core/ErrorBoundary/ErrorBoundary.tsx +++ b/src/core/ErrorBoundary/ErrorBoundary.tsx @@ -9,13 +9,18 @@ interface ErrorBoundaryState { error: { message: string; stack: string }; } -export class ErrorBoundary extends React.Component { +export class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { constructor(props: ErrorBoundaryProps) { super(props); this.state = { hasError: false, error: { message: "", stack: "" } }; } - static getDerivedStateFromError(error: ErrorBoundaryState["error"]): ErrorBoundaryState { + static getDerivedStateFromError( + error: ErrorBoundaryState["error"] + ): ErrorBoundaryState { // Update state so the next render will show the fallback UI. return { hasError: true, error }; } @@ -25,9 +30,12 @@ export class ErrorBoundary extends React.Component -

Template renderer encountered an error

+

+ Template renderer encountered an error +

- Message: {this.state.error.message} + Message:{" "} + {this.state.error.message}
Stack: {this.state.error.stack} diff --git a/src/core/PrivacyFilter/index.ts b/src/core/PrivacyFilter/index.ts deleted file mode 100644 index ce94764..0000000 --- a/src/core/PrivacyFilter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./PrivacyFilter"; diff --git a/src/core/PrivacyFilter/PrivacyFilter.tsx b/src/core/SelectiveRedaction/SelectiveRedaction.tsx similarity index 64% rename from src/core/PrivacyFilter/PrivacyFilter.tsx rename to src/core/SelectiveRedaction/SelectiveRedaction.tsx index e30f09f..f8d387d 100644 --- a/src/core/PrivacyFilter/PrivacyFilter.tsx +++ b/src/core/SelectiveRedaction/SelectiveRedaction.tsx @@ -2,7 +2,7 @@ import { Button } from "@tradetrust-tt/tradetrust-ui-components"; import React, { FunctionComponent } from "react"; import patternWaves from "/static/images/pattern-waves.png"; -interface PrivacyFilterProps { +interface SelectiveRedaction { editable: boolean; onToggleEditable: () => void; options?: { @@ -12,23 +12,36 @@ interface PrivacyFilterProps { }; } -export const PrivacyFilter: FunctionComponent = ({ editable, onToggleEditable, options }) => { +export const SelectiveRedaction: FunctionComponent = ({ + editable, + onToggleEditable, + options, +}) => { const defaultOptions = { - className: "print:hidden bg-cover bg-cerulean-500 text-white rounded-lg p-4 mb-8", + className: + "print:hidden bg-cover bg-cerulean-500 text-white rounded-lg p-4 mb-8", description: `Remove sensitive information on this document by clicking on the edit button. Downloaded document remains valid.`, buttonText: "Edit Document", }; const { className, description, buttonText } = options ?? defaultOptions; return ( -
+
-

The document allows fields to be selectively disclosed.

+

+ The document allows fields to be selectively disclosed. +

{description}

-
diff --git a/src/core/SelectiveRedaction/index.ts b/src/core/SelectiveRedaction/index.ts new file mode 100644 index 0000000..33d37fb --- /dev/null +++ b/src/core/SelectiveRedaction/index.ts @@ -0,0 +1 @@ +export * from "./SelectiveRedaction"; diff --git a/src/templates/examples/TemplateA/TemplateAWithWrapperAndErrorBoundry.tsx b/src/templates/examples/TemplateA/TemplateAWithWrapperAndErrorBoundry.tsx index 9434755..4053426 100644 --- a/src/templates/examples/TemplateA/TemplateAWithWrapperAndErrorBoundry.tsx +++ b/src/templates/examples/TemplateA/TemplateAWithWrapperAndErrorBoundry.tsx @@ -16,7 +16,6 @@ const Content = (document: TemplateADocument): JSX.Element => { - {document.data1} {document.data2} @@ -27,14 +26,15 @@ const Content = (document: TemplateADocument): JSX.Element => { ); }; - -export const TemplateAWithWrapperAndErrorBoundry: FunctionComponent> = ({ document }) => { +export const TemplateAWithWrapperAndErrorBoundry: FunctionComponent< + TemplateProps +> = ({ document }) => { const documentData = getDocumentData(document) as TemplateADocument; return ( <> - -
{Content(documentData)}
-
+ +
{Content(documentData)}
+
); -}; \ No newline at end of file +}; diff --git a/src/templates/examples/TemplateA/index.tsx b/src/templates/examples/TemplateA/index.tsx index fdce721..6c9f8ba 100644 --- a/src/templates/examples/TemplateA/index.tsx +++ b/src/templates/examples/TemplateA/index.tsx @@ -14,7 +14,7 @@ export const TemplateATemplates = [ template: TemplateAWithWrapperAndErrorBoundry, }, { - id: "template-a-with-qr", + id: "template-a-with-watermark", label: "With water mark", template: TemplateAWithWaterMark, }, diff --git a/src/templates/examples/TemplateB/TemplateBWithPrivacyFilter.stories.tsx b/src/templates/examples/TemplateB/TemplateBWithPrivacyFilter.stories.tsx deleted file mode 100644 index 1e40d54..0000000 --- a/src/templates/examples/TemplateB/TemplateBWithPrivacyFilter.stories.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React, { FunctionComponent } from "react"; -import { TemplateBWithPrivacyFilter } from "./TemplateBWithPrivacyFilter"; -import { TemplateBSampleV2 } from "./sampleV2"; - -export default { - title: "TemplateB", - component: TemplateBWithPrivacyFilter, - parameters: { - componentSubtitle: "Sample Template B", - }, -}; - -export const TemplateWithPrivacyFilter: FunctionComponent = () => { - return {}} />; -}; \ No newline at end of file diff --git a/src/templates/examples/TemplateB/TemplateBWithPrivacyFilter.tsx b/src/templates/examples/TemplateB/TemplateBWithPrivacyFilter.tsx deleted file mode 100644 index 1a76321..0000000 --- a/src/templates/examples/TemplateB/TemplateBWithPrivacyFilter.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import styled from "@emotion/styled"; -import { RedactableValue, TemplateProps } from "@tradetrust-tt/decentralized-renderer-react-components"; -import { format } from "date-fns"; -import React, { FunctionComponent, useState } from "react"; -import { DocumentQrCode } from "../../../core/DocumentQrCode"; -import { Wrapper } from "../../../core/Wrapper"; -import { IconRedact, PrivacyFilter } from "../../../core/PrivacyFilter"; -import { getDocumentData } from "../../../utils"; -import { TemplateB, TemplateBSchema } from "./types"; - -const CustomStyles = styled.div` - font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; - position: relative; - - h1 { - color: #4172af; - font-size: 40px; - font-weight: 700; - } - - h2 { - color: #4172af; - font-size: 28px; - font-weight: 700; - } - - .bg-blue { - background-color: #4172af; - color: #fff; - } - - th { - background-color: #4172af; - color: white; - } - - .table-responsive { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } -`; - -export const TemplateBWithPrivacyFilter: FunctionComponent> = ({ - document, - handleObfuscation, -}) => { - const [editable, setEditable] = useState(false); - const documentData = getDocumentData(document) as TemplateB; - const { - id, - date, - customerId, - terms, - billFrom, - billTo, - billableItems, - subtotal = 0, - tax = 0, - taxTotal = 0, - total = 0, - } = documentData; - const qrCodeUrl = documentData?.links?.self.href; - return ( - <> - - - setEditable(!editable)} /> - -
-
-

INVOICE

-
-
-

INVOICE #

-
-
-

DATE

-
-
-
-
-

{id}

-
-
-

{date && format(new Date(date), "d MMM yyyy")}

-
-
-
-
-

CUSTOMER ID

-
-
-

TERMS

-
-
-
-
-

{customerId}

-
-
{terms}
-
-
-
-
-

- handleObfuscation(`billFrom.name`)} - iconRedact={} - /> -

-

- handleObfuscation(`billFrom.streetAddress`)} - iconRedact={} - /> -

-

- {billFrom?.city} - {billFrom?.postalCode && `, `} - handleObfuscation(`billFrom.postalCode`)} - iconRedact={} - /> -

-

- handleObfuscation(`billFrom.phoneNumber`)} - iconRedact={} - /> -

-
-
-
-

BILL TO

-
-
-
-
-

- handleObfuscation(`billTo.name`)} - iconRedact={} - /> -

-

- handleObfuscation(`billTo.company.name`)} - iconRedact={} - /> -

-

- handleObfuscation(`billTo.company.streetAddress`)} - iconRedact={} - /> -

-

- {billTo.company.city} - {billTo.company.postalCode && `, `} - handleObfuscation(`billTo.company.postalCode`)} - iconRedact={} - /> -

-

- handleObfuscation(`billTo.company.phoneNumber`)} - iconRedact={} - /> -

-

- handleObfuscation(`billTo.email`)} - iconRedact={} - /> -

-
-
-
-
-
-
- - - - - - - - - - - {billableItems?.map((item, index) => { - return ( - - - - - - - ); - })} - -
DESCRIPTIONQTYUNIT PRICEAMOUNT
{item.description}{item.quantity}{item.unitPrice} {item.amount}
-
-
-
-

SUBTOTAL

-

TAX (${tax}%)

-
-

BALANCE DUE

-
-
-

{subtotal}

-

{taxTotal && taxTotal}

-
-

{total}

-
-
- {qrCodeUrl && } - - - - ); -}; diff --git a/src/templates/examples/TemplateB/TemplateBWithSelectiveRedaction.stories.tsx b/src/templates/examples/TemplateB/TemplateBWithSelectiveRedaction.stories.tsx new file mode 100644 index 0000000..517b9b0 --- /dev/null +++ b/src/templates/examples/TemplateB/TemplateBWithSelectiveRedaction.stories.tsx @@ -0,0 +1,20 @@ +import React, { FunctionComponent } from "react"; +import { TemplateBWithSelectiveRedaction } from "./TemplateBWithSelectiveRedaction"; +import { TemplateBSampleV2 } from "./sampleV2"; + +export default { + title: "TemplateB", + component: TemplateBWithSelectiveRedaction, + parameters: { + componentSubtitle: "Sample Template B", + }, +}; + +export const TemplateWithSelectiveRedaction: FunctionComponent = () => { + return ( + {}} + /> + ); +}; diff --git a/src/templates/examples/TemplateB/TemplateBWithSelectiveRedaction.tsx b/src/templates/examples/TemplateB/TemplateBWithSelectiveRedaction.tsx new file mode 100644 index 0000000..7b080c9 --- /dev/null +++ b/src/templates/examples/TemplateB/TemplateBWithSelectiveRedaction.tsx @@ -0,0 +1,279 @@ +import styled from "@emotion/styled"; +import { + RedactableValue, + TemplateProps, +} from "@tradetrust-tt/decentralized-renderer-react-components"; +import { format } from "date-fns"; +import React, { FunctionComponent, useState } from "react"; +import { DocumentQrCode } from "../../../core/DocumentQrCode"; +import { Wrapper } from "../../../core/Wrapper"; +import { + IconRedact, + SelectiveRedaction, +} from "../../../core/SelectiveRedaction"; +import { getDocumentData } from "../../../utils"; +import { TemplateB, TemplateBSchema } from "./types"; + +const CustomStyles = styled.div` + font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif; + position: relative; + + h1 { + color: #4172af; + font-size: 40px; + font-weight: 700; + } + + h2 { + color: #4172af; + font-size: 28px; + font-weight: 700; + } + + .bg-blue { + background-color: #4172af; + color: #fff; + } + + th { + background-color: #4172af; + color: white; + } + + .table-responsive { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +`; + +export const TemplateBWithSelectiveRedaction: FunctionComponent< + TemplateProps +> = ({ document, handleObfuscation }) => { + const [editable, setEditable] = useState(false); + const documentData = getDocumentData(document) as TemplateB; + const { + id, + date, + customerId, + terms, + billFrom, + billTo, + billableItems, + subtotal = 0, + tax = 0, + taxTotal = 0, + total = 0, + } = documentData; + const qrCodeUrl = documentData?.links?.self.href; + return ( + <> + + setEditable(!editable)} + /> + +
+
+

INVOICE

+
+
+

INVOICE #

+
+
+

DATE

+
+
+
+
+

{id}

+
+
+

{date && format(new Date(date), "d MMM yyyy")}

+
+
+
+
+

CUSTOMER ID

+
+
+

TERMS

+
+
+
+
+

{customerId}

+
+
{terms}
+
+
+
+
+

+ + handleObfuscation(`billFrom.name`) + } + iconRedact={} + /> +

+

+ + handleObfuscation(`billFrom.streetAddress`) + } + iconRedact={} + /> +

+

+ {billFrom?.city} + {billFrom?.postalCode && `, `} + + handleObfuscation(`billFrom.postalCode`) + } + iconRedact={} + /> +

+

+ + handleObfuscation(`billFrom.phoneNumber`) + } + iconRedact={} + /> +

+
+
+
+

BILL TO

+
+
+
+
+

+ + handleObfuscation(`billTo.name`) + } + iconRedact={} + /> +

+

+ + handleObfuscation(`billTo.company.name`) + } + iconRedact={} + /> +

+

+ + handleObfuscation(`billTo.company.streetAddress`) + } + iconRedact={} + /> +

+

+ {billTo.company.city} + {billTo.company.postalCode && `, `} + + handleObfuscation(`billTo.company.postalCode`) + } + iconRedact={} + /> +

+

+ + handleObfuscation(`billTo.company.phoneNumber`) + } + iconRedact={} + /> +

+

+ + handleObfuscation(`billTo.email`) + } + iconRedact={} + /> +

+
+
+
+
+
+
+ + + + + + + + + + + {billableItems?.map((item, index) => { + return ( + + + + + + + ); + })} + +
DESCRIPTIONQTYUNIT PRICEAMOUNT
{item.description} + {item.quantity} + + {item.unitPrice} + + {" "} + {item.amount} +
+
+
+
+

SUBTOTAL

+

TAX (${tax}%)

+
+

BALANCE DUE

+
+
+

{subtotal}

+

{taxTotal && taxTotal}

+
+

{total}

+
+
+ {qrCodeUrl && } + + + + ); +}; diff --git a/src/templates/examples/TemplateB/index.tsx b/src/templates/examples/TemplateB/index.tsx index dfbd993..bdf75ec 100644 --- a/src/templates/examples/TemplateB/index.tsx +++ b/src/templates/examples/TemplateB/index.tsx @@ -1,9 +1,9 @@ -import { TemplateBWithPrivacyFilter } from "./TemplateBWithPrivacyFilter"; +import { TemplateBWithSelectiveRedaction } from "./TemplateBWithSelectiveRedaction"; export const TemplateBTemplates = [ { id: "template-b", - label: "Template B with privacy filter", - template: TemplateBWithPrivacyFilter, + label: "Template B with SelectiveRedaction", + template: TemplateBWithSelectiveRedaction, }, ];