kodadot/nft-gallery

View on GitHub
components/shared/TransactionLoader.vue

Summary

Maintainability
Test Coverage
<template>
  <NeoModal
    :value="modelValue"
    :can-cancel="canCancel"
    :no-shadow="isMobile"
    :content-class="[isMobile ? 'mobile-modal' : '']"
    @close="emit('close')"
    @update:active="updateActive"
  >
    <div :class="{ 'desktop-width': !isMobile }">
      <div
        v-if="isFinalStep"
        class="flex py-5 px-6 items-center"
      >
        <div class="flex-grow text-center">
          {{ $t('success') }}
        </div>
        <NeoButton
          variant="text"
          no-shadow
          icon="xmark"
          size="medium"
          @click="emit('close')"
        />
      </div>
      <div
        v-else
        class="flex justify-between items-center py-5 px-6 border-b border-k-shade"
      >
        <span>Tx:</span>
        <div class="flex">
          <slot name="action-title">
            <span>{{ `${$t('teleport.send')} ${totalUsdValue}$` }}</span>
            <span class="text-k-grey ml-1 uppercase">{{
              `(${totalTokenAmount} ${unit})`
            }}</span>
          </slot>
        </div>

        <NeoButton
          variant="text"
          no-shadow
          icon="xmark"
          size="medium"
          @click="emit('close')"
        />
      </div>
      <figure class="px-6 pb-4">
        <img
          class="px-6"
          :class="{ 'py-6': isFinalStep }"
          :src="isFinalStep ? congrats : loading"
          alt="loading"
        >
      </figure>
      <div class="pb-4">
        <NeoSteps
          v-model="activeStep"
          step-size="0.5rem"
        >
          <NeoStepItem
            v-for="i in 3"
            :key="i"
            :icon="checkIconForStep(i)"
            :clickable="false"
            :label="steps[i - 1].label"
            :variant="i == 3 ? 'last' : undefined"
          >
            <div class="px-4 text-center">
              {{ steps[i - 1].tip }}
            </div>
          </NeoStepItem>
        </NeoSteps>
        <div
          v-if="activeStep === 2"
          class="text-center text-k-grey"
        >
          {{ `Est. waiting time ~ ${estimatedTimeLeft} seconds` }}
        </div>
        <div
          v-if="isFinalStep"
          class="flex justify-center mb-4"
        >
          <NeoButton
            v-safe-href="explorerLink"
            tag="a"
            target="_blank"
            class="px-4"
            no-shadow
            rounded
            :label="$i18n.t('transactionLoader.showTransaction')"
          />

          <NeoButton
            v-clipboard:copy="explorerLink"
            icon="copy"
            class="ml-4 px-4"
            rounded
            no-shadow
            @click="toast($i18n.t('transactionLoader.copyTransactionLink'))"
          />
        </div>
      </div>
    </div>
  </NeoModal>
</template>

<script lang="ts" setup>
import { NeoButton, NeoModal, NeoStepItem, NeoSteps } from '@kodadot1/brick'
import { TransactionStatus } from '@/composables/useTransactionStatus'
import { chainPropListOf } from '@/utils/config/chain.config'
import loading from '~/assets/svg/preloader.svg?url'
import congrats from '~/assets/svg/congrats-message-header.svg?url'

const props = withDefaults(
  defineProps<{
    status: TransactionStatus
    modelValue: boolean
    totalTokenAmount?: number
    totalUsdValue?: number
    unit?: string
    transactionId: string
    canCancel?: boolean
    isMobile?: boolean
  }>(),
  {
    canCancel: false,
    isMobile: false,
  },
)
const emit = defineEmits(['close', 'update:modelValue'])
const { $i18n } = useNuxtApp()
const { urlPrefix } = usePrefix()
const { estimatedTimes } = useBlockTime()
const { toast } = useToast()

const estimatedTimeLeft = computed(
  () => estimatedTimes.value[props.status] || 'few',
)
const explorerLink = computed(() => {
  const explorerBaseUrl = chainPropListOf(urlPrefix.value).blockExplorer
  return `${explorerBaseUrl}extrinsic/${props.transactionId}`
})

const activeStep = computed(() => {
  switch (props.status) {
    case TransactionStatus.Finalized:
      return 3
    case TransactionStatus.Unknown:
      return 1
    default:
      return 2
  }
})

const updateActive = (value: boolean) => {
  emit('update:modelValue', value)
}

const steps = [
  {
    label: $i18n.t('transactionLoader.sign'),
    tip: $i18n.t('transactionLoader.signTip'),
  },
  {
    label: $i18n.t('transactionLoader.pending'),
    tip: $i18n.t('transactionLoader.pendingTip'),
  },
  {
    label: $i18n.t('transactionLoader.completed'),
    tip: $i18n.t('transactionLoader.completedTip'),
  },
]

const isFinalStep = computed(() => activeStep.value === steps.length)

const checkIconForStep = (step: number) =>
  activeStep.value > step || activeStep.value === steps.length
    ? 'check'
    : undefined
</script>

<style scoped lang="scss">
.desktop-width {
  width: 27rem;
}
:deep(.mobile-modal) {
  @apply fixed rounded-[0.75rem_0.75rem_0_0] border-b-0 border-x-0 bottom-0 inset-x-0;
}
</style>