From bda1c0721b7716934234c1741d3c10c88857b87d Mon Sep 17 00:00:00 2001
From: Nikola Pavlov <nikola@txfusion.io>
Date: Mon, 23 Dec 2024 14:25:55 +0100
Subject: [PATCH 1/6] feat: add toggle switch between time ago and utc time

---
 .../common/table/TableHeadColumn.vue          |  2 +-
 .../common/table/fields/TimeField.vue         |  7 ++++++-
 .../app/src/components/transactions/Table.vue | 21 ++++++++++++++++---
 packages/app/src/locales/en.json              |  1 +
 4 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/packages/app/src/components/common/table/TableHeadColumn.vue b/packages/app/src/components/common/table/TableHeadColumn.vue
index 458fbd016a..eac1f77d84 100644
--- a/packages/app/src/components/common/table/TableHeadColumn.vue
+++ b/packages/app/src/components/common/table/TableHeadColumn.vue
@@ -8,6 +8,6 @@
 
 <style>
 .table-head-col {
-  @apply whitespace-nowrap px-2 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-700 first:pl-6 last:pr-6;
+  @apply whitespace-nowrap px-2 py-3 text-left text-xs font-semibold uppercase tracking-wider text-gray-700 first:pl-6 last:pr-6;
 }
 </style>
diff --git a/packages/app/src/components/common/table/fields/TimeField.vue b/packages/app/src/components/common/table/fields/TimeField.vue
index 24216056e4..43a9fa6623 100644
--- a/packages/app/src/components/common/table/fields/TimeField.vue
+++ b/packages/app/src/components/common/table/fields/TimeField.vue
@@ -1,7 +1,7 @@
 <template>
   <div class="info-field-time" :title="utcStringFromISOString(isoString)">
     <span class="time-ago">
-      {{ timeAgo }}
+      {{ isUtcDate ? localDateFromISOString(isoString) : timeAgo }}
     </span>
     <span v-if="showExactDate" class="full-date">{{ localDateFromISOString(isoString) }}</span>
   </div>
