kodadot/nft-gallery

View on GitHub
components/collection/EditModal.vue

Summary

Maintainability
Test Coverage
<template>
  <NeoModal
    :value="isModalActive"
    @close="isModalActive = false"
  >
    <ModalBody
      :title="$t('edit.collection.title')"
      :scrollable="false"
      @close="isModalActive = false"
    >
      <form
        class="flex flex-col gap-6"
        @submit.prevent
      >
        <NeoField
          :label="$t('mint.collection.name.label')"
          required
          :error="!name"
        >
          <NonRecommendFieldNotification
            :show="name && nameChanged"
            @undo="name = props.collection.name"
          >
            <NeoInput
              v-model="name"
              required
              :placeholder="$t('mint.collection.name.placeholder')"
            />
          </NonRecommendFieldNotification>
        </NeoField>

        <!-- collection description -->
        <NeoField :label="$t('mint.collection.description.label')">
          <NeoInput
            v-model="description"
            type="textarea"
            has-counter
            maxlength="1000"
            height="10rem"
            :placeholder="$t('mint.collection.description.placeholder')"
          />
        </NeoField>
        <CollectionEditSection :title="$t('edit.collection.image.label')">
          <NonRecommendFieldNotification
            :show="hasImageChanged"
            @undo="initLogoImage"
          >
            <FormLogoField
              v-model:file="image"
              v-model:url="imageUrl"
              :title="$t('edit.collection.image.message')"
            />
          </NonRecommendFieldNotification>
        </CollectionEditSection>

        <CollectionEditSection :title="$t('edit.collection.banner.label')">
          <p class="text-xs !mb-4 capitalize">
            {{ $t('edit.collection.banner.message') }}
          </p>

          <div
            v-if="bannerUrl"
            class="flex flex-col gap-2"
          >
            <img
              alt="collection banner"
              :src="bannerUrl"
              class="h-[167px] border border-border-color object-cover"
            >

            <p class="text-xs text-k-grey !mt-2 capitalize">
              {{ $t('edit.collection.banner.hint') }}
            </p>

            <FormOverrideFile
              @clear="() => {
                banner = undefined
                bannerUrl = undefined
              }"
              @select="value => banner = value"
            />
          </div>

          <div
            v-else
          >
            <DropUpload
              v-model="banner"
              required
              expanded
              preview
              :label="$t('edit.collection.drop')"
            />

            <p class="text-xs text-k-grey !mt-4 capitalize">
              {{ $t('edit.collection.banner.hint') }}
            </p>
          </div>
        </CollectionEditSection>

        <!-- collection max nfts -->
        <NeoField
          :label="$t('edit.collection.max.label')"
          required
        >
          <div class="w-full">
            <div class="flex justify-between">
              <p>{{ $t('mint.unlimited') }}</p>
              <NeoSwitch
                v-model="unlimited"
                position="left"
              />
            </div>
            <NeoInput
              v-if="!unlimited"
              v-model="max"
              class="mt-3"
              type="number"
              :placeholder="`${min} is the minimum`"
              :min="min"
            />

            <div class="text-k-grey text-xs !mt-2">
              {{ $t('edit.collection.max.hint') }}
            </div>
          </div>
        </NeoField>
      </form>

      <div class="flex flex-col !mt-6">
        <NeoButton
          variant="primary"
          size="large"
          :disabled="disabled"
          no-shadow
          :label="$t('edit.collection.saveChanges')"
          @click="editCollection"
        />
      </div>
    </ModalBody>
  </NeoModal>
</template>

<script setup lang="ts">
import { NeoButton, NeoField, NeoInput, NeoModal, NeoSwitch } from '@kodadot1/brick'
import ModalBody from '@/components/shared/modals/ModalBody.vue'
import type { UpdateCollection } from '@/composables/transaction/types'

export type CollectionEditMetadata = {
  name: string
  description: string
  image: string
  imageType: string
  banner?: string
  max: number | null
}

const emit = defineEmits(['submit'])
const props = defineProps<{
  modelValue: boolean
  collection: CollectionEditMetadata
  min?: number
}>()

const isModalActive = useVModel(props, 'modelValue')

const name = ref<string>()
const description = ref<string>()
const image = ref<File>()
const banner = ref<File>()
const imageUrl = ref<string>()
const bannerUrl = ref<string>()
const unlimited = ref(true)

const min = computed(() => props.min || 1)
const max = ref<number | null>(null)

const nameChanged = computed(() => props.collection.name !== name.value)
const hasImageChanged = computed(() => (!imageUrl.value && Boolean(props.collection.image)) || Boolean(image.value))
const originalLogoImageUrl = computed(() => sanitizeIpfsUrl(props.collection.image))

const disabled = computed(() => {
  const hasImage = imageUrl.value
  const isNameFilled = Boolean(name.value)

  const descriptionChanged = props.collection.description !== description.value
  const hasBannerChanged = (!bannerUrl.value && Boolean(props.collection.banner)) || Boolean(banner.value)
  const hasMaxChanged = max.value !== props.collection.max

  return !hasImage || !isNameFilled || (!nameChanged.value && !descriptionChanged && !hasImageChanged.value && !hasBannerChanged && !hasMaxChanged)
})

const initLogoImage = () => {
  imageUrl.value = originalLogoImageUrl.value
  image.value = undefined
}

const editCollection = async () => {
  emit('submit', {
    name: name.value,
    description: description.value,
    image: image.value || props.collection.image,
    imageType: props.collection.imageType,
    banner: bannerUrl.value ? banner.value || props.collection.banner : undefined,
    max: max.value,
  } as UpdateCollection)
}

watch(isModalActive, (value) => {
  if (value && props.collection) {
    name.value = props.collection.name
    description.value = props.collection.description
    bannerUrl.value = props.collection.banner && sanitizeIpfsUrl(props.collection.banner)
    banner.value = undefined
    initLogoImage()
    unlimited.value = !props.collection.max
    max.value = props.collection.max
  }
})

watch([banner, unlimited], ([banner, unlimited]) => {
  if (banner) {
    bannerUrl.value = URL.createObjectURL(banner)
  }

  max.value = unlimited ? null : max.value || props.collection.max
})
</script>