merchant-backoffice

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

commit e1bc057a4adb73aaebf605f3c8b2de33b5808845
parent 54ef0ca396ef6b2c38089c99cd4587eec3ce1fc2
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon,  8 Feb 2021 17:31:36 -0300

working on instances listing

Signed-off-by: Sebastian <sebasjm@gmail.com>

Diffstat:
Asrc/components/auth/LoginPage.tsx | 16++++++++++++++++
Asrc/components/hooks/backend.ts | 47+++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/navbar/index.tsx | 2+-
Msrc/declaration.d.ts | 422++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/routes/index.tsx | 11+++--------
Asrc/routes/instanceDetail/index.tsx | 7+++++++
Asrc/routes/instances/Table.tsx | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/routes/instances/View.stories.tsx | 35+++++++++++++++++++++++++++++++++++
Asrc/routes/instances/View.tsx | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/routes/instances/index.tsx | 127++++---------------------------------------------------------------------------
Dsrc/routes/instances/table.tsx | 89-------------------------------------------------------------------------------
11 files changed, 690 insertions(+), 230 deletions(-)

diff --git a/src/components/auth/LoginPage.tsx b/src/components/auth/LoginPage.tsx @@ -0,0 +1,15 @@ +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; + +interface Props { + onLogIn: (token: string) => void; +} + +export default function LoginPage({ onLogIn }: Props): VNode { + const [token, update] = useState('') + + return <div> + <input value={token} onInput={e => update(e?.currentTarget.value)} /> + <button onClick={(): void => onLogIn(token)}>set</button> + </div> +} +\ No newline at end of file diff --git a/src/components/hooks/backend.ts b/src/components/hooks/backend.ts @@ -0,0 +1,47 @@ +import useSWR, { mutate } from 'swr'; +import axios from 'axios' +import { MerchantBackend } from '../../declaration'; + +type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError<T>; + +interface HttpResponseOk<T> { + data: T; +} +interface HttpResponseError<T> { + data: undefined; + needsAuth: boolean; + error: Error; +} + +class AuthError extends Error { + public readonly isAuth = true +} + +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> { + 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 + if (res.status == 401) throw new AuthError() + + const error = new Error('An error occurred while fetching the data.') + const info = res.data + const status = res.status + throw { info, status, ...error } +} + +export function updateToken(token: string): void { + localStorage.setItem(TOKEN_KEY, token) + mutate('instances') +} + +export function useBackendInstances(): HttpResponse<MerchantBackend.Instances.InstancesResponse> { + const { data, error } = useSWR<MerchantBackend.Instances.InstancesResponse>('instances', fetcher) + + return { data, needsAuth: error instanceof AuthError, error } +} diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx @@ -17,7 +17,7 @@ export default function NavigationBar(): VNode { </a> </div> <div class="navbar-menu navbar-end"> - <button class="button is-primary">Log out</button> + <button class="button is-primary">Log out</button> </div> </nav> ); diff --git a/src/declaration.d.ts b/src/declaration.d.ts @@ -1,4 +1,3 @@ -import StringRenderer from "enzyme-adapter-preact-pure/build/src/StringRenderer"; declare module "*.css" { const mapping: Record<string, string>; @@ -9,14 +8,416 @@ declare module "*.scss" { export default mapping; } -namespace MerchantBackend { - type PrivateInstances = { - instances: Instance[]; - }; - type Instance = { +type EddsaPublicKey = string; +// type RelativeTime = Duration; +interface Timestamp { + // Milliseconds since epoch, or the special + // value "forever" to represent an event that will + // never happen. + t_ms: number | "never"; +} +interface Duration { + // Duration in milliseconds or "forever" + // to represent an infinite duration. + d_ms: number | "forever"; +} + +type Amount = string; +type UUID = string; +type Integer = number; + +export namespace MerchantBackend { + // Delivery location, loosely modeled as a subset of + // ISO20022's PostalAddress25. + interface Tax { + // the name of the tax name: string; - id: string; - merchant_pub: string; - payment_targets: string[]; + + // amount paid in tax + tax: Amount; + } + + interface Location { + // Nation with its own government. + country?: string; + + // Identifies a subdivision of a country such as state, region, county. + country_subdivision?: string; + + // Identifies a subdivision within a country sub-division. + district?: string; + + // Name of a built-up area, with defined boundaries, and a local government. + town?: string; + + // Specific location name within the town. + town_location?: string; + + // Identifier consisting of a group of letters and/or numbers that + // is added to a postal address to assist the sorting of mail. + post_code?: string; + + // Name of a street or thoroughfare. + street?: string; + + // Name of the building or house. + building_name?: string; + + // Number that identifies the position of a building on a street. + building_number?: string; + + // Free-form address lines, should not exceed 7 elements. + address_lines?: string[]; + } + namespace Instances { + + //POST /private/instances + interface InstanceConfigurationMessage { + // The URI where the wallet will send coins. A merchant may have + // multiple accounts, thus this is an array. Note that by + // removing URIs from this list the respective account is set to + // inactive and thus unavailable for new contracts, but preserved + // in the database as existing offers and contracts may still refer + // to it. + payto_uris: string[]; + + // Name of the merchant instance to create (will become $INSTANCE). + id: string; + + // Merchant name corresponding to this instance. + name: string; + + // "Authentication" header required to authorize management access the instance. + // Optional, if not given authentication will be disabled for + // this instance (hopefully authentication checks are still + // done by some reverse proxy). + auth_token?: string; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Maximum wire fee this instance is willing to pay. + // Can be overridden by the frontend on a per-order basis. + default_max_wire_fee: Amount; + + // Default factor for wire fee amortization calculations. + // Can be overridden by the frontend on a per-order basis. + default_wire_fee_amortization: Integer; + + // Maximum deposit fee (sum over all coins) this instance is willing to pay. + // Can be overridden by the frontend on a per-order basis. + default_max_deposit_fee: Amount; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; + + } + + // PATCH /private/instances/$INSTANCE + interface InstanceReconfigurationMessage { + // The URI where the wallet will send coins. A merchant may have + // multiple accounts, thus this is an array. Note that by + // removing URIs from this list + payto_uris: string[]; + + // Merchant name corresponding to this instance. + name: string; + + // "Authentication" header required to authorize management access the instance. + // Optional, if not given authentication will be disabled for + // this instance (hopefully authentication checks are still + // done by some reverse proxy). + auth_token?: string; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Maximum wire fee this instance is willing to pay. + // Can be overridden by the frontend on a per-order basis. + default_max_wire_fee: Amount; + + // Default factor for wire fee amortization calculations. + // Can be overridden by the frontend on a per-order basis. + default_wire_fee_amortization: Integer; + + // Maximum deposit fee (sum over all coins) this instance is willing to pay. + // Can be overridden by the frontend on a per-order basis. + default_max_deposit_fee: Amount; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; + + } + + // GET /private/instances + interface InstancesResponse { + // List of instances that are present in the backend (see Instance) + instances: Instance[]; + } + + interface Instance { + // Merchant name corresponding to this instance. + name: string; + + // Merchant instance this response is about ($INSTANCE) + id: string; + + // Public key of the merchant/instance, in Crockford Base32 encoding. + merchant_pub: EddsaPublicKey; + + // List of the payment targets supported by this instance. Clients can + // specify the desired payment target in /order requests. Note that + // front-ends do not have to support wallets selecting payment targets. + payment_targets: string[]; + + } + + //GET /private/instances/$INSTANCE + interface QueryInstancesResponse { + // The URI where the wallet will send coins. A merchant may have + // multiple accounts, thus this is an array. + accounts: MerchantAccount[]; + + // Merchant name corresponding to this instance. + name: string; + + // Public key of the merchant/instance, in Crockford Base32 encoding. + merchant_pub: EddsaPublicKey; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Maximum wire fee this instance is willing to pay. + // Can be overridden by the frontend on a per-order basis. + default_max_wire_fee: Amount; + + // Default factor for wire fee amortization calculations. + // Can be overridden by the frontend on a per-order basis. + default_wire_fee_amortization: Integer; + + // Maximum deposit fee (sum over all coins) this instance is willing to pay. + // Can be overridden by the frontend on a per-order basis. + default_max_deposit_fee: Amount; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_deadline: RelativeTime; + + } + + interface MerchantAccount { + + // payto:// URI of the account. + payto_uri: string; + + // Hash over the wire details (including over the salt) + h_wire: HashCode; + + // salt used to compute h_wire + salt: HashCode; + + // true if this account is active, + // false if it is historic. + active: boolean; + } + + // DELETE /private/instances/$INSTANCE + + + } + + namespace Inventory { + // POST /private/products + interface ProductAddDetail { + + // product ID to use. + product_id: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions + // eslint-disable-next-line @typescript-eslint/camelcase + description_i18n: { [lang_tag: string]: string }; + + // unit in which the product is measured (liters, kilograms, packages, etc.) + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: Amount; + + // An optional base64-encoded product image + image: ImageDataUrl; + + // a list of taxes paid by the merchant for one unit of this product + taxes: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Identifies where the product is in stock. + address: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + } + // PATCH /private/products/$PRODUCT_ID + interface ProductPatchDetail { + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions + // eslint-disable-next-line @typescript-eslint/camelcase + description_i18n: { [lang_tag: string]: string }; + + // unit in which the product is measured (liters, kilograms, packages, etc.) + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: Amount; + + // An optional base64-encoded product image + image: ImageDataUrl; + + // a list of taxes paid by the merchant for one unit of this product + taxes: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Number of units of the product that were lost (spoiled, stolen, etc.) + total_lost: Integer; + + // Identifies where the product is in stock. + address: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + } + + // GET /private/products + interface InventorySummaryResponse { + // List of products that are present in the inventory + products: InventoryEntry[]; + } + interface InventoryEntry { + // Product identifier, as found in the product. + product_id: string; + + } + + // GET /private/products/$PRODUCT_ID + interface ProductDetail { + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions + // eslint-disable-next-line @typescript-eslint/camelcase + description_i18n: { [lang_tag: string]: string }; + + // unit in which the product is measured (liters, kilograms, packages, etc.) + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: Amount; + + // An optional base64-encoded product image + image: ImageDataUrl; + + // a list of taxes paid by the merchant for one unit of this product + taxes: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Number of units of the product that have already been sold. + total_sold: Integer; + + // Number of units of the product that were lost (spoiled, stolen, etc.) + total_lost: Integer; + + // Identifies where the product is in stock. + address: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + } + + // POST /private/products/$PRODUCT_ID/lock + interface LockRequest { + + // UUID that identifies the frontend performing the lock + // It is suggested that clients use a timeflake for this, + // see https://github.com/anthonynsimon/timeflake + lock_uuid: UUID; + + // How long does the frontend intend to hold the lock + duration: RelativeTime; + + // How many units should be locked? + quantity: Integer; + + } + + // DELETE /private/products/$PRODUCT_ID + } -} -\ No newline at end of file +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx @@ -3,13 +3,11 @@ import { route, Route, Router } from 'preact-router'; import NotFoundPage from './notfound'; import Instances from './instances'; -import Dash from './dashboard'; -import BulmaForms from './forms'; -import BulmaProfile from './profile'; import Footer from '../components/footer'; import Sidebar from '../components/sidebar'; import NavigationBar from '../components/navbar'; import { useEffect } from 'preact/hooks'; +import InstanceDetail from './instanceDetail'; function Redirector({ to }: { path: string; to: string }): null { useEffect(() => { @@ -24,11 +22,8 @@ export default function PageRouter(): VNode { <NavigationBar /> <Sidebar /> <Router> - <Redirector path="/" to="/instances" /> - <Route path="/instances" component={Instances} /> - <Route path="/dashboard" component={Dash} /> - <Route path="/forms" component={BulmaForms} /> - <Route path="/profile" component={BulmaProfile} /> + <Route path="/" component={Instances} /> + <Route path="/i/:instance" component={InstanceDetail} /> <NotFoundPage default /> </Router> <Footer /> diff --git a/src/routes/instanceDetail/index.tsx b/src/routes/instanceDetail/index.tsx @@ -0,0 +1,6 @@ +import { h, VNode } from 'preact'; + + +export default function InstanceDetail({ instance }: any): VNode { + return <div>hola {instance}</div> +} +\ No newline at end of file diff --git a/src/routes/instances/Table.tsx b/src/routes/instances/Table.tsx @@ -0,0 +1,88 @@ +import { h, VNode } from "preact"; +import { MerchantBackend } from "../../declaration"; + +interface Props { + instances: MerchantBackend.Instances.Instance[]; +} + +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> + })} + + </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> + </div> + </div> + </div> + </div> + +} +\ No newline at end of file diff --git a/src/routes/instances/View.stories.tsx b/src/routes/instances/View.stories.tsx @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { h } from 'preact'; +import View from './View' + + +export default { + title: 'Instances/View', + component: View, + argTypes: { + onLogin: { action: 'onLogin' }, + onLogout: { action: 'onLogout' }, + onCreateAccount: { action: 'onCreateAccount' }, + }, +}; + +export const Empty = () => <View instances={[]} /> + +export const WithDefaultInstance = () => <View 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'] +}]} /> diff --git a/src/routes/instances/View.tsx b/src/routes/instances/View.tsx @@ -0,0 +1,74 @@ +import { h, VNode } from "preact"; +import { MerchantBackend } from "../../declaration"; +import Table from './Table'; + +interface Props { + instances: MerchantBackend.Instances.Instance[]; +} + +export default function View({ instances }: Props): VNode { + return <div id="app"> + <section class="section is-title-bar"> + <div class="level"> + <div class="level-left"> + <div class="level-item"> + <ul> + <li>Merchant</li> + <li>Instances</li> + </ul> + </div> + </div> + </div> + </section> + <section class="hero is-hero-bar"> + <div class="hero-body"> + <div class="level"> + <div class="level-left"> + <div class="level-item"> + <h1 class="title">List of configured instances</h1> + </div> + </div> + <div class="level-right" style="display: none;"> + <div class="level-item" /> + </div> + </div> + </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> + + </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> +} +\ No newline at end of file diff --git a/src/routes/instances/index.tsx b/src/routes/instances/index.tsx @@ -1,130 +1,15 @@ import { h, VNode } from 'preact'; -import { useState } from 'preact/hooks'; -import useSWR, { mutate } from 'swr'; -import Table from './table'; -import { MerchantBackend } from '../../declaration'; -import axios from 'axios' +import View from './View'; +import LoginPage from '../../components/auth/LoginPage'; +import { updateToken, useBackendInstances } from '../../components/hooks/backend'; -class AuthError extends Error { - public readonly isAuth = true -} -const BACKEND = 'http://localhost:9966' - -async function fetcher(url: string) { - const token = localStorage.getItem('backend-token') - 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 - if (res.status == 401) throw new AuthError() - - const error = new Error('An error occurred while fetching the data.') - const info = res.data - const status = res.status - throw { info, status, ...error } -} - -function updateToken(token: string): void { - localStorage.setItem('backend-token', token) - mutate('instances') -} - -type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError<T>; - -interface HttpResponseOk<T> { - data: T; -} -interface HttpResponseError<T> { - data: undefined; - needsAuth: boolean; - error: Error; -} - - -function useBackendInstances(): HttpResponse<MerchantBackend.PrivateInstances> { - const { data, error } = useSWR<MerchantBackend.PrivateInstances>('instances', fetcher) - return { data, needsAuth: error instanceof AuthError, error } -} - -function TokenModal(): VNode { - const [token, update] = useState('') - - return <div> - <input value={token} onInput={e => update(e?.currentTarget.value)} /> - <button onClick={(): void => updateToken(token)}>set</button> - </div> -} - -export default function BulmaTable({}): VNode { +export default function Instances(): VNode { const resp = useBackendInstances() if (!resp.data) { - return <TokenModal /> + return <LoginPage onLogIn={updateToken} /> } - return <div id="app"> - <section class="section is-title-bar"> - <div class="level"> - <div class="level-left"> - <div class="level-item"> - <ul> - <li>Merchant</li> - <li>Instances</li> - </ul> - </div> - </div> - </div> - </section> - <section class="hero is-hero-bar"> - <div class="hero-body"> - <div class="level"> - <div class="level-left"> - <div class="level-item"><h1 class="title"> - List of configured instances - </h1></div> - </div> - <div class="level-right" style="display: none;"> - <div class="level-item" /> - </div> - </div> - </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={resp.data.instances} /> - </div> - </div> - - </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>; + return <View instances={resp.data.instances} />; } \ No newline at end of file diff --git a/src/routes/instances/table.tsx b/src/routes/instances/table.tsx @@ -1,88 +0,0 @@ -import { h, VNode } from "preact"; -import { MerchantBackend } from "../../declaration"; - -interface Props { - instances: MerchantBackend.Instance[]; -} - -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>Name</th> - <th>Company</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> - })} - - </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> - </div> - </div> - </div> - </div> - -} -\ No newline at end of file