Files
gameno-front/src/views/account/MyCertificatesView.vue
T
kavehhn a9135bfa09 Initial commit: public frontend for GameNo.
Vue 3 + Vite student-facing app with routing, Pinia state, and API integration.
2026-08-09 04:18:09 +02:00

162 lines
3.8 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="certs-view">
<header class="page-head">
<h1>گواهینامهها</h1>
<p>گواهیهای صادرشده یا بارگذاریشده برای حساب شما.</p>
</header>
<div v-if="loading" class="loading-block">
<div class="spinner" aria-label="در حال بارگذاری"></div>
</div>
<div v-else-if="error" class="empty-state">
<h3>بارگذاری ناموفق بود</h3>
<p>{{ error }}</p>
<button type="button" class="btn btn-ink" @click="load">تلاش دوباره</button>
</div>
<div v-else-if="!certificates.length" class="empty-state">
<h3>گواهینامهای یافت نشد</h3>
<p>پس از صدور گواهی توسط آموزشگاه، فایل آن اینجا قابل دانلود است.</p>
</div>
<div v-else class="cert-list">
<article v-for="cert in certificates" :key="cert._id" class="cert-card">
<div class="cert-mark" aria-hidden="true">گ</div>
<div class="cert-body">
<div class="cert-top">
<h2>{{ cert.title }}</h2>
<span class="badge" :class="cert.isOfficial ? 'badge-gold' : 'badge-muted'">
{{ cert.isOfficial ? 'رسمی' : 'شخصی' }}
</span>
</div>
<p class="issuer">صادرکننده: {{ cert.issuer || 'گام نو' }}</p>
<p class="course" v-if="cert.course?.title">دوره: {{ cert.course.title }}</p>
<div class="cert-foot">
<span>{{ formatJalali(cert.issuedAt) }}</span>
<a
v-if="cert.presignedUrl"
class="btn btn-outline"
:href="cert.presignedUrl"
target="_blank"
rel="noopener noreferrer"
>
مشاهده / دانلود
</a>
</div>
</div>
</article>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { certificatesApi } from '@/api/certificatesApi'
import { formatJalali, getApiErrorMessage } from '@/utils/format'
const certificates = ref([])
const loading = ref(true)
const error = ref('')
async function load() {
loading.value = true
error.value = ''
try {
const res = await certificatesApi.getMyCertificates({ limit: 50 })
certificates.value = res.data || []
} catch (err) {
error.value = getApiErrorMessage(err)
certificates.value = []
} finally {
loading.value = false
}
}
onMounted(load)
</script>
<style scoped>
.page-head {
margin-bottom: 1.5rem;
}
.page-head h1 {
font-family: var(--font-display);
font-size: clamp(1.9rem, 3.5vw, 2.4rem);
margin-bottom: 0.3rem;
}
.page-head p {
color: var(--ink-soft);
}
.cert-list {
display: grid;
gap: 1rem;
}
.cert-card {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
padding: 1.25rem;
border-radius: 1.2rem;
background: linear-gradient(145deg, #fff9ec, var(--chalk));
border: 1px solid rgba(196, 134, 12, 0.18);
box-shadow: 0 10px 28px rgba(20, 53, 43, 0.05);
}
.cert-mark {
width: 3.2rem;
height: 3.2rem;
border-radius: 1rem;
display: grid;
place-items: center;
background: var(--ink);
color: var(--saffron);
font-family: var(--font-display);
font-size: 1.5rem;
font-weight: 700;
}
.cert-top {
display: flex;
justify-content: space-between;
gap: 0.75rem;
align-items: flex-start;
margin-bottom: 0.35rem;
}
.cert-top h2 {
font-size: 1.15rem;
}
.issuer,
.course {
color: var(--ink-soft);
font-size: 0.9rem;
}
.cert-foot {
margin-top: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.cert-foot span {
font-weight: 600;
color: var(--ink-soft);
font-size: 0.9rem;
}
@media (max-width: 560px) {
.cert-card {
grid-template-columns: 1fr;
}
}
</style>