gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: fix #8573


From: gnunet
Subject: [taler-wallet-core] branch master updated: fix #8573
Date: Tue, 05 Mar 2024 14:02:38 +0100

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

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new a6b6c6abf fix #8573
a6b6c6abf is described below

commit a6b6c6abf3f2c5cc9b20a6204078416ea5fba510
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Mar 5 10:02:32 2024 -0300

    fix #8573
---
 .../demobank-ui/src/components/Cashouts/views.tsx  | 19 ++++---
 .../src/components/Transactions/views.tsx          | 32 +++++------
 packages/demobank-ui/src/context/config.ts         |  1 +
 packages/demobank-ui/src/pages/BankFrame.tsx       |  6 ++-
 packages/demobank-ui/src/pages/LoginForm.tsx       |  9 ++--
 packages/web-util/src/components/Attention.tsx     | 62 ++++++++++++++--------
 packages/web-util/src/components/Button.tsx        |  3 +-
 .../src/components/GlobalNotificationBanner.tsx    | 11 ++--
 packages/web-util/src/hooks/useNotifications.ts    | 47 ++++++++++------
 9 files changed, 113 insertions(+), 77 deletions(-)

diff --git a/packages/demobank-ui/src/components/Cashouts/views.tsx 
b/packages/demobank-ui/src/components/Cashouts/views.tsx
index db1fdbfc5..90ee6bc2f 100644
--- a/packages/demobank-ui/src/components/Cashouts/views.tsx
+++ b/packages/demobank-ui/src/components/Cashouts/views.tsx
@@ -148,16 +148,15 @@ export function ReadyView({
                           locale: dateLocale,
                         });
                     return (
-                      <tr
+                      <a
+                        name="cashout details"
                         key={idx}
-                        class="border-b border-gray-200 hover:bg-gray-200 
last:border-none"
+                        class="table-row border-b border-gray-200 
hover:bg-gray-200 last:border-none"
+                        // class="table-row"
+                        href={routeCashoutDetails.url({
+                          cid: String(item.id),
+                        })}
                       >
-                        <a
-                          name="cashout details"
-                          href={routeCashoutDetails.url({
-                            cid: String(item.id),
-                          })}
-                        >
                           <td class="relative py-2 pl-2 pr-2 text-sm ">
                             <div class="font-medium text-gray-900">
                               {creationTime}
@@ -201,10 +200,10 @@ export function ReadyView({
                           </td>
 
                           <td class="hidden sm:table-cell px-3 py-3.5 text-sm 
text-gray-500 break-all min-w-md">
+
                             {item.subject}
                           </td>
-                        </a>
-                      </tr>
+                      </a>
                     );
                   })}
                 </Fragment>
diff --git a/packages/demobank-ui/src/components/Transactions/views.tsx 
b/packages/demobank-ui/src/components/Transactions/views.tsx
index ba400b37a..cdf134b2f 100644
--- a/packages/demobank-ui/src/components/Transactions/views.tsx
+++ b/packages/demobank-ui/src/components/Transactions/views.tsx
@@ -16,11 +16,10 @@
 
 import { Attention, useTranslationContext } from "@gnu-taler/web-util/browser";
 import { format } from "date-fns";
-import { Fragment, h, VNode } from "preact";
+import { Fragment, VNode, h } from "preact";
 import { useBankCoreApiContext } from "../../context/config.js";
 import { RenderAmount } from "../../pages/PaytoWireTransferForm.js";
 import { State } from "./index.js";
