feat: integrate new certificate endpoint from backend
- Updated certificate page to use useCertificatePublicData hook
- Removed old multi-API-call approach
- Now uses single /certificates/{userId} endpoint
- Simplified data fetching for user, team, submission data
- Certificate now works for unauthenticated users
- Removed duplicate certificate hook file
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
afaaff4a7f
commit
f5882e3cdc
@@ -3,10 +3,7 @@ import { useParams, useNavigate } from 'react-router';
|
|||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { decodeCertificateId } from '../../../utils/certificate';
|
import { decodeCertificateId } from '../../../utils/certificate';
|
||||||
import {
|
import {
|
||||||
useTeamById,
|
useCertificatePublicData,
|
||||||
useTeamSubmission,
|
|
||||||
useAuthStore,
|
|
||||||
useUserById,
|
|
||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
@@ -20,7 +17,6 @@ interface DecodedCert {
|
|||||||
const CertificatePage: FC = (): ReactElement => {
|
const CertificatePage: FC = (): ReactElement => {
|
||||||
const { certId } = useParams<{ certId: string }>();
|
const { certId } = useParams<{ certId: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { session } = useAuthStore();
|
|
||||||
const [decodedInfo, setDecodedInfo] = useState<DecodedCert | null>(null);
|
const [decodedInfo, setDecodedInfo] = useState<DecodedCert | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const teamNameRef = useRef<HTMLHeadingElement>(null);
|
const teamNameRef = useRef<HTMLHeadingElement>(null);
|
||||||
@@ -43,6 +39,12 @@ const CertificatePage: FC = (): ReactElement => {
|
|||||||
}
|
}
|
||||||
}, [certId]);
|
}, [certId]);
|
||||||
|
|
||||||
|
// Fetch certificate data using the new endpoint
|
||||||
|
const { data: certificateData, isLoading: isLoadingCertificate } = useCertificatePublicData(
|
||||||
|
decodedInfo?.userId || '',
|
||||||
|
!!decodedInfo?.userId
|
||||||
|
);
|
||||||
|
|
||||||
// Generate QR Code
|
// Generate QR Code
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (certId) {
|
if (certId) {
|
||||||
@@ -62,28 +64,15 @@ const CertificatePage: FC = (): ReactElement => {
|
|||||||
}
|
}
|
||||||
}, [certId]);
|
}, [certId]);
|
||||||
|
|
||||||
const { data: teamData, isLoading: isLoadingTeam } = useTeamById(
|
const certificate = certificateData?.data;
|
||||||
decodedInfo?.teamId || '',
|
const team = certificate?.team;
|
||||||
!!decodedInfo?.teamId
|
const submission = certificate?.submission;
|
||||||
);
|
const certificateUser = certificate?.user;
|
||||||
const { data: submissionData, isLoading: isLoadingSubmission } =
|
|
||||||
useTeamSubmission(decodedInfo?.teamId || '', !!decodedInfo?.teamId);
|
|
||||||
|
|
||||||
// Fetch the certificate owner's user data
|
const isLoading = (!decodedInfo && !error) || isLoadingCertificate;
|
||||||
const { data: certificateUserData, isLoading: isLoadingCertUser } = useUserById(
|
|
||||||
decodedInfo?.userId || '',
|
|
||||||
!!decodedInfo?.userId
|
|
||||||
);
|
|
||||||
|
|
||||||
const team = teamData?.data;
|
// Certificate name from the user data
|
||||||
const submission = submissionData?.data;
|
const certificateName = certificateUser?.fullname;
|
||||||
const certificateUser = certificateUserData?.data;
|
|
||||||
|
|
||||||
const isLoading =
|
|
||||||
(!decodedInfo && !error) || isLoadingTeam || isLoadingSubmission || isLoadingCertUser;
|
|
||||||
|
|
||||||
// Certificate name: Use the user from the certificate ID, fallback to team leader
|
|
||||||
const certificateName = certificateUser?.fullname || team?.leader?.fullname;
|
|
||||||
|
|
||||||
// Dynamic font sizing: shrink by 2px if height exceeds 80px
|
// Dynamic font sizing: shrink by 2px if height exceeds 80px
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -216,7 +205,7 @@ const CertificatePage: FC = (): ReactElement => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!submission || submission.id !== decodedInfo?.submissionId) {
|
if (!certificateUser) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-950">
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-950">
|
||||||
<div className="text-6xl mb-4">📄</div>
|
<div className="text-6xl mb-4">📄</div>
|
||||||
@@ -224,7 +213,7 @@ const CertificatePage: FC = (): ReactElement => {
|
|||||||
Certificate Not Found
|
Certificate Not Found
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||||
The submission associated with this certificate could not be found.
|
The user associated with this certificate could not be found.
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={() => navigate('/')}>Back to Home</Button>
|
<Button onClick={() => navigate('/')}>Back to Home</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -292,14 +281,16 @@ const CertificatePage: FC = (): ReactElement => {
|
|||||||
{team?.name}
|
{team?.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{team && (
|
||||||
variant="secondary"
|
<Button
|
||||||
onClick={() =>
|
variant="secondary"
|
||||||
navigate(`/teams/${decodedInfo?.teamId}/submission`)
|
onClick={() =>
|
||||||
}
|
navigate(`/teams/${team.id}/submission`)
|
||||||
>
|
}
|
||||||
Back to Submission
|
>
|
||||||
</Button>
|
Back to Submission
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -445,16 +436,14 @@ const CertificatePage: FC = (): ReactElement => {
|
|||||||
margin: 0,
|
margin: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{submission.submitted_at
|
{new Date().toLocaleDateString(
|
||||||
? new Date(submission.submitted_at).toLocaleDateString(
|
'id-ID',
|
||||||
'id-ID',
|
{
|
||||||
{
|
day: 'numeric',
|
||||||
day: 'numeric',
|
month: 'long',
|
||||||
month: 'long',
|
year: 'numeric',
|
||||||
year: 'numeric',
|
}
|
||||||
}
|
)}
|
||||||
)
|
|
||||||
: 'N/A'}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -500,15 +489,17 @@ const CertificatePage: FC = (): ReactElement => {
|
|||||||
>
|
>
|
||||||
{isGenerating ? '⏳ Generating...' : '🖨️ Print'}
|
{isGenerating ? '⏳ Generating...' : '🖨️ Print'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{team && (
|
||||||
onClick={() =>
|
<Button
|
||||||
navigate(`/teams/${decodedInfo?.teamId}/submission`)
|
onClick={() =>
|
||||||
}
|
navigate(`/teams/${team.id}/submission`)
|
||||||
variant="secondary"
|
}
|
||||||
className="col-span-2 flex items-center gap-2 xl:col-span-1"
|
variant="secondary"
|
||||||
>
|
className="col-span-2 flex items-center gap-2 xl:col-span-1"
|
||||||
View Submission
|
>
|
||||||
</Button>
|
View Submission
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user