gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-backoffice] branch master updated: order detail


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated: order detail
Date: Wed, 31 Mar 2021 15:16:34 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository merchant-backoffice.

The following commit(s) were added to refs/heads/master by this push:
     new e9a4228  order detail
e9a4228 is described below

commit e9a4228ef2d7210e67aded9559eeed79dfa4624f
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Mar 31 10:16:17 2021 -0300

    order detail
---
 CHANGELOG.md                                       |   8 +-
 packages/frontend/package.json                     |   1 +
 packages/frontend/src/InstanceRoutes.tsx           |  33 --
 packages/frontend/src/components/form/Input.tsx    |   2 +-
 .../src/components/form/InputWithAddon.tsx         |   2 +-
 packages/frontend/src/components/menu/SideBar.tsx  |   4 +-
 packages/frontend/src/messages/en.po               |  47 ++-
 .../paths/instance/orders/details/DetailPage.tsx   | 368 +++++++++++++++++++--
 .../src/paths/instance/orders/details/Timeline.tsx |  35 ++
 .../src/paths/instance/orders/details/index.tsx    |  18 +-
 .../src/paths/instance/orders/list/Table.tsx       |   7 +-
 .../src/paths/instance/orders/list/index.tsx       |   7 +-
 packages/frontend/src/scss/main.scss               |  25 +-
 packages/frontend/src/utils/functions.ts           |   5 +
 pnpm-lock.yaml                                     |   6 +
 15 files changed, 452 insertions(+), 116 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 646d44e..082c849 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,14 +27,8 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - product detail: we could have some button that brings us to the detailed 
screen for the product
  - order id field to go
 
-created
-wired (if wired === true)
-refund
+frontend, too many redirects
 
-error details
-
-fatal error: exchange error
-warning: exchange repotred problem
  - navigation to another instance should not do full refresh
  - cleanup instance and token management, because code is a mess and can be 
refactored 
 ## [Unreleased]
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index e5ea502..34eff24 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -77,6 +77,7 @@
     "bulma-radio": "^1.1.1",
     "bulma-responsive-tables": "^1.2.3",
     "bulma-switch-control": "^1.1.1",
+    "bulma-timeline": "^3.0.4",
     "bulma-upload-control": "^1.2.0",
     "dotenv": "^8.2.0",
     "enzyme": "^3.11.0",
diff --git a/packages/frontend/src/InstanceRoutes.tsx 
b/packages/frontend/src/InstanceRoutes.tsx
index ba0c8b2..2114c6c 100644
--- a/packages/frontend/src/InstanceRoutes.tsx
+++ b/packages/frontend/src/InstanceRoutes.tsx
@@ -204,37 +204,6 @@ export function InstanceRoutes({ id, admin }: Props): 
VNode {
       }
 
       <Route path="/" component={Redirect} to={InstancePaths.order_list} />
-      {/* 
-        component={DetailPage}
-
-        onUnauthorized={() => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Access denied`, 
description: i18n`Check your token is valid`, type: 'ERROR', }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-
-        onNotFound={() => {
-          if (admin) {
-            return <Fragment>
-              <NotificationCard notification={{
-                message: 'No default instance',
-                description: 'in order to use merchant backoffice, you should 
create the default instance',
-                type: 'INFO'
-              }} />
-              <InstanceCreatePage onError={() => null} onConfirm={() => null} 
/>
-            </Fragment>
-          }
-          return <NotFoundPage />
-        }} 
-
-        onLoadError={(error: SwrError) => {
-          return <Fragment>
-            <NotificationCard notification={{ message: i18n`Problem reaching 
the server`, description: i18n`Got message: ${error.message} from: 
${error.backend} (hasToken: ${error.hasToken})`, type: 'ERROR' }} />
-            <LoginPage onConfirm={updateLoginStatus} />
-          </Fragment>
-        }}
-      /> */}
 
       <Route path={InstancePaths.update}
         component={InstanceUpdatePage}
@@ -286,12 +255,10 @@ export function InstanceRoutes({ id, admin }: Props): 
VNode {
         }}
 
         onConfirm={() => {
-          // pushNotification({ message: i18n`create_success`, type: 'SUCCESS' 
});
           route(`/`);
         }}
 
         onUpdateError={(e: Error) => {
-          // pushNotification({ message: i18n`update_error`, type: 'ERROR' });
         }}
       />
 
