merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit df1918755c0af900c6c447f1d4e0ef27174fb26e
parent e1bc057a4adb73aaebf605f3c8b2de33b5808845
Author: Sebastian <sebasjm@gmail.com>
Date:   Wed, 10 Feb 2021 16:02:06 -0300

simple login, but integrated with merchant backend for create update and delete

Diffstat:
M.gitignore | 1+
M.storybook/main.js | 4++--
M.storybook/preview.js | 1+
Mpackage.json | 11+++++++++--
Msrc/components/hooks/backend.ts | 63++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/components/navbar/index.tsx | 2+-
Msrc/declaration.d.ts | 7++++++-
Msrc/routes/dashboard/index.tsx | 120++++++++++++++++++++++++++++++++++++++++----------------------------------------
Asrc/routes/instances/ConfirmModal.tsx | 31+++++++++++++++++++++++++++++++
Asrc/routes/instances/CreateModal.tsx | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/routes/instances/DeleteAllModal.tsx | 30++++++++++++++++++++++++++++++
Asrc/routes/instances/DeleteModal.tsx | 16++++++++++++++++
Msrc/routes/instances/Table.tsx | 196++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Asrc/routes/instances/UpdateModal.stories.tsx | 24++++++++++++++++++++++++
Asrc/routes/instances/UpdateModal.tsx | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/routes/instances/View.stories.tsx | 45+++++++++++++++++++++++++++------------------
Msrc/routes/instances/View.tsx | 71+++++++++++++++++++++++++++++++++++------------------------------------
Msrc/routes/instances/index.tsx | 18++++++++++++------
Msrc/scss/main.scss | 23+++++++++++++++++++++++
Myarn.lock | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
20 files changed, 772 insertions(+), 220 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,4 @@ /build /node_modules /size-plugin.json +/storybook-static diff --git a/.storybook/main.js b/.storybook/main.js @@ -5,7 +5,7 @@ module.exports = { ], "addons": [ "@storybook/preset-scss", - "@storybook/addon-links", - "@storybook/addon-essentials" + // "@storybook/addon-a11y", + "@storybook/addon-essentials" //docs, control, actions, viewpot, toolbar, background ] } \ No newline at end of file diff --git a/.storybook/preview.js b/.storybook/preview.js @@ -1,5 +1,6 @@ import "../src/scss/main.scss" export const parameters = { + controls: { expanded: true }, actions: { argTypesRegex: "^on[A-Z].*" }, } \ No newline at end of file diff --git a/package.json b/package.json @@ -20,17 +20,24 @@ ], "ignorePatterns": [ "build/" - ] + ], + "rules": { + "@typescript-eslint/camelcase": [ + "off" + ] + } }, "dependencies": { "axios": "^0.21.1", "preact": "^10.3.1", "preact-router": "^3.2.1", - "swr": "^0.4.1" + "swr": "^0.4.1", + "yup": "^0.32.8" }, "devDependencies": { "@babel/core": "^7.12.13", "@babel/plugin-transform-react-jsx-source": "^7.12.13", + "@storybook/addon-a11y": "^6.1.17", "@storybook/addon-actions": "^6.1.16", "@storybook/addon-essentials": "^6.1.16", "@storybook/addon-links": "^6.1.16", diff --git a/src/components/hooks/backend.ts b/src/components/hooks/backend.ts @@ -1,4 +1,4 @@ -import useSWR, { mutate } from 'swr'; +import useSWR, { mutate as globalMutate } from 'swr'; import axios from 'axios' import { MerchantBackend } from '../../declaration'; @@ -20,13 +20,20 @@ class AuthError extends Error { const BACKEND = 'http://localhost:9966' const TOKEN_KEY = 'backend-token' -// eslint-disable-next-line @typescript-eslint/no-explicit-any -async function fetcher(url: string): Promise<any> { +type Methods = 'get' | 'post' | 'patch' | 'delete' | 'put'; + +async function request(url: string, method?: Methods, data?: object): Promise<any> { const token = localStorage.getItem(TOKEN_KEY) const headers = token ? { Authorization: `Bearer secret-token:${token}` } : undefined - const res = await axios.get(`${BACKEND}/private/${url}`, { headers }) - if (res.status == 200) return res.data + const res = await axios({ + method: method || 'get', + url: `${BACKEND}/private${url}`, + responseType: 'json', + headers, + data + }) + if (res.status == 200 || res.status == 204) return res.data if (res.status == 401) throw new AuthError() const error = new Error('An error occurred while fetching the data.') @@ -35,13 +42,51 @@ async function fetcher(url: string): Promise<any> { throw { info, status, ...error } } +async function fetcher(url: string): Promise<any> { + return request(url, 'get') +} + export function updateToken(token: string): void { localStorage.setItem(TOKEN_KEY, token) - mutate('instances') + globalMutate('instances') +} + +interface WithCreate<T> { + create: (data: T) => Promise<void>; +} +interface WithUpdate<T> { + update: (id: string, data: T) => Promise<void>; +} +interface WithDelete { + delete: (id: string) => Promise<void>; } -export function useBackendInstances(): HttpResponse<MerchantBackend.Instances.InstancesResponse> { - const { data, error } = useSWR<MerchantBackend.Instances.InstancesResponse>('instances', fetcher) +export function useBackendInstances(): HttpResponse<MerchantBackend.Instances.InstancesResponse> & WithCreate<MerchantBackend.Instances.InstanceConfigurationMessage> { + const { data, error, mutate } = useSWR<MerchantBackend.Instances.InstancesResponse>('/instances', fetcher) + + const create = async (instance: MerchantBackend.Instances.InstanceConfigurationMessage) => { + await request('/instances', 'post', instance) + + globalMutate('/instances') + } + + return { data, needsAuth: error instanceof AuthError, error, create } +} + +export function useBackendInstance(id: string | null): HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> & WithUpdate<MerchantBackend.Instances.InstanceReconfigurationMessage> & WithDelete { + const { data, error } = useSWR<MerchantBackend.Instances.QueryInstancesResponse>(id ? `/instances/${id}` : null, fetcher) + + const update = async (updateId: string, instance: MerchantBackend.Instances.InstanceReconfigurationMessage) => { + await request(`/instances/${updateId}`, 'patch', instance) + + globalMutate(`/instances/${updateId}`, null) + }; + const _delete = async (deleteId: string) => { + await request(`/instances/${deleteId}`, 'delete') + + globalMutate('/instances') + globalMutate(`/instances/${deleteId}`, null) + } - return { data, needsAuth: error instanceof AuthError, error } + return { data, needsAuth: error instanceof AuthError, error, update, delete: _delete } } diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx @@ -12,7 +12,7 @@ export default function NavigationBar(): VNode { </div> </div> <div class="navbar-brand is-right"> - <a class="navbar-item is-hidden-desktop jb-navbar-menu-toggle" data-target="navbar-menu"> + <a class="navbar-item is-hidden-desktop jb-navbar-menu-toggle"> <span class="icon"><i class="mdi mdi-dots-vertical" /></span> </a> </div> diff --git a/src/declaration.d.ts b/src/declaration.d.ts @@ -1,3 +1,4 @@ +import { number } from "yup/lib/locale"; declare module "*.css" { const mapping: Record<string, string>; @@ -9,7 +10,7 @@ declare module "*.scss" { } type EddsaPublicKey = string; -// type RelativeTime = Duration; +type RelativeTime = Duration; interface Timestamp { // Milliseconds since epoch, or the special // value "forever" to represent an event that will @@ -22,6 +23,10 @@ interface Duration { d_ms: number | "forever"; } +interface WidthId { + id: string; +} + type Amount = string; type UUID = string; type Integer = number; diff --git a/src/routes/dashboard/index.tsx b/src/routes/dashboard/index.tsx @@ -155,13 +155,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/rebecca-bauch.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Rebecca Bauch</td> - <td data-label="Company">Daugherty-Daniel</td> - <td data-label="City">South Cory</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Rebecca Bauch</td> + <td>Daugherty-Daniel</td> + <td>South Cory</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="79">79</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Oct 25, 2020">Oct 25, 2020</small> </td> <td class="is-actions-cell"> @@ -169,7 +169,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -181,13 +181,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/felicita-yundt.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Felicita Yundt</td> - <td data-label="Company">Johns-Weissnat</td> - <td data-label="City">East Ariel</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Felicita Yundt</td> + <td>Johns-Weissnat</td> + <td>East Ariel</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="67">67</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Jan 8, 2020">Jan 8, 2020</small> </td> <td class="is-actions-cell"> @@ -195,7 +195,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -207,13 +207,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/mr-larry-satterfield-v.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Mr. Larry Satterfield V</td> - <td data-label="Company">Hyatt Ltd</td> - <td data-label="City">Windlerburgh</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Mr. Larry Satterfield V</td> + <td>Hyatt Ltd</td> + <td>Windlerburgh</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="16">16</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Dec 18, 2020">Dec 18, 2020</small> </td> <td class="is-actions-cell"> @@ -221,7 +221,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -233,13 +233,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/mr-broderick-kub.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Mr. Broderick Kub</td> - <td data-label="Company">Kshlerin, Bauch and Ernser</td> - <td data-label="City">New Kirstenport</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Mr. Broderick Kub</td> + <td>Kshlerin, Bauch and Ernser</td> + <td>New Kirstenport</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="71">71</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Sep 13, 2020">Sep 13, 2020</small> </td> <td class="is-actions-cell"> @@ -247,7 +247,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -259,13 +259,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/barry-weber.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Barry Weber</td> - <td data-label="Company">Schulist, Mosciski and Heidenreich</td> - <td data-label="City">East Violettestad</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Barry Weber</td> + <td>Schulist, Mosciski and Heidenreich</td> + <td>East Violettestad</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="80">80</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Jul 24, 2020">Jul 24, 2020</small> </td> <td class="is-actions-cell"> @@ -273,7 +273,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -285,13 +285,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/bert-kautzer-md.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Bert Kautzer MD</td> - <td data-label="Company">Gerhold and Sons</td> - <td data-label="City">Mayeport</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Bert Kautzer MD</td> + <td>Gerhold and Sons</td> + <td>Mayeport</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="62">62</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Mar 30, 2020">Mar 30, 2020</small> </td> <td class="is-actions-cell"> @@ -299,7 +299,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -311,13 +311,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/lonzo-steuber.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Lonzo Steuber</td> - <td data-label="Company">Skiles Ltd</td> - <td data-label="City">Marilouville</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Lonzo Steuber</td> + <td>Skiles Ltd</td> + <td>Marilouville</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="17">17</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Feb 12, 2020">Feb 12, 2020</small> </td> <td class="is-actions-cell"> @@ -325,7 +325,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -337,13 +337,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/jonathon-hahn.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Jonathon Hahn</td> - <td data-label="Company">Flatley Ltd</td> - <td data-label="City">Billiemouth</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Jonathon Hahn</td> + <td>Flatley Ltd</td> + <td>Billiemouth</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="74">74</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Dec 30, 2020">Dec 30, 2020</small> </td> <td class="is-actions-cell"> @@ -351,7 +351,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -363,13 +363,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/ryley-wuckert.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Ryley Wuckert</td> - <td data-label="Company">Heller-Little</td> - <td data-label="City">Emeraldtown</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Ryley Wuckert</td> + <td>Heller-Little</td> + <td>Emeraldtown</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="54">54</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Jun 28, 2020">Jun 28, 2020</small> </td> <td class="is-actions-cell"> @@ -377,7 +377,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> @@ -389,13 +389,13 @@ export default function BulmaIndex({}): VNode { <img src="https://avatars.dicebear.com/4.5/api/male/sienna-hayes.svg" class="is-rounded" /> </div> </td> - <td data-label="Name">Sienna Hayes</td> - <td data-label="Company">Conn, Jerde and Douglas</td> - <td data-label="City">Jonathanfort</td> - <td data-label="Progress" class="is-progress-cell"> + <td>Sienna Hayes</td> + <td>Conn, Jerde and Douglas</td> + <td>Jonathanfort</td> + <td class="is-progress-cell"> <progress max="100" class="progress is-small is-primary" value="55">55</progress> </td> - <td data-label="Created"> + <td> <small class="has-text-grey is-abbr-like" title="Mar 7, 2020">Mar 7, 2020</small> </td> <td class="is-actions-cell"> @@ -403,7 +403,7 @@ export default function BulmaIndex({}): VNode { <button class="button is-small is-primary" type="button"> <span class="icon"><i class="mdi mdi-eye" /></span> </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> + <button class="button is-small is-danger jb-modal" type="button"> <span class="icon"><i class="mdi mdi-trash-can" /></span> </button> </div> diff --git a/src/routes/instances/ConfirmModal.tsx b/src/routes/instances/ConfirmModal.tsx @@ -0,0 +1,30 @@ +import { h, VNode } from "preact"; + +interface Props { + active?: boolean; + description?: string; + onCancel?: () => void; + onConfirm?: () => void; + children?: VNode[]; + danger?: boolean; +} + +export default function ConfirmModal({ active, description, onCancel, onConfirm, children, danger }: Props): VNode { + return <div class={active ? "modal is-active is-clipped" : "modal"}> + <div class="modal-background jb-modal-close" onClick={onCancel} /> + <div class="modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">Confirm action: {description}</p> + <button class="delete jb-modal-close" aria-label="close" onClick={onCancel} /> + </header> + <section class="modal-card-body"> + {children} + </section> + <footer class="modal-card-foot"> + <button class="button jb-modal-close" onClick={onCancel} >Cancel</button> + <button class={danger ? "button is-danger jb-modal-close" : "button is-info jb-modal-close"} onClick={onConfirm} >Confirm</button> + </footer> + </div> + <button class="modal-close is-large jb-modal-close" aria-label="close" onClick={onCancel} /> + </div> +} +\ No newline at end of file diff --git a/src/routes/instances/CreateModal.tsx b/src/routes/instances/CreateModal.tsx @@ -0,0 +1,86 @@ +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { Duration, MerchantBackend } from "../../declaration"; +import * as yup from 'yup'; +import ConfirmModal from "./ConfirmModal"; + +function stringToArray(this: yup.AnySchema, current: any, original: string): string[] { + if (this.isType(current)) return current; + return original.split(',').filter(s => s.length > 0) +} +function numberToDuration(this: yup.AnySchema, current: any, original: string): Duration { + if (this.isType(current)) return current; + console.log('numberToDuration', current, original) + const d_ms = parseInt(original, 10) + return { d_ms } +} +/* +validations + * delays size + * payto-uri format + * currency +*/ + +const schema = yup.object().shape({ + id: yup.string().required().label("Id"), + name: yup.string().required().label("Name"), + payto_uris: yup.array().of(yup.string()) + .min(1).label("Payment Method") + .meta({ placeholder: 'comma separated values' }) + .transform(stringToArray), + default_max_deposit_fee: yup.string().required().label("Max Deposit Fee"), + default_max_wire_fee: yup.string().required().label("Max Wire"), + default_wire_fee_amortization: yup.number().required().label("WireFee Amortization"), + // default_pay_delay: yup.number().required().label("Pay delay").transform(numberToDuration), + // default_wire_transfer_delay: yup.number().required().label("Wire transfer Delay").transform(numberToDuration), +}); + +interface Props { + active: boolean; + onCancel: () => void; + onConfirm: (i: MerchantBackend.Instances.InstanceConfigurationMessage) => void; +} + +interface KeyValue { + [key: string]: string; +} + +export default function CreateModal({ active, onCancel, onConfirm }: Props): VNode { + const [value, valueHandler] = useState((active || {}) as any) + const [errors, setErrors] = useState<KeyValue>({}) + + const submit = (): void => { + try { + schema.validateSync(value, { abortEarly: false }) + onConfirm({ ...schema.cast(value), address: {}, jurisdiction: {}, default_wire_transfer_delay: { d_ms: 6000 }, default_pay_delay: { d_ms: 3000 } } as MerchantBackend.Instances.InstanceConfigurationMessage); + } catch (err) { + const errors = err.inner as yup.ValidationError[] + const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) + setErrors(pathMessages) + } + } + + return <ConfirmModal description="update instance" active={active} onConfirm={submit} onCancel={onCancel}> + {Object.keys(schema.fields).map(f => { + + const info = schema.fields[f].describe() + + // Just text field for now + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label">{info.label}</label> + </div> + <div class="field-body"> + <div class="field"> + <p class="control is-expanded has-icons-left"> + <input class="input" type="text" placeholder={info?.meta?.placeholder} readonly={info?.meta?.readonly} name={f} value={value[f]} onChange={e => valueHandler(prev => ({ ...prev, [f]: e.currentTarget.value }))} /> + </p> + {errors[f] ? <p class="help is-danger">{errors[f]}</p> : null} + </div> + </div> + </div> + + })} + + </ConfirmModal> +} +\ No newline at end of file diff --git a/src/routes/instances/DeleteAllModal.tsx b/src/routes/instances/DeleteAllModal.tsx @@ -0,0 +1,29 @@ +import { h, VNode } from "preact"; +import { MerchantBackend } from "../../declaration"; + +interface Props { + element: MerchantBackend.Instances.Instance | null; + onCancel: () => void; + onConfirm: (i: MerchantBackend.Instances.Instance) => void; +} + +export default function DeleteAllModal({ element, onCancel, onConfirm }: Props): VNode { + return <div class={element ? "modal is-active is-clipped" : "modal"}> + <div class="modal-background jb-modal-close" onClick={e => onCancel()} /> + <div class="modal-card"> + <header class="modal-card-head"> + <p class="modal-card-title">Confirm action</p> + <button class="delete jb-modal-close" aria-label="close" onClick={e => onCancel()} /> + </header> + <section class="modal-card-body"> + <p>This will permanently delete instance "{element?.name}" with id <b>{element?.id}</b></p> + <p>Please confirm this action</p> + </section> + <footer class="modal-card-foot"> + <button class="button jb-modal-close" onClick={e => onCancel()} >Cancel</button> + <button class="button is-danger jb-modal-close" onClick={element ? e => onConfirm(element) : undefined} >Delete</button> + </footer> + </div> + <button class="modal-close is-large jb-modal-close" aria-label="close" onClick={e => onCancel()} /> + </div> +} +\ No newline at end of file diff --git a/src/routes/instances/DeleteModal.tsx b/src/routes/instances/DeleteModal.tsx @@ -0,0 +1,16 @@ +import { h, VNode } from "preact"; +import { MerchantBackend } from "../../declaration"; +import ConfirmModal from "./ConfirmModal"; + +interface Props { + element: MerchantBackend.Instances.Instance; + onCancel: () => void; + onConfirm: (i: MerchantBackend.Instances.Instance) => void; +} + +export default function DeleteModal({ element, onCancel, onConfirm }: Props): VNode { + return <ConfirmModal description="delete instance" danger active onCancel={onCancel} onConfirm={() => onConfirm(element)}> + <p>This will permanently delete instance "{element.name}" with id <b>{element.id}</b></p> + <p>Please confirm this actionssss</p> + </ConfirmModal> +} diff --git a/src/routes/instances/Table.tsx b/src/routes/instances/Table.tsx @@ -1,88 +1,130 @@ import { h, VNode } from "preact"; -import { MerchantBackend } from "../../declaration"; +import { useEffect, useState } from "preact/hooks"; +import { MerchantBackend, WidthId as WithId } from "../../declaration"; +import DeleteModal from './DeleteModal' +import UpdateModal from './UpdateModal' +import CreateModal from './CreateModal' interface Props { instances: MerchantBackend.Instances.Instance[]; + onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) => void; + onUpdate: (id: string, d: MerchantBackend.Instances.InstanceReconfigurationMessage) => void; + onDelete: (id: string) => void; + onSelect: (id: string | null) => void; + selected: MerchantBackend.Instances.QueryInstancesResponse & WithId | undefined; } -export default function Table({ instances }: Props): VNode { - return <div class="b-table has-pagination"> - <div class="table-wrapper has-mobile-cards"> - <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> - <thead> - <tr> - <th class="is-checkbox-cell"> - <label class="b-checkbox checkbox"> - <input type="checkbox" value="false" /> - <span class="check" /> - </label> - </th> - <th /> - <th>id</th> - <th>name</th> - <th>City</th> - <th>Progress</th> - <th>Created</th> - <th /> - </tr> - </thead> - <tbody> - {instances.map(i => { - return <tr> - <td class="is-checkbox-cell"> - <label class="b-checkbox checkbox"> - <input type="checkbox" value="false" /> - <span class="check" /> - </label> - </td> - <td class="is-image-cell"> - <div class="image"> - <img src="https://avatars.dicebear.com/4.5/api/female/rebecca-bauch.svg" class="is-rounded" /> - </div> - </td> - <td data-label="Name">Rebecca Bauch</td> - <td data-label="Company">Daugherty-Daniel</td> - <td data-label="City">South Cory</td> - <td data-label="Progress" class="is-progress-cell"> - <progress max="100" class="progress is-small is-primary" value="79">79</progress> - </td> - <td data-label="Created"> - <small class="has-text-grey is-abbr-like" title="Oct 25, 2020">Oct 25, 2020</small> - </td> - <td class="is-actions-cell"> - <div class="buttons is-right"> - <button class="button is-small is-primary" type="button"> - <span class="icon"><i class="mdi mdi-eye" /></span> - </button> - <button class="button is-small is-danger jb-modal" data-target="sample-modal" type="button"> - <span class="icon"><i class="mdi mdi-trash-can" /></span> - </button> - </div> - </td> - </tr> - })} +function toggleSelected<T>(id: T) { + return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] : prev.filter(e => e != id) +} - </tbody> - </table> - </div> - <div class="notification"> - <div class="level"> - <div class="level-left"> - <div class="level-item"> - <div class="buttons has-addons"> - <button type="button" class="button is-active">1</button> - <button type="button" class="button">2</button> - <button type="button" class="button">3</button> - </div> - </div> - </div> - <div class="level-right"> - <div class="level-item"> - <small>Page 1 of 3</small> - </div> +interface Actions { + element: MerchantBackend.Instances.Instance; + type: 'DELETE' | 'UPDATE'; +} + +function notEmpty<TValue>(value: TValue | null | undefined): value is TValue { + return value !== null && value !== undefined; +} + +function buildActions(intances: MerchantBackend.Instances.Instance[], selected: string[], action: 'DELETE'): Actions[] { + return selected.map(id => intances.find(i => i.id === id)) + .filter(notEmpty) + .map(id => ({ element: id, type: action })) +} + +export default function Table({ instances, onCreate, onDelete, onSelect, onUpdate, selected }: Props): VNode { + const [toBeCreated, toBeCreatedHandler] = useState<boolean>(false) + const [actionQueue, actionQueueHandler] = useState<Actions[]>([]); + const [toBeDeleted, toBeDeletedHandler] = useState<MerchantBackend.Instances.Instance | null>(null) + + const [rowSelection, rowSelectionHandler] = useState<string[]>([]) + + useEffect(() => { + if (actionQueue.length > 0 && !toBeDeleted && actionQueue[0].type == 'DELETE') { + toBeDeletedHandler(actionQueue[0].element) + actionQueueHandler(actionQueue.slice(1)) + } + }, [actionQueue, toBeDeleted]) + + useEffect(() => { + if (actionQueue.length > 0 && !selected && actionQueue[0].type == 'UPDATE') { + onSelect(actionQueue[0].element.id) + actionQueueHandler(actionQueue.slice(1)) + } + }, [actionQueue, selected]) + + return <div class="card has-table"> + <header class="card-header"> + <p class="card-header-title"><span class="icon"><i class="mdi mdi-account-multiple" /></span>Instances</p> + <button class={rowSelection.length > 0 ? "card-header-icon" : "card-header-icon is-hidden"} type="button" onClick={e => actionQueueHandler(buildActions(instances, rowSelection, 'DELETE'))} > + <span class="icon"><i class="mdi mdi-trash-can" /></span> + </button> + <button class="card-header-icon" type="button" onClick={e => toBeCreatedHandler(true)}> + <span class="icon"><i class="mdi mdi-plus" /></span> + </button> + </header> + <div class="card-content"> + <div class="b-table has-pagination"> + <div class="table-wrapper has-mobile-cards"> + <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> + <thead> + <tr> + <th class="is-checkbox-cell"> + <label class="b-checkbox checkbox"> + <input type="checkbox" checked={rowSelection.length === instances.length} onClick={e => rowSelectionHandler(rowSelection.length === instances.length ? [] : instances.map(i => i.id))} /> + <span class="check" /> + </label> + </th> + <th>id</th> + <th>name</th> + <th>public key</th> + <th>payments</th> + <th /> + </tr> + </thead> + <tbody> + {instances.map(i => { + return <tr> + <td class="is-checkbox-cell"> + <label class="b-checkbox checkbox"> + <input type="checkbox" checked={rowSelection.indexOf(i.id) != -1} onClick={e => rowSelectionHandler(toggleSelected(i.id))} /> + <span class="check" /> + </label> + </td> + <td >{i.id}</td> + <td >{i.name}</td> + <td >{i.merchant_pub}</td> + <td >{i.payment_targets}</td> + <td class="is-actions-cell"> + <div class="buttons is-right"> + <button class="button is-small is-primary" type="button" onClick={e => onSelect(i.id)}> + <span class="icon"><i class="mdi mdi-eye" /></span> + </button> + <button class="button is-small is-danger jb-modal" type="button" onClick={e => toBeDeletedHandler(i)}> + <span class="icon"><i class="mdi mdi-trash-can" /></span> + </button> + </div> + </td> + </tr> + })} + + </tbody> + </table> </div> + {toBeDeleted ? <DeleteModal element={toBeDeleted} onCancel={() => toBeDeletedHandler(null)} onConfirm={(i) => { + onDelete(i.id) + toBeDeletedHandler(null); + }} /> : null} + {selected ? <UpdateModal element={selected} onCancel={() => onSelect(null)} onConfirm={(i) => { + onUpdate(selected.id, i) + onSelect(null); + }} /> : null} + {toBeCreated ? <CreateModal active={toBeCreated} onCancel={() => toBeCreatedHandler(false)} onConfirm={(i) => { + onCreate(i) + toBeCreatedHandler(false); + }} /> : null} </div> </div> - </div> - + </div > } \ No newline at end of file diff --git a/src/routes/instances/UpdateModal.stories.tsx b/src/routes/instances/UpdateModal.stories.tsx @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { h } from 'preact'; +import UpdateModal from './UpdateModal' + + +export default { + title: 'Instances/UpdateModal', + component: UpdateModal, + argTypes: { + element: { control: 'object' }, + onCancel: { action: 'onCancel' }, + onConfirm: { action: 'onConfirm' }, + } +}; + +export const WithDefaultInstance = (a) => <UpdateModal {...a} />; +WithDefaultInstance.args = { + element: { + id: 'default', + name: 'the default instance', + merchant_pub: 'abcdef', + payment_targets: ['qwe','asd'] + } +} diff --git a/src/routes/instances/UpdateModal.tsx b/src/routes/instances/UpdateModal.tsx @@ -0,0 +1,76 @@ +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { MerchantBackend } from "../../declaration"; +import * as yup from 'yup'; +import ConfirmModal from "./ConfirmModal"; + +function stringToArray(this: yup.AnySchema, current: any, original: string): string[] { + if (this.isType(current)) return current; + return original.split(',').filter(s => s.length > 0) +} + +const schema = yup.object().shape({ + name: yup.string().required().label("Name"), + payto_uris: yup.array().of(yup.string()) + .min(1).label("Payment Method") + .meta({ placeholder: 'comma separated values' }) + .transform(stringToArray), + default_max_deposit_fee: yup.string().required().label("Max Deposit Fee"), + default_max_wire_fee: yup.string().required().label("Max Wire"), + default_wire_fee_amortization: yup.number().required().label("WireFee Amortization"), + // default_pay_delay: yup.number().required().label("Pay delay").transform(numberToDuration), + // default_wire_transfer_delay: yup.number().required().label("Wire transfer Delay").transform(numberToDuration), +}); + +interface Props { + element: MerchantBackend.Instances.QueryInstancesResponse | null; + onCancel: () => void; + onConfirm: (i: MerchantBackend.Instances.InstanceReconfigurationMessage) => void; +} + +interface KeyValue { + [key: string]: string; +} + +export default function UpdateModal({ element, onCancel, onConfirm }: Props): VNode { + const copy = !element ? {} : Object.keys(schema.fields).reduce((prev,cur) => ({...prev, [cur]: (element as any)[cur] }), {}) + + const [value, valueHandler] = useState(copy) + const [errors, setErrors] = useState<KeyValue>({}) + + const submit = (): void => { + try { + schema.validateSync(value, { abortEarly: false }) + + onConfirm({...schema.cast(value), address: {}, jurisdiction: {}, default_wire_transfer_delay: { d_ms: 6000 }, default_pay_delay: { d_ms: 3000 }} as MerchantBackend.Instances.InstanceReconfigurationMessage); + } catch (err) { + const errors = err.inner as yup.ValidationError[] + const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ ...prev, [cur.path]: cur.message }), {}) + setErrors(pathMessages) + } + } + + return <ConfirmModal description="update instance" active={element != null} onConfirm={submit} onCancel={onCancel}> + {Object.keys(schema.fields).map(f => { + + const info = schema.fields[f].describe() + + // Just text field for now + return <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label">{info.label}</label> + </div> + <div class="field-body"> + <div class="field"> + <p class="control is-expanded has-icons-left"> + <input class="input" type="text" placeholder={info?.meta?.placeholder} readonly={info?.meta?.readonly} name={f} value={value[f]} onChange={e => valueHandler(prev => ({ ...prev, [f]: e.currentTarget.value }))} /> + </p> + {errors[f] ? <p class="help is-danger">{errors[f]}</p> : null} + </div> + </div> + </div> + + })} + + </ConfirmModal> +} +\ No newline at end of file diff --git a/src/routes/instances/View.stories.tsx b/src/routes/instances/View.stories.tsx @@ -13,23 +13,32 @@ export default { }, }; -export const Empty = () => <View instances={[]} /> +export const Empty = (a) => <View {...a} />; +Empty.args = { + instances: [] +} -export const WithDefaultInstance = () => <View instances={[{ - id: 'default', - name: 'the default instance', - merchant_pub: 'abcdef', - payment_targets: [] -}]} /> +export const WithDefaultInstance = (a) => <View {...a} />; +WithDefaultInstance.args = { + instances: [{ + id: 'default', + name: 'the default instance', + merchant_pub: 'abcdef', + payment_targets: [] + }] +} -export const WithTwoInstance = () => <View instances={[{ - id: 'first', - name: 'the first instance', - merchant_pub: 'abcdefgh', - payment_targets: [] -},{ - id: 'second', - name: 'other instance', - merchant_pub: 'zxcvvbnm', - payment_targets: ['pay-to'] -}]} /> +export const WithTwoInstance = (a) => <View {...a} />; +WithTwoInstance.args = { + instances: [{ + id: 'first', + name: 'the first instance', + merchant_pub: 'abcdefgh', + payment_targets: [] + },{ + id: 'second', + name: 'other instance', + merchant_pub: 'zxcvvbnm', + payment_targets: ['pay-to'] + }] +} diff --git a/src/routes/instances/View.tsx b/src/routes/instances/View.tsx @@ -1,14 +1,41 @@ import { h, VNode } from "preact"; -import { MerchantBackend } from "../../declaration"; +import { MerchantBackend, WidthId } from "../../declaration"; import Table from './Table'; interface Props { instances: MerchantBackend.Instances.Instance[]; + onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) => void; + onUpdate: (id: string, d: MerchantBackend.Instances.InstanceReconfigurationMessage) => void; + onDelete: (id: string) => void; + onSelect: (id: string | null) => void; + selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId | undefined; } -export default function View({ instances }: Props): VNode { +export default function View({ instances, onCreate, onDelete, onSelect, onUpdate, selected }: Props): VNode { return <div id="app"> + <div class="toast"> + <article class="message"> + <div class="message-header"> + <p>Normal message</p> + <button class="delete" aria-label="delete" /> + </div> + <div class="message-body"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + </div> + </article> + <article class="message is-danger"> + <div class="message-header"> + <p>Normal message</p> + <button class="delete" aria-label="delete" /> + </div> + <div class="message-body"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + </div> + </article> + </div> + <section class="section is-title-bar"> + <div class="level"> <div class="level-left"> <div class="level-item"> @@ -35,40 +62,12 @@ export default function View({ instances }: Props): VNode { </div> </section> <section class="section is-main-section"> - <div class="card has-table"> - <header class="card-header"> - <p class="card-header-title"> - <span class="icon"><i class="mdi mdi-account-multiple" /></span> - Instances - </p> - <a href="#" class="card-header-icon"> - <span class="icon"><i class="mdi mdi-reload" /></span> - </a> - </header> - <div class="card-content"> - <Table instances={instances} /> - </div> - </div> - + <Table instances={instances} onCreate={onCreate} + onUpdate={onUpdate} + onDelete={onDelete} onSelect={onSelect} + selected={selected} + /> </section> - <div id="sample-modal" class="modal"> - <div class="modal-background jb-modal-close" /> - <div class="modal-card"> - <header class="modal-card-head"> - <p class="modal-card-title">Confirm action</p> - <button class="delete jb-modal-close" aria-label="close" /> - </header> - <section class="modal-card-body"> - <p>This will permanently delete <b>Some Object</b></p> - <p>This is sample modal</p> - </section> - <footer class="modal-card-foot"> - <button class="button jb-modal-close">Cancel</button> - <button class="button is-danger jb-modal-close">Delete</button> - </footer> - </div> - <button class="modal-close is-large jb-modal-close" aria-label="close" /> - </div> - </div> + </div > } \ No newline at end of file diff --git a/src/routes/instances/index.tsx b/src/routes/instances/index.tsx @@ -1,15 +1,22 @@ import { h, VNode } from 'preact'; import View from './View'; import LoginPage from '../../components/auth/LoginPage'; -import { updateToken, useBackendInstances } from '../../components/hooks/backend'; +import { updateToken, useBackendInstance, useBackendInstances } from '../../components/hooks/backend'; +import { useState } from 'preact/hooks'; export default function Instances(): VNode { - const resp = useBackendInstances() + const list = useBackendInstances() + const [selectedId, select] = useState<string|null>(null) + const details = useBackendInstance(selectedId) - if (!resp.data) { + if (!list.data || (selectedId != null && !details.data)) { return <LoginPage onLogIn={updateToken} /> } - return <View instances={resp.data.instances} />; -} -\ No newline at end of file + return <View instances={list.data.instances} + onCreate={list.create} onUpdate={details.update} + onDelete={details.delete} onSelect={select} + selected={ !details.data || !selectedId ? undefined : {...details.data, id:selectedId} } + />; +} diff --git a/src/scss/main.scss b/src/scss/main.scss @@ -20,3 +20,26 @@ @import "modal"; @import "footer"; @import "misc"; + +@import "fonts/nunito.css"; +@import "icons/materialdesignicons-4.9.95.min.css"; + +.toast { + position: fixed; + right: 10px; + top: 10px; + z-index: 999; + + display: flex; + flex-direction: column; + padding: 15px; + text-align: right; + align-items: flex-end; + width: auto; + pointer-events: none; +} + +.toast > .message { + white-space:pre-wrap; + opacity:80%; +} diff --git a/yarn.lock b/yarn.lock @@ -960,7 +960,7 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== @@ -1018,7 +1018,7 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@emotion/cache@^10.0.27": +"@emotion/cache@^10.0.27", "@emotion/cache@^10.0.9": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ== @@ -1028,7 +1028,7 @@ "@emotion/utils" "0.11.3" "@emotion/weak-memoize" "0.2.5" -"@emotion/core@^10.1.1": +"@emotion/core@^10.0.9", "@emotion/core@^10.1.1": version "10.1.1" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3" integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA== @@ -1040,7 +1040,7 @@ "@emotion/sheet" "0.9.4" "@emotion/utils" "0.11.3" -"@emotion/css@^10.0.27": +"@emotion/css@^10.0.27", "@emotion/css@^10.0.9": version "10.0.27" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw== @@ -1514,6 +1514,28 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@storybook/addon-a11y@^6.1.17": + version "6.1.17" + resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-6.1.17.tgz#67fe6e140c6a1365f6547ac9458c454c9d7ad572" + integrity sha512-RBL0sx5FuNrPNW7GQaSdyly5WwsqD7FB+APPPFYvEbwyIksS5rZ3x2JnO2iM6UQxCkYxOL2Ou3ZPgJloXfwjxA== + dependencies: + "@storybook/addons" "6.1.17" + "@storybook/api" "6.1.17" + "@storybook/channels" "6.1.17" + "@storybook/client-api" "6.1.17" + "@storybook/client-logger" "6.1.17" + "@storybook/components" "6.1.17" + "@storybook/core-events" "6.1.17" + "@storybook/theming" "6.1.17" + axe-core "^4.0.1" + core-js "^3.0.1" + global "^4.3.2" + lodash "^4.17.15" + react-sizeme "^2.5.2" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/addon-actions@6.1.17", "@storybook/addon-actions@^6.1.16": version "6.1.17" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.1.17.tgz#9d32336284738cefa69b99acafa4b132d5533600" @@ -1633,6 +1655,31 @@ regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" +"@storybook/addon-knobs@^6.1.17": + version "6.1.17" + resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-6.1.17.tgz#c7cdd5be813c2b80ce7f464eabf8ceb06486e82d" + integrity sha512-WUkoGtHhXIurXFQybsMXZaphAtCNclZjZHvji8O5eg+ahx7pIM/Wldh3uJwOdOkW5LHxT76hLxPvuXvOEysnbw== + dependencies: + "@storybook/addons" "6.1.17" + "@storybook/api" "6.1.17" + "@storybook/channels" "6.1.17" + "@storybook/client-api" "6.1.17" + "@storybook/components" "6.1.17" + "@storybook/core-events" "6.1.17" + "@storybook/theming" "6.1.17" + copy-to-clipboard "^3.0.8" + core-js "^3.0.1" + escape-html "^1.0.3" + fast-deep-equal "^3.1.1" + global "^4.3.2" + lodash "^4.17.15" + prop-types "^15.7.2" + qs "^6.6.0" + react-color "^2.17.0" + react-lifecycles-compat "^3.0.4" + react-select "^3.0.8" + regenerator-runtime "^0.13.7" + "@storybook/addon-links@^6.1.16": version "6.1.17" resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-6.1.17.tgz#53cd2f85a83006950cd86cef9975ae8311ac1de4" @@ -1651,6 +1698,26 @@ regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" +"@storybook/addon-storysource@^6.1.17": + version "6.1.17" + resolved "https://registry.yarnpkg.com/@storybook/addon-storysource/-/addon-storysource-6.1.17.tgz#8db1ce2851227a2fe705d1b7f2d6cd4cff786481" + integrity sha512-baRAlC8EErK0slK1GP8XgE34omiQP3zip7690yB2zj5aBdDiUUIjeBqzhluW840OEIGUrBicvLZaO8JmLSffTA== + dependencies: + "@storybook/addons" "6.1.17" + "@storybook/api" "6.1.17" + "@storybook/client-logger" "6.1.17" + "@storybook/components" "6.1.17" + "@storybook/router" "6.1.17" + "@storybook/source-loader" "6.1.17" + "@storybook/theming" "6.1.17" + core-js "^3.0.1" + estraverse "^4.2.0" + loader-utils "^2.0.0" + prettier "~2.0.5" + prop-types "^15.7.2" + react-syntax-highlighter "^13.5.0" + regenerator-runtime "^0.13.7" + "@storybook/addon-toolbars@6.1.17": version "6.1.17" resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.1.17.tgz#15a015dc871e26be66340b5641631e649925c5e2" @@ -2207,6 +2274,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/lodash@^4.14.165": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + "@types/markdown-to-jsx@^6.11.0": version "6.11.3" resolved "https://registry.yarnpkg.com/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.3.tgz#cdd1619308fecbc8be7e6a26f3751260249b020e" @@ -3062,6 +3134,11 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axe-core@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224" + integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg== + axios@^0.21.1: version "0.21.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" @@ -5327,6 +5404,14 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" + integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -5769,7 +5854,7 @@ escape-goat@^2.0.0: resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== -escape-html@~1.0.3: +escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= @@ -8993,7 +9078,7 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.15: +lodash-es@^4.17.11, lodash-es@^4.17.15: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7" integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA== @@ -9270,6 +9355,11 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +memoize-one@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== + memoizerific@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" @@ -9592,6 +9682,11 @@ nan@^2.12.1, nan@^2.13.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -11109,7 +11204,7 @@ prompts@^2.0.1, prompts@^2.2.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -11118,6 +11213,11 @@ prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, object-assign "^4.1.1" react-is "^16.8.1" +property-expr@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" + integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== + property-information@^5.0.0, property-information@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" @@ -11420,6 +11520,13 @@ react-hotkeys@2.0.0: dependencies: prop-types "^15.6.1" +react-input-autosize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85" + integrity sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg== + dependencies: + prop-types "^15.5.8" + react-inspector@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.0.tgz#45a325e15f33e595be5356ca2d3ceffb7d6b8c3a" @@ -11466,7 +11573,21 @@ react-refresh@0.8.3: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== -react-sizeme@^2.6.7: +react-select@^3.0.8: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.2.0.tgz#de9284700196f5f9b5277c5d850a9ce85f5c72fe" + integrity sha512-B/q3TnCZXEKItO0fFN/I0tWOX3WJvi/X2wtdffmwSQVRwg5BpValScTO1vdic9AxlUgmeSzib2hAZAwIUQUZGQ== + dependencies: + "@babel/runtime" "^7.4.4" + "@emotion/cache" "^10.0.9" + "@emotion/core" "^10.0.9" + "@emotion/css" "^10.0.9" + memoize-one "^5.0.0" + prop-types "^15.6.0" + react-input-autosize "^3.0.0" + react-transition-group "^4.3.0" + +react-sizeme@^2.5.2, react-sizeme@^2.6.7: version "2.6.12" resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.6.12.tgz#ed207be5476f4a85bf364e92042520499455453e" integrity sha512-tL4sCgfmvapYRZ1FO2VmBmjPVzzqgHA7kI8lSJ6JS6L78jXFNRdOZFpXyK6P1NBZvKPPCZxReNgzZNUajAerZw== @@ -11496,6 +11617,16 @@ react-textarea-autosize@^8.1.1: use-composed-ref "^1.0.0" use-latest "^1.0.0" +react-transition-group@^4.3.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" @@ -13330,6 +13461,11 @@ toposort@^1.0.0: resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + totalist@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" @@ -14573,6 +14709,19 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yup@^0.32.8: + version "0.32.8" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.8.tgz#16e4a949a86a69505abf99fd0941305ac9adfc39" + integrity sha512-SZulv5FIZ9d5H99EN5tRCRPXL0eyoYxWIP1AacCrjC9d4DfP13J1dROdKGfpfRHT3eQB6/ikBl5jG21smAfCkA== + dependencies: + "@babel/runtime" "^7.10.5" + "@types/lodash" "^4.14.165" + lodash "^4.17.20" + lodash-es "^4.17.11" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"