-import { useAccountDetails } from "../../hooks/access.js";
 
 export function ReadyView({
   transactions,
@@ -31,21 +30,24 @@ export function ReadyView({
   const { i18n, dateLocale } = useTranslationContext();
   const { config } = useBankCoreApiContext()
 
-  if (!transactions.length) return <div class="px-4 mt-4">
-    <div class="sm:flex sm:items-center">
-      <div class="sm:flex-auto">
-        <h1 class="text-base font-semibold leading-6 text-gray-900">
-          <i18n.Translate>Transactions history</i18n.Translate>
-        </h1>
+  if (!transactions.length) {
+    return <div class="px-4 mt-4">
+      <div class="sm:flex sm:items-center">
+        <div class="sm:flex-auto">
+          <h1 class="text-base font-semibold leading-6 text-gray-900">
+            <i18n.Translate>Transactions history</i18n.Translate>
+          </h1>
+        </div>
       </div>
-    </div>
-    <Attention type="info" title={i18n.str`No moves in your account yet.`}>
 
-      <i18n.Translate>
-        You can start sending a wire transfer or withdrawing to your wallet.
-      </i18n.Translate>
-    </Attention>
-  </div>;
+      <Attention type="low" title={i18n.str`No transactions yet.`}>
+        <i18n.Translate>
+          You can start sending a wire transfer or withdrawing to your wallet.
+        </i18n.Translate>
+      </Attention>
+    </div>;
+  }
+
   const txByDate = transactions.reduce(
     (prev, cur) => {
       const d =
diff --git a/packages/demobank-ui/src/context/config.ts 
b/packages/demobank-ui/src/context/config.ts
index 529108275..e968b7ff4 100644
--- a/packages/demobank-ui/src/context/config.ts
+++ b/packages/demobank-ui/src/context/config.ts
@@ -239,6 +239,7 @@ class CacheAwareTalerCoreBankHttpClient extends 
TalerCoreBankHttpClient {
     if (resp.type === "ok") {
       await revalidateAccountDetails();
       await revalidateCashouts();
+      await revalidateTransactions();
     }
     return resp;
   }
diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx 
b/packages/demobank-ui/src/pages/BankFrame.tsx
index b914aa360..b6bfe1cfb 100644
--- a/packages/demobank-ui/src/pages/BankFrame.tsx
+++ b/packages/demobank-ui/src/pages/BankFrame.tsx
@@ -144,7 +144,11 @@ export function BankFrame({
         </Header>
       </div>
 
-      <GlobalNotificationsBanner />
+      <div class="fixed z-20 w-full">
+        <div class="mx-auto w-4/5">
+          <GlobalNotificationsBanner />
+        </div>
+      </div>
 
       <main class="-mt-32 flex-1">
         {account && routeAccountDetails && (
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx 
b/packages/demobank-ui/src/pages/LoginForm.tsx
index 09c0a8785..f0ca447e1 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -15,25 +15,22 @@
  */
 
 import {
-  HttpStatusCode,
-  TranslatedString,
-  assertUnreachable,
+  HttpStatusCode
 } from "@gnu-taler/taler-util";
 import {
   Button,
   LocalNotificationBanner,
   ShowInputErrorLabel,
-  useLocalNotification,
   useLocalNotificationHandler,
-  useTranslationContext,
+  useTranslationContext
 } from "@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useBankCoreApiContext } from "../context/config.js";
 import { useBackendState } from "../hooks/backend.js";
+import { RouteDefinition } from "../route.js";
 import { undefinedIfEmpty } from "../utils.js";
 import { doAutoFocus } from "./PaytoWireTransferForm.js";
-import { EmptyObject, RouteDefinition } from "../route.js";
 
 /**
  * Collect and submit login data.
diff --git a/packages/web-util/src/components/Attention.tsx 
b/packages/web-util/src/components/Attention.tsx
index b85230a1b..50378e85a 100644
--- a/packages/web-util/src/components/Attention.tsx
+++ b/packages/web-util/src/components/Attention.tsx
@@ -1,36 +1,51 @@
-import { TranslatedString, assertUnreachable } from "@gnu-taler/taler-util";
+import { Duration, TranslatedString, assertUnreachable } from 
"@gnu-taler/taler-util";
 import { ComponentChildren, Fragment, VNode, h } from "preact";
 
 interface Props {
-  type?: "info" | "success" | "warning" | "danger",
+  type?: "info" | "success" | "warning" | "danger" | "low",
   onClose?: () => void,
   title: TranslatedString,
   children?: ComponentChildren,
+  timeout?: Duration,
 }
-export function Attention({ type = "info", title, children, onClose }: Props): 
VNode {
+export function Attention({ type = "info", title, children, onClose, timeout = 
Duration.getForever() }: Props): VNode {
   return <div class={`group attention-${type} mt-2 shadow-lg`}>
-    <div class="rounded-md group-[.attention-info]:bg-blue-50 
group-[.attention-warning]:bg-yellow-50 group-[.attention-danger]:bg-red-50 
group-[.attention-success]:bg-green-50 p-4 shadow">
+    <style>{`
+    .progress {
+        animation: notificationTimeoutBar 3s ease-in-out;
+        animation-fill-mode:both; 
+    }
+
+    @keyframes notificationTimeoutBar {
+      0% { width: 0; }
+      100% { width: 100%; }
+    }
+  `}</style>
+
+    <div data-timed={timeout.d_ms !== "forever"} class="rounded-md 
data-[timed=true]:rounded-b-none group-[.attention-info]:bg-blue-50 
group-[.attention-low]:bg-gray-100 group-[.attention-warning]:bg-yellow-50 
group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 p-4 
shadow">
       <div class="flex">
         <div >
-          <svg xmlns="http://www.w3.org/2000/svg"; stroke="none" viewBox="0 0 
24 24" fill="currentColor" class="w-8 h-8 group-[.attention-info]:text-blue-400 
group-[.attention-warning]:text-yellow-400 
group-[.attention-danger]:text-red-400 
group-[.attention-success]:text-green-400">
-            {(() => {
-              switch (type) {
-                case "info":
-                  return <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 
0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 
01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 
0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" />
-                case "warning":
-                  return <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 
4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 
0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 
0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
-                case "danger":
-                  return <path fill-rule="evenodd" d="M2.25 12c0-5.385 
4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 
12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 
8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
-                case "success":
-                  return <path fill-rule="evenodd" d="M7.493 18.75c-.425 
0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 
1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 
012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 
0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 
1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 
1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.987. [...]
-                default:
-                  assertUnreachable(type)
-              }
-            })()}
-          </svg>
+          {type === "low" ? undefined :
+            <svg xmlns="http://www.w3.org/2000/svg"; stroke="none" viewBox="0 0 
24 24" fill="currentColor" class="w-8 h-8 group-[.attention-info]:text-blue-400 
group-[.attention-warning]:text-yellow-400 
group-[.attention-danger]:text-red-400 
group-[.attention-success]:text-green-400">
+              {(() => {
+                switch (type) {
+                  case "info":
+                    return <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 
8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 
01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 
0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" />
+                  case "warning":
+                    return <path fill-rule="evenodd" d="M9.401 3.003c1.155-2 
4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 
0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 
0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+                  case "danger":
+                    return <path fill-rule="evenodd" d="M2.25 12c0-5.385 
4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 
12zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 
8.25a.75.75 0 100-1.5.75.75 0 000 1.5z" />
+                  case "success":
+                    return <path fill-rule="evenodd" d="M7.493 18.75c-.425 
0-.82-.236-.975-.632A7.48 7.48 0 016 15.375c0-1.75.599-3.358 
1.602-4.634.151-.192.373-.309.6-.397.473-.183.89-.514 1.212-.924a9.042 9.042 0 
012.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 00.322-1.672V3a.75.75 
0 01.75-.75 2.25 2.25 0 012.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 
1.282.725 1.282h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 
1.285a11.95 11.95 0 01-2.649 7.521c-.388.482-.98 [...]
+                  default:
+                    assertUnreachable(type)
+                }
+              })()}
+            </svg>
+          }
         </div>
         <div class="ml-3 w-full">
-          <h3 class="text-sm group-hover:text-white font-bold 
group-[.attention-info]:text-blue-800 group-[.attention-success]:text-green-800 
group-[.attention-warning]:text-yellow-800 
group-[.attention-danger]:text-red-800">
+          <h3 class="text-sm font-bold group-[.attention-info]:text-blue-800 
group-[.attention-success]:text-green-800 
group-[.attention-warning]:text-yellow-800 
group-[.attention-danger]:text-red-800">
             {title}
           </h3>
           <div class="mt-2 text-sm group-[.attention-info]:text-blue-700 
group-[.attention-warning]:text-yellow-700 
group-[.attention-danger]:text-red-700 
group-[.attention-success]:text-green-700">
@@ -53,6 +68,11 @@ export function Attention({ type = "info", title, children, 
onClose }: Props): V
         }
       </div>
     </div>
+    {timeout.d_ms === "forever" ? undefined :
+      <div class="meter group-[.attention-info]:bg-blue-50 
group-[.attention-low]:bg-gray-100 group-[.attention-warning]:bg-yellow-50 
group-[.attention-danger]:bg-red-50 group-[.attention-success]:bg-green-50 h-1 
relative overflow-hidden -mt-1">
+        <span class="w-full h-full block"><span class="h-full block progress 
group-[.attention-info]:bg-blue-600 group-[.attention-low]:bg-gray-600 
group-[.attention-warning]:bg-yellow-600 group-[.attention-danger]:bg-red-600 
group-[.attention-success]:bg-green-600"></span></span>
+      </div>
+    }
 
   </div>
 }
diff --git a/packages/web-util/src/components/Button.tsx 
b/packages/web-util/src/components/Button.tsx
index 758efafcf..26b778eec 100644
--- a/packages/web-util/src/components/Button.tsx
+++ b/packages/web-util/src/components/Button.tsx
@@ -45,8 +45,7 @@ export function Button<T extends OperationResult<A, B>, A, 
B>({
     handler.onClick().then((resp) => {
       if (resp) {
         if (resp.type === "ok") {
-          // @ts-expect-error this is an operationOk
-          const result: OperationOk<any> = resp.body
+          const result: OperationOk<any> = resp
           // @ts-expect-error this is an operationOk
           const msg = handler.onOperationSuccess(result)
           if (msg) {
diff --git a/packages/web-util/src/components/GlobalNotificationBanner.tsx 
b/packages/web-util/src/components/GlobalNotificationBanner.tsx
index c8049acc3..b0a06f7e1 100644
--- a/packages/web-util/src/components/GlobalNotificationBanner.tsx
+++ b/packages/web-util/src/components/GlobalNotificationBanner.tsx
@@ -1,28 +1,27 @@
 import { Fragment, VNode, h } from "preact"
-import { Attention, useNotifications } from "../index.browser.js"
+import { Attention, GLOBAL_NOTIFICATION_TIMEOUT, useNotifications } from 
"../index.browser.js"
 
 export function GlobalNotificationsBanner(): VNode {
   const notifs = useNotifications()
   if (notifs.length === 0) return <Fragment />
-  return <div class="fixed z-20 w-full p-4"> {
+  return <Fragment> {
     notifs.map(n => {
       switch (n.message.type) {
         case "error":
           return <Attention type="danger" title={n.message.title} onClose={() 
=> {
             n.remove()
-          }}>
+          }} timeout={GLOBAL_NOTIFICATION_TIMEOUT}>
             {n.message.description &&
               <div class="mt-2 text-sm text-red-700">
                 {n.message.description}
               </div>
             }
-            {/* <MaybeShowDebugInfo info={n.message.debug} /> */}
           </Attention>
         case "info":
           return <Attention type="success" title={n.message.title} onClose={() 
=> {
             n.remove();
-          }} />
+          }} timeout={GLOBAL_NOTIFICATION_TIMEOUT} />
       }
     })}
-  </div>
+  </Fragment>
 }
diff --git a/packages/web-util/src/hooks/useNotifications.ts 
b/packages/web-util/src/hooks/useNotifications.ts
index 33e0cdf53..9f955f92d 100644
--- a/packages/web-util/src/hooks/useNotifications.ts
+++ b/packages/web-util/src/hooks/useNotifications.ts
@@ -1,4 +1,4 @@
-import { OperationFail, OperationOk, OperationResult, TalerError, 
TalerErrorCode, TranslatedString } from "@gnu-taler/taler-util";
+import { Duration, OperationFail, OperationOk, OperationResult, TalerError, 
TalerErrorCode, TranslatedString } from "@gnu-taler/taler-util";
 import { useEffect, useState } from "preact/hooks";
 import { ButtonHandler } from "../components/Button.js";
 import { InternationalizationAPI, memoryMap, useTranslationContext } from 
"../index.browser.js";
@@ -19,10 +19,28 @@ export interface InfoNotification {
 const storage = memoryMap<Map<string, NotificationMessage>>();
 const NOTIFICATION_KEY = "notification";
 
+export const GLOBAL_NOTIFICATION_TIMEOUT: Duration = { d_ms: 3 * 1000 }
+
+function removeFromStorage(n: NotificationMessage) {
+  const h = hash(n)
+  const mem = storage.get(NOTIFICATION_KEY) ?? new Map();
+  const newState = new Map(mem);
+  newState.delete(h);
+  storage.set(NOTIFICATION_KEY, newState);
+}
+
+
 export function notify(notif: NotificationMessage): void {
   const currentState: Map<string, NotificationMessage> =
     storage.get(NOTIFICATION_KEY) ?? new Map();
   const newState = currentState.set(hash(notif), notif);
+
+  if (GLOBAL_NOTIFICATION_TIMEOUT.d_ms !== "forever") {
+    setTimeout(() => {
+      removeFromStorage(notif)
+    }, GLOBAL_NOTIFICATION_TIMEOUT.d_ms);
+  }
+
   storage.set(NOTIFICATION_KEY, newState);
 }
 export function notifyError(
@@ -73,10 +91,7 @@ export function useNotifications(): Notification[] {
     return {
       message,
       remove: () => {
-        const mem = storage.get(NOTIFICATION_KEY) ?? new Map();
-        const newState = new Map(mem);
-        newState.delete(hash(message));
-        storage.set(NOTIFICATION_KEY, newState);
+        removeFromStorage(message)
       },
     };
   });
@@ -124,7 +139,7 @@ export type ErrorNotificationHandler = (cb: (notify: typeof 
errorMap) => Promise
  * @returns 
  */
 export function useLocalNotification(): [Notification | undefined, (n: 
NotificationMessage) => void, ErrorNotificationHandler] {
-  const {i18n} = useTranslationContext();
+  const { i18n } = useTranslationContext();
 
   const [value, setter] = useState<NotificationMessage>();
   const notif = !value ? undefined : {
@@ -154,12 +169,12 @@ export function useLocalNotification(): [Notification | 
undefined, (n: Notificat
   return [notif, setter, errorHandling]
 }
 
-type HandlerMaker = <T extends OperationResult<A, B>,A,B>(
+type HandlerMaker = <T extends OperationResult<A, B>, A, B>(
   onClick: () => Promise<T | undefined>,
-  onOperationSuccess: ((result:T extends OperationOk<any> ? T :never) => void) 
| ((result:T extends OperationOk<any> ? T :never) => TranslatedString | 
undefined),
+  onOperationSuccess: ((result: T extends OperationOk<any> ? T : never) => 
void) | ((result: T extends OperationOk<any> ? T : never) => TranslatedString | 
undefined),
   onOperationFail: (d: T extends OperationFail<any> ? T : never) => 
TranslatedString,
   onOperationComplete?: () => void,
-) => ButtonHandler<T,A,B>;
+) => ButtonHandler<T, A, B>;
 
 export function useLocalNotificationHandler(): [Notification | undefined, 
HandlerMaker, (n: NotificationMessage) => void] {
   const [value, setter] = useState<NotificationMessage>();
@@ -169,20 +184,20 @@ export function useLocalNotificationHandler(): 
[Notification | undefined, Handle
       setter(undefined);
     },
   }
-  
-  function makeHandler<T extends OperationResult<A, B>,A,B>(
+
+  function makeHandler<T extends OperationResult<A, B>, A, B>(
     onClick: () => Promise<T | undefined>,
-    onOperationSuccess: ((result:T extends OperationOk<any> ? T :never) => 
void) | ((result:T extends OperationOk<any> ? T :never) => TranslatedString | 
undefined),
+    onOperationSuccess: ((result: T extends OperationOk<any> ? T : never) => 
void) | ((result: T extends OperationOk<any> ? T : never) => TranslatedString | 
undefined),
     onOperationFail: (d: T extends OperationFail<any> ? T : never) => 
TranslatedString,
-    onOperationComplete?: () => void,  
-  ): ButtonHandler<T,A,B> {
+    onOperationComplete?: () => void,
+  ): ButtonHandler<T, A, B> {
     return { onClick, onNotification: setter, onOperationFail, 
onOperationSuccess, onOperationComplete }
   }
-  
+
   return [notif, makeHandler, setter]
 }
 
-export function buildRequestErrorMessage( i18n: InternationalizationAPI, 
cause: TalerError): ErrorNotification {
+export function buildRequestErrorMessage(i18n: InternationalizationAPI, cause: 
TalerError): ErrorNotification {
   let result: ErrorNotification;
   switch (cause.errorDetail.code) {
     case TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT: {

-- 
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]