@@ -28,6 +28,11 @@ const props = defineProps({
     default: true,
     required: false,
   },
+  isUtcDate: {
+    type: Boolean,
+    default: false,
+    required: false,
+  },
 });
 
 const messages = ref({
diff --git a/packages/app/src/components/transactions/Table.vue b/packages/app/src/components/transactions/Table.vue
index aedbdf4ca9..e551ac9dff 100644
--- a/packages/app/src/components/transactions/Table.vue
+++ b/packages/app/src/components/transactions/Table.vue
@@ -8,8 +8,12 @@
       <TableHeadColumn v-if="columns.includes('method')">
         {{ t("transactions.table.method") }}
       </TableHeadColumn>
-      <TableHeadColumn v-if="columns.includes('age')">
-        {{ t("transactions.table.age") }}
+      <TableHeadColumn
+        v-if="columns.includes('age')"
+        @click="toggleAgeTimestamp()"
+        class="hover:cursor-pointer text-blue-700"
+      >
+        {{ isTimeAgeView ? t("transactions.table.age") : t("transactions.table.dateTimeUTC") }}
       </TableHeadColumn>
       <TableHeadColumn v-if="columns.includes('from')" class="tablet-column-hidden">
         {{ t("transactions.table.from") }}
@@ -71,7 +75,12 @@
         :data-heading="t('transactions.table.age')"
       >
         <CopyButton :value="utcStringFromISOString(item.receivedAt)">
-          <TimeField :value="item.receivedAt" :show-exact-date="false" :data-testid="$testId.timestamp" />
+          <TimeField
+            :value="item.receivedAt"
+            :show-exact-date="false"
+            :data-testid="$testId.timestamp"
+            :is-utc-date="!isTimeAgeView"
+          />
         </CopyButton>
       </TableBodyColumn>
       <TableBodyColumn
@@ -364,6 +373,12 @@ const isHighRowsSize = computed(() => props.columns.includes("fee"));
 function getDirection(item: TransactionListItem): Direction {
   return item.from === item.to ? "self" : item.to !== props.searchParams?.address ? "out" : "in";
 }
+
+const isTimeAgeView = ref(true);
+
+const toggleAgeTimestamp = () => {
+  isTimeAgeView.value = !isTimeAgeView.value;
+};
 </script>
 
 <style lang="scss">
diff --git a/packages/app/src/locales/en.json b/packages/app/src/locales/en.json
index 70faef1b62..289060c7fc 100644
--- a/packages/app/src/locales/en.json
+++ b/packages/app/src/locales/en.json
@@ -143,6 +143,7 @@
             "transferMethodName": "Transfer",
             "timestamp": "Timestamp",
             "age": "Age",
+            "dateTimeUTC": "Date Time (UTC)",
             "tokenAddress": "Token address",
             "tokenName": "Token name",
             "tokenSymbol": "Token symbol",

From a0615c73186b39ce53efe1deac33445062aaff33 Mon Sep 17 00:00:00 2001
From: Nikola Pavlov <nikola@txfusion.io>
Date: Mon, 27 Jan 2025 10:21:52 +0100
Subject: [PATCH 2/6] feat: refactor the props for timefield component

---
 packages/app/src/components/batches/Table.vue |  3 ++-
 packages/app/src/components/blocks/Table.vue  |  4 +++-
 .../common/table/fields/TimeField.vue         | 24 ++++++++-----------
 .../app/src/components/transactions/Table.vue |  5 ++--
 .../transactions/infoTable/GeneralInfo.vue    |  3 ++-
 .../app/src/components/transfers/Table.vue    |  2 +-
 packages/app/src/types.ts                     |  6 +++++
 7 files changed, 26 insertions(+), 21 deletions(-)

diff --git a/packages/app/src/components/batches/Table.vue b/packages/app/src/components/batches/Table.vue
index 88d4d94a42..51ff3ead92 100644
--- a/packages/app/src/components/batches/Table.vue
+++ b/packages/app/src/components/batches/Table.vue
@@ -34,7 +34,7 @@
       </TableBodyColumn>
       <TableBodyColumn v-if="columns.includes('age')" :data-heading="t('batches.table.age')">
         <CopyButton :value="utcStringFromISOString(item.timestamp)">
-          <TimeField :value="item.timestamp" :show-exact-date="false" />
+          <TimeField :value="item.timestamp" />
         </CopyButton>
       </TableBodyColumn>
     </template>
@@ -65,6 +65,7 @@ import ZkSyncIcon from "@/components/icons/ZkSync.vue";
 import type { BatchListItem } from "@/composables/useBatches";
 import type { PropType } from "vue";
 
+import { TimeFormat } from "@/types";
 import { utcStringFromISOString } from "@/utils/helpers";
 
 const { t, te } = useI18n();
diff --git a/packages/app/src/components/blocks/Table.vue b/packages/app/src/components/blocks/Table.vue
index 178c14c116..cdb2b248cb 100644
--- a/packages/app/src/components/blocks/Table.vue
+++ b/packages/app/src/components/blocks/Table.vue
@@ -49,7 +49,7 @@
       </TableBodyColumn>
       <TableBodyColumn :data-heading="t('blocks.table.age')">
         <CopyButton :value="item.timestamp">
-          <TimeField :value="item.timestamp" :show-exact-date="false" />
+          <TimeField :value="item.timestamp" />
         </CopyButton>
       </TableBodyColumn>
     </template>
@@ -79,6 +79,8 @@ import TimeField from "@/components/common/table/fields/TimeField.vue";
 import type { BlockListItem } from "@/composables/useBlock";
 import type { PropType } from "vue";
 
+import { TimeFormat } from "@/types";
+
 const { t } = useI18n();
 
 defineProps({
diff --git a/packages/app/src/components/common/table/fields/TimeField.vue b/packages/app/src/components/common/table/fields/TimeField.vue
index 43a9fa6623..6a14053db6 100644
--- a/packages/app/src/components/common/table/fields/TimeField.vue
+++ b/packages/app/src/components/common/table/fields/TimeField.vue
@@ -1,18 +1,20 @@
 <template>
   <div class="info-field-time" :title="utcStringFromISOString(isoString)">
-    <span class="time-ago">
-      {{ isUtcDate ? localDateFromISOString(isoString) : timeAgo }}
-    </span>
-    <span v-if="showExactDate" class="full-date">{{ localDateFromISOString(isoString) }}</span>
+    <span class="time-ago" v-if="format === TimeFormat.FULL">{{ localDateFromISOString(isoString) }}</span>
+    <span class="time-ago" v-else>{{ timeAgo }}</span>
+    <span v-if="format === TimeFormat.TIME_AGO_AND_FULL" class="full-date">{{
+      localDateFromISOString(isoString)
+    }}</span>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from "vue";
+import { computed, type PropType, ref } from "vue";
 import { useI18n } from "vue-i18n";
 
 import { useTimeAgo } from "@vueuse/core";
 
+import { TimeFormat } from "@/types";
 import { ISOStringFromUnixTimestamp, localDateFromISOString, utcStringFromISOString } from "@/utils/helpers";
 
 const { t } = useI18n();
@@ -23,15 +25,9 @@ const props = defineProps({
     default: "",
     required: true,
   },
-  showExactDate: {
-    type: Boolean,
-    default: true,
-    required: false,
-  },
-  isUtcDate: {
-    type: Boolean,
-    default: false,
-    required: false,
+  format: {
+    type: String as PropType<TimeFormat>,
+    default: TimeFormat.TIME_AGO,
   },
 });
 
diff --git a/packages/app/src/components/transactions/Table.vue b/packages/app/src/components/transactions/Table.vue
index e551ac9dff..06e917e336 100644
--- a/packages/app/src/components/transactions/Table.vue
+++ b/packages/app/src/components/transactions/Table.vue
@@ -77,9 +77,8 @@
         <CopyButton :value="utcStringFromISOString(item.receivedAt)">
           <TimeField
             :value="item.receivedAt"
-            :show-exact-date="false"
             :data-testid="$testId.timestamp"
-            :is-utc-date="!isTimeAgeView"
+            :format="isTimeAgeView ? TimeFormat.TIME_AGO : TimeFormat.FULL"
           />
         </CopyButton>
       </TableBodyColumn>
@@ -246,8 +245,8 @@ import useTransactions, { type TransactionListItem, type TransactionSearchParams
 
 import type { Direction } from "@/components/transactions/TransactionDirectionTableCell.vue";
 import type { AbiFragment } from "@/composables/useAddress";
-import type { NetworkOrigin } from "@/types";
 
+import { type NetworkOrigin, TimeFormat } from "@/types";
 import { isContractDeployerAddress, utcStringFromISOString } from "@/utils/helpers";
 
 const { currentNetwork } = useContext();
diff --git a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue
index 7acc57fa46..fa6a84bd04 100644
--- a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue
+++ b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue
@@ -216,7 +216,7 @@
           </InfoTooltip>
         </table-body-column>
         <table-body-column class="transaction-table-value">
-          <TimeField :value="transaction?.receivedAt" />
+          <TimeField :value="transaction?.receivedAt" :format="TimeFormat.TIME_AGO_AND_FULL" />
         </table-body-column>
       </tr>
     </template>
@@ -255,6 +255,7 @@ import TransferTableCell from "@/components/transactions/infoTable/TransferTable
 
 import type { TransactionItem } from "@/composables/useTransaction";
 
+import { TimeFormat } from "@/types";
 import { isContractDeployerAddress } from "@/utils/helpers";
 
 const { t } = useI18n();
diff --git a/packages/app/src/components/transfers/Table.vue b/packages/app/src/components/transfers/Table.vue
index c80ac12370..0a7ca43a04 100644
--- a/packages/app/src/components/transfers/Table.vue
+++ b/packages/app/src/components/transfers/Table.vue
@@ -42,7 +42,7 @@
 
       <TableBodyColumn :data-heading="t('transfers.table.age')">
         <CopyButton :value="utcStringFromISOString(item.timestamp)">
-          <TimeField :data-testid="$testId.timestamp" :value="item.timestamp" :show-exact-date="false" />
+          <TimeField :data-testid="$testId.timestamp" :value="item.timestamp" />
         </CopyButton>
       </TableBodyColumn>
       <TableBodyColumn :data-heading="t('transfers.table.type')" class="transfer-type">
diff --git a/packages/app/src/types.ts b/packages/app/src/types.ts
index d0c87863c2..caf3a22281 100644
--- a/packages/app/src/types.ts
+++ b/packages/app/src/types.ts
@@ -59,3 +59,9 @@ export type ContractVerificationData = {
 };
 
 export type ContractVerificationStatus = "successful" | "failed" | "in_progress" | "queued";
+
+export enum TimeFormat {
+  TIME_AGO = "time_ago",
+  FULL = "full",
+  TIME_AGO_AND_FULL = "time_ago_and_full",
+}

From 9010f97789121ff21016d0d61b1457ea9d63c0bb Mon Sep 17 00:00:00 2001
From: Nikola Pavlov <nikola@txfusion.io>
Date: Mon, 27 Jan 2025 10:23:54 +0100
Subject: [PATCH 3/6] feat: remove unused lines

---
 packages/app/src/components/batches/Table.vue | 1 -
 packages/app/src/components/blocks/Table.vue  | 2 --
 2 files changed, 3 deletions(-)

diff --git a/packages/app/src/components/batches/Table.vue b/packages/app/src/components/batches/Table.vue
index 51ff3ead92..8c1f523b15 100644
--- a/packages/app/src/components/batches/Table.vue
+++ b/packages/app/src/components/batches/Table.vue
@@ -65,7 +65,6 @@ import ZkSyncIcon from "@/components/icons/ZkSync.vue";
 import type { BatchListItem } from "@/composables/useBatches";
 import type { PropType } from "vue";
 
-import { TimeFormat } from "@/types";
 import { utcStringFromISOString } from "@/utils/helpers";
 
 const { t, te } = useI18n();
diff --git a/packages/app/src/components/blocks/Table.vue b/packages/app/src/components/blocks/Table.vue
index cdb2b248cb..534f72e8e1 100644
--- a/packages/app/src/components/blocks/Table.vue
+++ b/packages/app/src/components/blocks/Table.vue
@@ -79,8 +79,6 @@ import TimeField from "@/components/common/table/fields/TimeField.vue";
 import type { BlockListItem } from "@/composables/useBlock";
 import type { PropType } from "vue";
 
-import { TimeFormat } from "@/types";
-
 const { t } = useI18n();
 
 defineProps({

From f2883e2d4f8cbbfc3a647c420aef37c940ba85e1 Mon Sep 17 00:00:00 2001
From: Nikola Pavlov <nikola@txfusion.io>
Date: Mon, 27 Jan 2025 11:49:15 +0100
Subject: [PATCH 4/6] feat: fix props requirement

---
 packages/app/src/components/batches/Table.vue                 | 3 ++-
 packages/app/src/components/blocks/Table.vue                  | 4 +++-
 packages/app/src/components/common/table/fields/TimeField.vue | 3 ++-
 .../app/src/components/transactions/infoTable/GeneralInfo.vue | 3 +--
 packages/app/src/components/transfers/Table.vue               | 3 ++-
 5 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/packages/app/src/components/batches/Table.vue b/packages/app/src/components/batches/Table.vue
index 8c1f523b15..2fe784d2a8 100644
--- a/packages/app/src/components/batches/Table.vue
+++ b/packages/app/src/components/batches/Table.vue
@@ -34,7 +34,7 @@
       </TableBodyColumn>
       <TableBodyColumn v-if="columns.includes('age')" :data-heading="t('batches.table.age')">
         <CopyButton :value="utcStringFromISOString(item.timestamp)">
-          <TimeField :value="item.timestamp" />
+          <TimeField :value="item.timestamp" :format="TimeFormat.TIME_AGO" />
         </CopyButton>
       </TableBodyColumn>
     </template>
@@ -65,6 +65,7 @@ import ZkSyncIcon from "@/components/icons/ZkSync.vue";
 import type { BatchListItem } from "@/composables/useBatches";
 import type { PropType } from "vue";
 
+import { TimeFormat } from "@/types";
 import { utcStringFromISOString } from "@/utils/helpers";
 
 const { t, te } = useI18n();
diff --git a/packages/app/src/components/blocks/Table.vue b/packages/app/src/components/blocks/Table.vue
index 534f72e8e1..1692e0a753 100644
--- a/packages/app/src/components/blocks/Table.vue
+++ b/packages/app/src/components/blocks/Table.vue
@@ -49,7 +49,7 @@
       </TableBodyColumn>
       <TableBodyColumn :data-heading="t('blocks.table.age')">
         <CopyButton :value="item.timestamp">
-          <TimeField :value="item.timestamp" />
+          <TimeField :value="item.timestamp" :format="TimeFormat.TIME_AGO" />
         </CopyButton>
       </TableBodyColumn>
     </template>
@@ -79,6 +79,8 @@ import TimeField from "@/components/common/table/fields/TimeField.vue";
 import type { BlockListItem } from "@/composables/useBlock";
 import type { PropType } from "vue";
 
+import { TimeFormat } from "@/types";
+
 const { t } = useI18n();
 
 defineProps({
diff --git a/packages/app/src/components/common/table/fields/TimeField.vue b/packages/app/src/components/common/table/fields/TimeField.vue
index 6a14053db6..d61817de16 100644
--- a/packages/app/src/components/common/table/fields/TimeField.vue
+++ b/packages/app/src/components/common/table/fields/TimeField.vue
@@ -27,7 +27,8 @@ const props = defineProps({
   },
   format: {
     type: String as PropType<TimeFormat>,
-    default: TimeFormat.TIME_AGO,
+    default: TimeFormat.TIME_AGO_AND_FULL,
+    required: false,
   },
 });
 
diff --git a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue
index fa6a84bd04..7acc57fa46 100644
--- a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue
+++ b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue
@@ -216,7 +216,7 @@
           </InfoTooltip>
         </table-body-column>
         <table-body-column class="transaction-table-value">
-          <TimeField :value="transaction?.receivedAt" :format="TimeFormat.TIME_AGO_AND_FULL" />
+          <TimeField :value="transaction?.receivedAt" />
         </table-body-column>
       </tr>
     </template>
@@ -255,7 +255,6 @@ import TransferTableCell from "@/components/transactions/infoTable/TransferTable
 
 import type { TransactionItem } from "@/composables/useTransaction";
 
-import { TimeFormat } from "@/types";
 import { isContractDeployerAddress } from "@/utils/helpers";
 
 const { t } = useI18n();
diff --git a/packages/app/src/components/transfers/Table.vue b/packages/app/src/components/transfers/Table.vue
index 0a7ca43a04..f52883fae5 100644
--- a/packages/app/src/components/transfers/Table.vue
+++ b/packages/app/src/components/transfers/Table.vue
@@ -42,7 +42,7 @@
 
       <TableBodyColumn :data-heading="t('transfers.table.age')">
         <CopyButton :value="utcStringFromISOString(item.timestamp)">
-          <TimeField :data-testid="$testId.timestamp" :value="item.timestamp" />
+          <TimeField :data-testid="$testId.timestamp" :value="item.timestamp" :format="TimeFormat.TIME_AGO" />
         </CopyButton>
       </TableBodyColumn>
       <TableBodyColumn :data-heading="t('transfers.table.type')" class="transfer-type">
@@ -156,6 +156,7 @@ import TransactionNetworkSquareBlock from "@/components/transactions/Transaction
 
 import useTransfers, { type Transfer } from "@/composables/useTransfers";
 
+import { TimeFormat } from "@/types";
 import { utcStringFromISOString } from "@/utils/helpers";
 
 const { t } = useI18n();

From 8f190e8daba84fdd78b5af31243dd4cd66ec6ad6 Mon Sep 17 00:00:00 2001
From: Nikola Pavlov <nikola@txfusion.io>
Date: Mon, 27 Jan 2025 11:59:01 +0100
Subject: [PATCH 5/6] feat: fix TimeField test case

---
 .../tests/components/common/table/fields/TimeField.spec.ts    | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/app/tests/components/common/table/fields/TimeField.spec.ts b/packages/app/tests/components/common/table/fields/TimeField.spec.ts
index 1ee586b36b..24dc6eae32 100644
--- a/packages/app/tests/components/common/table/fields/TimeField.spec.ts
+++ b/packages/app/tests/components/common/table/fields/TimeField.spec.ts
@@ -8,6 +8,8 @@ import TimeField from "@/components/common/table/fields/TimeField.vue";
 
 import enUS from "@/locales/en.json";
 
+import { TimeFormat } from "@/types";
+
 describe("TimeField", () => {
   const i18n = createI18n({
     locale: "en",
@@ -47,7 +49,7 @@ describe("TimeField", () => {
       global,
       props: {
         value: "2022-12-02T09:26:06.605Z",
-        showExactDate: false,
+        format: TimeFormat.TIME_AGO,
       },
     });
 

From 443557bfd0656b36f17a87aee918f1cee65f3e58 Mon Sep 17 00:00:00 2001
From: Vasyl Ivanchuk <vasyl.ivanchuk@gmail.com>
Date: Tue, 28 Jan 2025 17:40:59 +0200
Subject: [PATCH 6/6] chore: time field test descriptions

---
 .../tests/components/common/table/fields/TimeField.spec.ts    | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/app/tests/components/common/table/fields/TimeField.spec.ts b/packages/app/tests/components/common/table/fields/TimeField.spec.ts
index 24dc6eae32..888446ff90 100644
--- a/packages/app/tests/components/common/table/fields/TimeField.spec.ts
+++ b/packages/app/tests/components/common/table/fields/TimeField.spec.ts
@@ -33,7 +33,7 @@ describe("TimeField", () => {
     expect(container.querySelector(".info-field-time")?.getAttribute("title")).toBe("2022-12-02 09:26:06 UTC");
     unmount();
   });
-  it("renders full date when showExactDate is true by default", () => {
+  it("renders full date when time format is not specified", () => {
     const { container, unmount } = render(TimeField, {
       global,
       props: {
@@ -44,7 +44,7 @@ describe("TimeField", () => {
     expect(container.querySelector(".full-date")?.textContent).toBe("2022-12-02 12:26");
     unmount();
   });
-  it("doesn't render full date when showExactDate is false", () => {
+  it("doesn't render full date when time format is TIME_AGO", () => {
     const { container, unmount } = render(TimeField, {
       global,
       props: {