diff --git a/packages/frontend/src/components/form/Input.tsx 
b/packages/frontend/src/components/form/Input.tsx
index 565e17b..c3ffa25 100644
--- a/packages/frontend/src/components/form/Input.tsx
+++ b/packages/frontend/src/components/form/Input.tsx
@@ -59,7 +59,7 @@ export function Input<T>({ name, readonly, expand, inputType, 
fromStr = defaultF
           <TextInput error={error}
             inputType={inputType}
             placeholder={placeholder} readonly={readonly}
-            name={String(name)} value={toStr(value)} disabled={readonly}
+            name={String(name)} value={toStr(value)} 
             onChange={(e:h.JSX.TargetedEvent<HTMLInputElement>): void => 
onChange(fromStr(e.currentTarget.value))} />
           <Message id={`fields.instance.${name}.help`}> </Message>
         </p>
diff --git a/packages/frontend/src/components/form/InputWithAddon.tsx 
b/packages/frontend/src/components/form/InputWithAddon.tsx
index f583f08..6b194ab 100644
--- a/packages/frontend/src/components/form/InputWithAddon.tsx
+++ b/packages/frontend/src/components/form/InputWithAddon.tsx
@@ -59,7 +59,7 @@ export function InputWithAddon<T>({ name, readonly, 
addonBefore, expand, inputTy
           </div>}
           <p class={ expand ? "control is-expanded" : "control" }>
             <input class={error ? "input is-danger" : "input"} type={inputType}
-              placeholder={placeholder} readonly={readonly} disabled={readonly}
+              placeholder={placeholder} readonly={readonly} 
               name={String(name)} value={toStr(value)}
               onChange={(e): void => onChange(fromStr(e.currentTarget.value))} 
/>
             <Message id={`fields.instance.${name}.help`}> </Message>
diff --git a/packages/frontend/src/components/menu/SideBar.tsx 
b/packages/frontend/src/components/menu/SideBar.tsx
index 92e0f46..a6fe766 100644
--- a/packages/frontend/src/components/menu/SideBar.tsx
+++ b/packages/frontend/src/components/menu/SideBar.tsx
@@ -74,12 +74,12 @@ export function Sidebar({ mobile, instance, onLogout, admin 
}: Props): VNode {
               <span class="menu-item-label">Transfers</span>
             </a>
           </li>
-          <li>
+          {/* <li>
             <a href="/tips" class="has-icon">
               <span class="icon"><i class="mdi mdi-cash" /></span>
               <span class="menu-item-label">Tips</span>
             </a>
-          </li>
+          </li> */}
         </ul>
         <p class="menu-label">Connection</p>
         <ul class="menu-list">
diff --git a/packages/frontend/src/messages/en.po 
b/packages/frontend/src/messages/en.po
index 4abba9a..c081637 100644
--- a/packages/frontend/src/messages/en.po
+++ b/packages/frontend/src/messages/en.po
@@ -311,50 +311,41 @@ msgstr "Description"
 msgid "fields.instance.description.placeholder"
 msgstr "add more information about the refund"
 
-#  msgid "fields.instance.reason.tooltip"
-#  msgstr ""
 msgid "fields.instance.order_status.placeholder"
 msgstr ""
 
-#  msgid "fields.instance.order_status.tooltip"
-#  msgstr ""
-
 msgid "fields.instance.order_status.label"
 msgstr "Order status"
 
-
-#  msgid "fields.instance.order_status_url.placeholder"
-#  msgstr ""
-
-#  msgid "fields.instance.order_status_url.tooltip"
-#  msgstr ""
-
 msgid "fields.instance.order_status_url.label"
 msgstr "Order status URL"
 
-#  msgid "fields.instance.taler_pay_uri.placeholder"
-#  msgstr ""
-
-#  msgid "fields.instance.taler_pay_uri.tooltip"
-#  msgstr ""
-
 msgid "fields.instance.taler_pay_uri.label"
 msgstr "Taler Pay URI"
 
-#  msgid "fields.instance.amount.placeholder"
-#  msgstr ""
-
-#  msgid "fields.instance.amount.tooltip"
-#  msgstr ""
 
 msgid "fields.instance.amount.label"
 msgstr "Amount"
 
-#  msgid "fields.instance.summary.placeholder"
-#  msgstr ""
+msgid "fields.instance.summary.label"
+msgstr "Summary"
 
-#  msgid "fields.instance.summary.tooltip"
-#  msgstr ""
 
-msgid "fields.instance.summary.label"
+msgid "fields.instance.contract_terms.amount.label"
+msgstr "Amount"
+
+msgid "fields.instance.contract_terms.summary.label"
 msgstr "Summary"
+
+msgid "fields.instance.refund_amount.label"
+msgstr "Refund Amount"
+
+msgid "fields.instance.deposit_total.label"
+msgstr "Deposit Total"
+
+msgid "fields.instance.contract_terms.max_fee.label"
+msgstr "Max Fee"
+
+msgid "fields.instance.contract_terms.max_wire_fee.label"
+msgstr "Max Wire Fee"
+
diff --git a/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx 
b/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
index 4c81302..0359509 100644
--- a/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
+++ b/packages/frontend/src/paths/instance/orders/details/DetailPage.tsx
@@ -25,12 +25,19 @@ import { MerchantBackend } from "../../../../declaration";
 import { Input } from "../../../../components/form/Input";
 import { FormProvider } from "../../../../components/form/Field";
 import { NotificationCard } from "../../../../components/menu";
+import { useConfigContext } from "../../../../context/backend";
+import { InputCurrency } from "../../../../components/form/InputCurrency";
+import { copyToClipboard } from "../../../../utils/functions";
+import { format } from "date-fns";
+import { Event, Timeline } from "./Timeline";
+import { RefundModal } from "../list/Table";
 
 type Entity = MerchantBackend.Orders.MerchantOrderStatusResponse;
 interface Props {
   onBack: () => void;
   selected: Entity;
-
+  id: string;
+  onRefund: (id:string, value: MerchantBackend.Orders.RefundRequest) => void;
 }
 
 interface KeyValue {
@@ -41,41 +48,316 @@ type Paid = MerchantBackend.Orders.CheckPaymentPaidResponse
 type Unpaid = MerchantBackend.Orders.CheckPaymentUnpaidResponse
 type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse
 
-export function DetailPage({ selected }: Props): VNode {
-  const [value, valueHandler] = useState<Partial<Entity>>(selected)
+
+function ClaimedPage({ id, order }: { id: string; order: 
MerchantBackend.Orders.CheckPaymentClaimedResponse }) {
+  const events: Event[] = []
+  events.push({
+    when: new Date(),
+    description: 'now',
+    type: 'now'
+  })
+  events.push({
+    when: new Date(order.contract_terms.timestamp.t_ms),
+    description: 'order created',
+    type: 'start'
+  })
+  events.push({
+    when: new Date(order.contract_terms.pay_deadline.t_ms),
+    description: 'pay deadline',
+    type: 'deadline'
+  })
+  events.push({
+    when: new Date(order.contract_terms.refund_deadline.t_ms),
+    description: 'refund deadline',
+    type: 'deadline'
+  })
+  events.push({
+    when: new Date(order.contract_terms.wire_transfer_deadline.t_ms),
+    description: 'wire deadline',
+    type: 'deadline'
+  })
+  if (order.contract_terms.delivery_date) events.push({
+    when: new Date(order.contract_terms.delivery_date?.t_ms),
+    description: 'delivery',
+    type: 'delivery'
+  })
+
+  events.sort((a, b) => a.when.getTime() - b.when.getTime())
+  const [value, valueHandler] = useState<Partial<Claimed>>(order)
   const [errors, setErrors] = useState<KeyValue>({})
+  const config = useConfigContext()
+
+  return <div>
+    <section class="section">
+      <div class="columns">
+        <div class="column" />
+        <div class="column is-10">
 
+          <section class="hero is-hero-bar">
+            <div class="hero-body">
+
+              <div class="level">
+                <div class="level-left">
+                  <div class="level-item">
+                    Order #{id}
+                    <div class="tag is-info ml-4">claimed</div>
+                  </div>
+                </div>
+              </div>
+              <div class="level">
+                <div class="level-left">
+                  <div class="level-item">
+                    <h1 class="title">
+                      {order.contract_terms.amount}
+                    </h1>
+                  </div>
+                </div>
+                <div class="level-right">
+                  <div class="level-item">
+                    <h1 class="title">
+                      <button class="button is-info" onClick={() => {
+                        if (order.contract_terms.fulfillment_url) 
copyToClipboard(order.contract_terms.fulfillment_url)
+                      }}>copy url</button>
+                    </h1>
+                  </div>
+                </div>
+              </div>
+
+              <div class="level">
+                <div class="level-left" style={{ maxWidth: '100%' }}>
+                  <div class="level-item" style={{ maxWidth: '100%' }}>
+                    <div class="content" style={{
+                      whiteSpace: 'nowrap',
+                      overflow: 'hidden',
+                      textOverflow: 'ellipsis',
+                      // maxWidth: '100%',
+                    }}>
+                      <p>pay at: <a 
href={order.contract_terms.fulfillment_url} rel="nofollow" 
target="new">{order.contract_terms.fulfillment_url}</a></p>
+                      <p>{format(new 
Date(order.contract_terms.timestamp.t_ms), 'yyyy/MM/dd HH:mm:ss')}</p>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </section>
+
+          <section class="section">
+            <div class="columns">
+              <div class="column is-4">
+                <div class="title">Timeline</div>
+                <Timeline events={events} />
+              </div>
+              <div class="column is-8" >
+                <div class="title">Payment details</div>
+                <FormProvider<Claimed> errors={errors} object={value} 
valueHandler={valueHandler} >
+                  <Input name="contract_terms.summary" readonly 
inputType="multiline" />
+                  <InputCurrency name="contract_terms.amount" readonly 
currency={config.currency} />
+                  <Input<Claimed> name="order_status" readonly />
+                </FormProvider>
+              </div>
+            </div>
+          </section>
+
+          <section class="section">
+            <div class="columns">
+              <div class="column is-2" />
+              <div class="column is-8" >
+                <div class="title">Payment details</div>
+                <FormProvider<Claimed> errors={errors} object={value} 
valueHandler={valueHandler} >
+                  <Input name="contract_terms.summary" readonly 
inputType="multiline" />
+                  <InputCurrency name="contract_terms.amount" readonly 
currency={config.currency} />
+                  <Input<Claimed> name="order_status" readonly />
+                </FormProvider>
+              </div>
+              <div class="column" />
+            </div>
+          </section>
+
+        </div>
+        <div class="column" />
+      </div>
+    </section>
+  </div>
+}
+function PaidPage({ id, order, onRefund }: { id: string; order: 
MerchantBackend.Orders.CheckPaymentPaidResponse, onRefund: (id:string) => void 
}) {
+  const events: Event[] = []
+  events.push({
+    when: new Date(),
+    description: 'now',
+    type: 'now'
+  })
+  events.push({
+    when: new Date(order.contract_terms.timestamp.t_ms),
+    description: 'order created',
+    type: 'start'
+  })
+  events.push({
+    when: new Date(order.contract_terms.pay_deadline.t_ms),
+    description: 'pay deadline',
+    type: 'deadline'
+  })
+  events.push({
+    when: new Date(order.contract_terms.refund_deadline.t_ms),
+    description: 'refund deadline',
+    type: 'deadline'
+  })
+  events.push({
+    when: new Date(order.contract_terms.wire_transfer_deadline.t_ms),
+    description: 'wire deadline',
+    type: 'deadline'
+  })
+  if (order.contract_terms.delivery_date) events.push({
+    when: new Date(order.contract_terms.delivery_date?.t_ms),
+    description: 'delivery',
+    type: 'delivery'
+  })
+  order.refund_details.forEach(e => {
+    events.push({
+      when: new Date(e.timestamp.t_ms),
+      description: `refund: ${e.amount}`,
+      type: 'refund',
+    })
+  })
+  order.wire_details.forEach(e => {
+    events.push({
+      when: new Date(e.execution_time.t_ms),
+      description: `wired`,
+      type: 'wired',
+    })
+  })
+  if (order.contract_terms.wire_transfer_deadline.t_ms !== 'never' &&
+  order.contract_terms.wire_transfer_deadline.t_ms < new Date().getTime() ) 
events.push({
+    when: new Date(order.contract_terms.wire_transfer_deadline.t_ms - 1000*10),
+    description: `wired (faked)`,
+    type: 'wired',
+  })
+
+  events.sort((a, b) => a.when.getTime() - b.when.getTime())
+  const [value, valueHandler] = useState<Partial<Paid>>({...order, fee: 
'COL:0.1'} as any)
+  const [errors, setErrors] = useState<KeyValue>({})
+  const config = useConfigContext()
+
+  const refundable = !order.refunded &&
+    new Date().getTime() <order.contract_terms.refund_deadline.t_ms
 
   return <div>
-    <NotificationCard notification={{
-      message: 'DEMO WARNING',
-      type:'WARN',
-      description: <Fragment>
-      <p>UNDER CONSTRUCTION: for now we are showing some field of the order 
depending on the state</p>
-      <p><b>unpaid:</b> status_url and pay_uri</p>
-      <p><b>claimed:</b> contractTerms.amount</p>
-      <p><b>paid:</b> deposit_total</p>
-      </Fragment>
-    }} />
+    <section class="section">
+      <div class="columns">
+        <div class="column" />
+        <div class="column is-10">
+
+          <section class="hero is-hero-bar">
+            <div class="hero-body">
+
+              <div class="level">
+                <div class="level-left">
+                  <div class="level-item">
+                    Order #{id}
+                    <div class="tag is-success ml-4">paid</div>
+                    {order.wired ?
+                      <div class="tag is-success ml-4">wired</div> : null
+                    }
+                    {order.refunded ?
+                      <div class="tag is-danger ml-4">refunded</div> : null
+                    }
+                  </div>
+                </div>
+              </div>
+              <div class="level">
+                <div class="level-left">
+                  <div class="level-item">
+                    <h1 class="title">
+                      {order.contract_terms.amount}
+                    </h1>
+                  </div>
+                </div>
+                <div class="level-right">
+                  <div class="level-item">
+                    <h1 class="title">
+                      <div class="buttons">
+                        { refundable && <button class="button is-danger" 
onClick={() => onRefund(id) }>refund</button> }
+                        <button class="button is-info" onClick={() => {
+                          if (order.contract_terms.fulfillment_url) 
copyToClipboard(order.contract_terms.fulfillment_url)
+                        }}>copy url</button>
+                      </div>
+                    </h1>
+                  </div>
+                </div>
+              </div>
+
+              <div class="level">
+                <div class="level-left" style={{ maxWidth: '100%' }}>
+                  <div class="level-item" style={{ maxWidth: '100%' }}>
+                    <div class="content" style={{
+                      whiteSpace: 'nowrap',
+                      overflow: 'hidden',
+                      textOverflow: 'ellipsis',
+                      // maxWidth: '100%',
+                    }}>
+                      <p><a href={order.contract_terms.fulfillment_url} 
rel="nofollow" target="new">{order.contract_terms.fulfillment_url}</a></p>
+                      <p>{format(new 
Date(order.contract_terms.timestamp.t_ms), 'yyyy/MM/dd HH:mm:ss')}</p>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </section>
+
+          <section class="section">
+            <div class="columns">
+              <div class="column is-4">
+                <div class="title">Timeline</div>
+                <Timeline events={events} />
+              </div>
+              <div class="column is-8" >
+                <div class="title">Payment details</div>
+                <FormProvider<Paid> errors={errors} object={value} 
valueHandler={valueHandler} >
+                  <Input name="contract_terms.summary" readonly 
inputType="multiline" />
+                  <InputCurrency name="contract_terms.amount" readonly 
currency={config.currency} />
+                  <InputCurrency name="fee" readonly 
currency={config.currency} />
+                  <InputCurrency<Paid> name="refund_amount" readonly 
currency={config.currency} />
+                  <InputCurrency<Paid> name="deposit_total" readonly 
currency={config.currency} />
+                  <Input<Paid> name="order_status" readonly />
+                </FormProvider>
+              </div>
+            </div>
+          </section>
+
+        </div>
+        <div class="column" />
+      </div>
+    </section>
+  </div>
+}
+
+function UnpaidPage({ id, order }: { id: string; order: 
MerchantBackend.Orders.CheckPaymentUnpaidResponse }) {
+  const [value, valueHandler] = useState<Partial<Unpaid>>(order)
+  const [errors, setErrors] = useState<KeyValue>({})
+  return <div>
+
+    <section class="hero is-hero-bar">
+      <div class="hero-body">
+        <div class="level">
+          <div class="level-left">
+            <div class="level-item">
+              <h1 class="title">
+                Order #{id}
+              </h1>
+            </div>
+            <div class="tag is-dark">unpaid</div>
+          </div>
+        </div>
+      </div>
+    </section>
 
     <section class="section is-main-section">
       <div class="columns">
         <div class="column" />
         <div class="column is-6">
-          <FormProvider<Entity> errors={errors} object={value} 
valueHandler={valueHandler} >
-            {selected.order_status === 'unpaid' && <Fragment>
-              <Input<Unpaid> name="order_status" readonly />
-              <Input<Unpaid> name="order_status_url" readonly />
-              <Input<Unpaid> name="taler_pay_uri" readonly />
-            </Fragment>}
-            {selected.order_status === 'claimed' && <Fragment>
-              <Input<Claimed> name="order_status" readonly />
-              <Input name="contract_terms.amount" readonly />
-            </Fragment>}
-            {selected.order_status === 'paid' && <Fragment>
-              <Input<Paid> name="order_status" readonly />
-              <Input name="contract_terms.deposit_total" readonly />
-            </Fragment>}
+          <FormProvider<Unpaid> errors={errors} object={value} 
valueHandler={valueHandler} >
+            <Input<Unpaid> name="order_status" readonly />
+            <Input<Unpaid> name="order_status_url" readonly />
+            <Input<Unpaid> name="taler_pay_uri" readonly />
           </FormProvider>
         </div>
         <div class="column" />
@@ -83,5 +365,37 @@ export function DetailPage({ selected }: Props): VNode {
     </section>
 
   </div>
+}
+
+export function DetailPage({ id, selected, onRefund }: Props): VNode {
+  const [showRefund, setShowRefund] = useState<string | undefined>(undefined)
+
+  const DetailByStatus = function (){
+    switch (selected.order_status) {
+      case 'claimed': return <ClaimedPage id={id} order={selected} />
+      case 'paid': return <PaidPage id={id} order={selected} onRefund={(order) 
=> setShowRefund(id)} />
+      case 'unpaid': return <UnpaidPage id={id} order={selected} />
+      default: return <div>unknown order status</div>
+    }
+  }
+  
+  return <Fragment>
+    <NotificationCard notification={{
+      message: 'DEMO WARNING',
+      type: 'WARN',
+      description: <ul>
+        <li>wired event is faked</li>
+        <li>fee value is fake, is not being calculated</li>
+      </ul>
+    }} />
 
+    {DetailByStatus()}
+    {showRefund && <RefundModal
+      onCancel={() => setShowRefund(undefined)}
+      onConfirm={(value) => {
+        onRefund(showRefund, value)
+        setShowRefund(undefined)
+      }}
+    />}
+  </Fragment>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/orders/details/Timeline.tsx 
b/packages/frontend/src/paths/instance/orders/details/Timeline.tsx
new file mode 100644
index 0000000..c198cba
--- /dev/null
+++ b/packages/frontend/src/paths/instance/orders/details/Timeline.tsx
@@ -0,0 +1,35 @@
+import { format } from "date-fns";
+import { h } from "preact";
+
+interface Props {
+  events: Event[]
+}
+
+export function Timeline({ events }: Props) {
+  return <div class="timeline">
+    {events.map(e => {
+      return <div class="timeline-item">
+        {(() => {
+          switch (e.type) {
+            case "deadline": return <div class="timeline-marker is-icon "><i 
class="mdi mdi-flag" /></div>
+            case "delivery": return <div class="timeline-marker is-icon "><i 
class="mdi mdi-delivery" /></div>
+            case "start": return <div class="timeline-marker is-icon 
is-success"><i class="mdi mdi-flag " /></div>
+            case "wired": return <div class="timeline-marker is-icon 
is-success"><i class="mdi mdi-cash" /></div>
+            case "refund": return <div class="timeline-marker is-icon 
is-danger"><i class="mdi mdi-cash" /></div>
+            case "now": return <div class="timeline-marker is-icon is-info"><i 
class="mdi mdi-clock" /></div>
+          }
+        })()}
+        <div class="timeline-content">
+          <p class="heading">{format(e.when, 'yyyy/MM/dd HH:mm:ss')}</p>
+          <p>{e.description}</p>
+        </div>
+      </div>
+    })}
+  </div >;
+
+}
+export interface Event {
+  when: Date;
+  description: string;
+  type: 'start' | 'refund' | 'wired' | 'deadline' | 'delivery' | 'now'
+}
diff --git a/packages/frontend/src/paths/instance/orders/details/index.tsx 
b/packages/frontend/src/paths/instance/orders/details/index.tsx
index eae4f15..209d1ce 100644
--- a/packages/frontend/src/paths/instance/orders/details/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/details/index.tsx
@@ -14,9 +14,11 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
 import { Loading } from "../../../../components/exception/loading";
 import { SwrError, useOrderDetails, useOrderMutateAPI } from 
"../../../../hooks/backend";
- import { DetailPage } from "./DetailPage";
+import { Notification } from "../../../../utils/types";
+import { DetailPage } from "./DetailPage";
 
 export interface Props {
   oid: string;
@@ -30,6 +32,7 @@ export interface Props {
 export default function Update({ oid, onBack, onLoadError, onNotFound, 
onUnauthorized }: Props): VNode {
   const { refundOrder } = useOrderMutateAPI();
   const details = useOrderDetails(oid)
+  const [notif, setNotif] = useState<Notification | undefined>(undefined)
 
   if (details.unauthorized) return onUnauthorized()
   if (details.notfound) return onNotFound();
@@ -42,7 +45,18 @@ export default function Update({ oid, onBack, onLoadError, 
onNotFound, onUnautho
   return <Fragment>
     <DetailPage
       onBack={onBack}
+      id={oid}
+      onRefund={(id, value) => refundOrder(id, value)
+        .then(() => setNotif({
+          message: 'refund created successfully',
+          type: "SUCCESS"
+        })).catch((error) => setNotif({
+          message: 'could not create the refund',
+          type: "ERROR",
+          description: error.message
+        }))
+      }
       selected={details.data}
-      />
+    />
   </Fragment>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/paths/instance/orders/list/Table.tsx 
b/packages/frontend/src/paths/instance/orders/list/Table.tsx
index 82b3c83..a35281d 100644
--- a/packages/frontend/src/paths/instance/orders/list/Table.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/Table.tsx
@@ -127,12 +127,12 @@ function Table({ instances, onSelect, onRefund, 
onCopyURL, onLoadMoreAfter, onLo
             <td onClick={(): void => onSelect(i)} style={{ cursor: 'pointer' 
}} >{i.summary}</td>
             <td class="is-actions-cell right-sticky">
               <div class="buttons is-right">
-                {(i.refundable || pos === 0) &&
+                {(i.refundable) &&
                   <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onRefund(i)}>
                     Refund
                   </button>
                 }
-                {(!i.paid || pos === 0) &&
+                {(!i.paid) &&
                   <button class="button is-small is-info jb-modal" 
type="button" onClick={(): void => onCopyURL(i)}>
                     copy url
                   </button>
@@ -192,8 +192,5 @@ export function RefundModal({ onCancel, onConfirm }: 
RefundModalProps): VNode {
       <InputSelector name="mainReason" values={['duplicated', 'requested by 
the customer', 'other']} />
       {form.mainReason && <Input<State> name="description" />}
     </FormProvider>
-    <div class="block">
-      You are going to refund the order
-    </div>
   </ConfirmModal>
 }
diff --git a/packages/frontend/src/paths/instance/orders/list/index.tsx 
b/packages/frontend/src/paths/instance/orders/list/index.tsx
index 47f3673..f31100b 100644
--- a/packages/frontend/src/paths/instance/orders/list/index.tsx
+++ b/packages/frontend/src/paths/instance/orders/list/index.tsx
@@ -29,6 +29,7 @@ import { format } from 'date-fns';
 import { DatePicker } from '../../../../components/form/DatePicker';
 import { NotificationCard } from '../../../../components/menu';
 import { Notification } from '../../../../utils/types';
+import { copyToClipboard } from '../../../../utils/functions';
 
 interface Props {
   onUnauthorized: () => VNode;
@@ -47,7 +48,7 @@ export default function ({ onUnauthorized, onLoadError, 
onCreate, onSelect, onNo
 
   const result = useInstanceOrders(filter, setNewDate)
   const { createOrder, refundOrder, getPaymentURL } = useOrderMutateAPI()
-  const { currency } = useConfigContext()
+  // const { currency } = useConfigContext()
 
   let instances: (MerchantBackend.Orders.OrderHistoryEntry & { id: string })[];
 
@@ -119,9 +120,7 @@ export default function ({ onUnauthorized, onLoadError, 
onCreate, onSelect, onNo
     <CardTable instances={instances}
       onCreate={onCreate}
       onSelect={(order) => onSelect(order.id)}
-      onCopyURL={(id) => getPaymentURL(id).then(url => {
-        navigator.clipboard.writeText(url)
-      })}
+      onCopyURL={(id) => getPaymentURL(id).then(copyToClipboard)}
       onRefund={(id, value) => refundOrder(id, value)
         .then(() => setNotif({
           message: 'refund created successfully',
diff --git a/packages/frontend/src/scss/main.scss 
b/packages/frontend/src/scss/main.scss
index ac57135..369571e 100644
--- a/packages/frontend/src/scss/main.scss
+++ b/packages/frontend/src/scss/main.scss
@@ -5,22 +5,22 @@
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.
-
+ 
  GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
+ 
  You should have received a copy of the GNU General Public License along with
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
-
+ 
  /**
  *
  * @author Sebastian Javier Marchano (sebasjm)
  */
-
-/* Theme style (colors & sizes) */
-@import "theme-default";
+ 
+ /* Theme style (colors & sizes) */
+ @import "theme-default";
 
 /* Core Libs & Lib configs */
 @import "libs/all";
@@ -48,6 +48,19 @@
 @import "icons/materialdesignicons-4.9.95.min.css";
 
 @import 
"../../node_modules/@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css";
+@import "../../node_modules/bulma-timeline/dist/css/bulma-timeline.min.css";
+
+.timeline .timeline-item .timeline-content {
+  padding-top: 0;
+}
+
+.timeline .timeline-item:last-child::before {
+  display: none;
+}
+
+.timeline .timeline-item .timeline-marker {
+  top: 0;
+}
 
 .toast {
   position: absolute;
diff --git a/packages/frontend/src/utils/functions.ts 
b/packages/frontend/src/utils/functions.ts
index 7550e68..4fc2815 100644
--- a/packages/frontend/src/utils/functions.ts
+++ b/packages/frontend/src/utils/functions.ts
@@ -35,3 +35,8 @@ export function onTranslationError(error: MessageError) {
   if (typeof window === "undefined") return;
   window.MerchantBackoffice.missing_locales = 
window.MerchantBackoffice.missing_locales.concat(error.path.join())
 }
+
+export async function copyToClipboard(text:string) {
+  return navigator.clipboard.writeText(text)
+}
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1d791fe..259207a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -37,6 +37,7 @@ importers:
       bulma-radio: 1.1.1
       bulma-responsive-tables: 1.2.3
       bulma-switch-control: 1.1.1
+      bulma-timeline: 3.0.4
       bulma-upload-control: 1.2.0
       dotenv: 8.2.0
       enzyme: 3.11.0
@@ -86,6 +87,7 @@ importers:
       bulma-radio: ^1.1.1
       bulma-responsive-tables: ^1.2.3
       bulma-switch-control: ^1.1.1
+      bulma-timeline: ^3.0.4
       bulma-upload-control: ^1.2.0
       date-fns: ^2.19.0
       dotenv: ^8.2.0
@@ -4966,6 +4968,10 @@ packages:
     dev: true
     resolution:
       integrity: 
sha512-uvPhLeiip1P/JZf9nidbA+7cQmUYKzS5vVbzhEAUk0oz6H3hPhHDIef/rUwqig1veRUd7vXBZ1hOcsM9gLxv/A==
+  /bulma-timeline/3.0.4:
+    dev: true
+    resolution:
+      integrity: 
sha512-gCUOcSUuzHoeVMkCpLF49j5Z5yl78XQ+KgJcT+1ju5WIGgBgVytRUob/dw5NHAxPLO2rmcvwYNbCJFp7w4WT4Q==
   /bulma-upload-control/1.2.0:
     dependencies:
       bulma: 0.9.2

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]