DONE: digital card with profile display: WIP: membership condition on letter, is_first_time column in users table

This commit is contained in:
ISMAIL MASSERAN
2026-07-06 09:43:58 +08:00
parent 61345e9cf0
commit ac8f00175a
34 changed files with 1662 additions and 99 deletions
@@ -0,0 +1,104 @@
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import QRCode from 'qrcode'
import logoUrl from '@/assets/images/logo.svg'
import { displayCardValue } from '../utils/member-digital-card.utils'
const props = withDefaults(
defineProps<{
profileUrl?: string | null
memberNumber?: string | number | null
expanded?: boolean
}>(),
{ expanded: false },
)
const qrDataUrl = ref('')
const qrError = ref(false)
const qrPixelSize = computed(() => (props.expanded ? 220 : 120))
async function renderQrCode() {
if (!props.profileUrl) {
qrDataUrl.value = ''
qrError.value = false
return
}
try {
qrDataUrl.value = await QRCode.toDataURL(props.profileUrl, {
margin: 1,
width: qrPixelSize.value,
color: {
dark: '#0f172a',
light: '#ffffff',
},
})
qrError.value = false
} catch {
qrDataUrl.value = ''
qrError.value = true
}
}
watch([() => props.profileUrl, () => props.expanded], renderQrCode, { immediate: true })
</script>
<template>
<div :class="[
'relative h-full w-full overflow-hidden rounded-2xl bg-linear-to-br from-primary/90 via-primary to-primary/80 text-primary-foreground shadow-lg ring-1 ring-white/20',
expanded ? 'p-5 sm:p-6' : 'p-3 sm:p-4',
]">
<div class="pointer-events-none absolute inset-0 bg-noise opacity-30" />
<div class="pointer-events-none absolute -right-12 -top-12 rounded-full bg-white/10"
:class="expanded ? 'size-44' : 'size-32'" />
<div class="pointer-events-none absolute -bottom-16 -left-10 rounded-full bg-white/5"
:class="expanded ? 'size-48' : 'size-36'" />
<div class="relative flex h-full min-h-0 flex-col">
<div class="flex shrink-0 items-center justify-between gap-2">
<img :src="logoUrl" alt="" class="w-auto shrink-0 brightness-0 invert"
:class="expanded ? 'h-7 sm:h-9' : 'h-5 sm:h-6'" />
<div class="text-right font-semibold uppercase opacity-75"
:class="expanded ? 'text-sm tracking-[0.18em]' : 'text-[9px] tracking-[0.18em]'">
Belakang · Kod QR
</div>
</div>
<div class="flex min-h-0 flex-1 items-center" :class="expanded ? 'mt-5 gap-6' : 'mt-3 gap-3'">
<div class="shrink-0 rounded-lg bg-white shadow-sm" :class="expanded ? 'p-3' : 'p-1.5'">
<img v-if="qrDataUrl" :src="qrDataUrl" alt="Kod QR profil anggota" class="block"
:class="expanded ? 'size-32 sm:size-36' : 'size-18 sm:size-20'" />
<div v-else class="flex items-center justify-center"
:class="expanded ? 'size-32 sm:size-36' : 'size-18 sm:size-20'">
<span class="px-1 text-center leading-tight text-slate-500" :class="expanded ? 'text-sm' : 'text-[9px]'">
{{ qrError ? 'Kod QR tidak tersedia.' : 'Memuatkan...' }}
</span>
</div>
</div>
<div class="flex min-w-0 flex-1 flex-col justify-center" :class="expanded ? 'gap-5' : 'gap-3'">
<p class="leading-snug opacity-85" :class="expanded ? 'text-base sm:text-lg' : 'text-[9px] sm:text-[10px]'">
Imbas untuk sahkan profil anggota MyKOPKB.
</p>
<div>
<div class="font-medium uppercase tracking-widest opacity-60" :class="expanded ? 'text-sm' : 'text-[9px]'">
No. Anggota
</div>
<div class="mt-0.5 font-mono font-semibold tracking-widest"
:class="expanded ? 'text-3xl sm:text-4xl' : 'text-base sm:text-lg'">
{{ displayCardValue(memberNumber) }}
</div>
</div>
</div>
</div>
<div class="shrink-0 border-t border-white/15 text-center" :class="expanded ? 'mt-4 pt-3' : 'mt-2 pt-2'">
<p class="uppercase opacity-50" :class="expanded ? 'text-xs tracking-[0.2em]' : 'text-[8px] tracking-[0.2em]'">
Koperasi Permodalan Kelantan Berhad (KOPKB)
</p>
</div>
</div>
</div>
</template>