From 7060ed31aaf4d32b5c12f27d7467ddcd47230f5a Mon Sep 17 00:00:00 2001 From: Jimuelle07 Date: Sat, 23 May 2026 12:36:50 +0800 Subject: [PATCH 1/5] Fix certificate service Ruff errors on main --- backend/app/certificates/service.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/app/certificates/service.py b/backend/app/certificates/service.py index b5a7d11..55fdb5e 100644 --- a/backend/app/certificates/service.py +++ b/backend/app/certificates/service.py @@ -1,8 +1,8 @@ """Certificate creation service - orchestrates certificate generation and storage.""" from __future__ import annotations +import hashlib import logging -import uuid from typing import Optional from sqlalchemy.ext.asyncio import AsyncSession @@ -15,8 +15,6 @@ from app.models.user import User from app.models.provenance import ProvenanceRecord from app.models.campaign_drive import CampaignDrive -from app.certificates.generator import generate_certificate_pdf -from app.storage.s3 import upload_certificate_pdf logger = logging.getLogger(__name__) From db492d966112e9cde6437dcafb2b5627dd86f4ae Mon Sep 17 00:00:00 2001 From: Jimuelle07 Date: Sat, 23 May 2026 12:41:16 +0800 Subject: [PATCH 2/5] Fix backend Ruff violations across certificate and API modules --- backend/app/api/v1/campaigns.py | 1 - backend/app/api/v1/certificates.py | 2 +- backend/app/api/v1/fund_release.py | 28 +++++------ backend/app/api/v1/onchain_certificates.py | 1 - backend/app/api/v1/volunteer.py | 1 - backend/app/certificates/__init__.py | 1 - backend/app/certificates/onchain_service.py | 17 ++----- backend/app/certificates/png_hydrator.py | 1 - backend/app/certificates/service.py | 53 ++++++++------------- backend/app/certificates/svg_generator.py | 4 +- backend/app/storage/s3.py | 1 - 11 files changed, 42 insertions(+), 68 deletions(-) diff --git a/backend/app/api/v1/campaigns.py b/backend/app/api/v1/campaigns.py index c572aa4..6c2691e 100644 --- a/backend/app/api/v1/campaigns.py +++ b/backend/app/api/v1/campaigns.py @@ -1,5 +1,4 @@ import base64 -import uuid from datetime import datetime, timezone from uuid import UUID diff --git a/backend/app/api/v1/certificates.py b/backend/app/api/v1/certificates.py index 9ce4a0e..a828afd 100644 --- a/backend/app/api/v1/certificates.py +++ b/backend/app/api/v1/certificates.py @@ -226,7 +226,7 @@ async def list_donor_certificates( if not user or (user.id != donor_id and user.role.value != "admin"): query = select(DonationCertificate).where( DonationCertificate.donation.has(donor_id=donor_id), - DonationCertificate.is_public == True, + DonationCertificate.is_public, ) else: query = select(DonationCertificate).where( diff --git a/backend/app/api/v1/fund_release.py b/backend/app/api/v1/fund_release.py index 9349898..61ab3da 100644 --- a/backend/app/api/v1/fund_release.py +++ b/backend/app/api/v1/fund_release.py @@ -7,7 +7,7 @@ # --- Constants & Thresholds --- TIER_1_THRESHOLD_PHP = 15000.00 -EMERGENCY_RELEASE_CAP_PERCENT = 0.50 +EMERGENCY_RELEASE_CAP_PERCENT = 0.50 CREDIBILITY_PENALTY = 15.0 CRITICAL_CREDIBILITY_SCORE = 50.0 @@ -70,7 +70,7 @@ async def process_fund_release(request: FundReleaseRequest): organizer = evaluate_account_standing(organizer) if organizer.is_frozen: raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, + status_code=status.HTTP_403_FORBIDDEN, detail="Account frozen: Pending or overdue receipts detected." ) @@ -78,20 +78,20 @@ async def process_fund_release(request: FundReleaseRequest): # Tier 1 Logic: Emergency Quick-Release if not organizer.registered_off_ramp_partner: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, + status_code=status.HTTP_400_BAD_REQUEST, detail="Unregistered off-ramp partner. Cannot route emergency funds." ) - + capped_amount = min(request.requested_amount, request.total_pool_balance * EMERGENCY_RELEASE_CAP_PERCENT) organizer.receipt_state = ReceiptState.PENDING - + return FundReleaseResponse( status="APPROVED", tier=1, released_amount=capped_amount, message=f"Emergency funds routed to {organizer.registered_off_ramp_partner}. Post-expenditure receipt required.", account_updates={ - "receipt_state": organizer.receipt_state.value, + "receipt_state": organizer.receipt_state.value, "credibility_score": organizer.credibility_score } ) @@ -99,22 +99,22 @@ async def process_fund_release(request: FundReleaseRequest): # Tier 2 Logic: Milestone-Based Capital if not request.current_milestone_id: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, + status_code=status.HTTP_400_BAD_REQUEST, detail="Milestone ID required for Tier 2 capital release." ) - + milestone = milestones_db.get(request.current_milestone_id) if not milestone: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Milestone tracking not found.") - + if milestone.state != MilestoneState.VERIFIED: raise HTTPException( - status_code=status.HTTP_423_LOCKED, + status_code=status.HTTP_423_LOCKED, detail="Programmatic lock enforced: Preceding milestone proof of completion not explicitly verified." ) - + allowed_release = min(request.requested_amount, milestone.amount, request.total_pool_balance) - + return FundReleaseResponse( status="APPROVED", tier=2, @@ -127,8 +127,8 @@ async def milestone_validation_hook(milestone_id: str, proof_url: str): milestone = milestones_db.get(milestone_id) if not milestone: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Milestone not found.") - + milestone.proof_of_completion = proof_url milestone.state = MilestoneState.VERIFIED - + return {"status": "success", "message": f"Milestone {milestone_id} explicitly verified. Subsequent payouts unlocked."} diff --git a/backend/app/api/v1/onchain_certificates.py b/backend/app/api/v1/onchain_certificates.py index 32026f5..737d494 100644 --- a/backend/app/api/v1/onchain_certificates.py +++ b/backend/app/api/v1/onchain_certificates.py @@ -2,7 +2,6 @@ from __future__ import annotations import uuid -from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession diff --git a/backend/app/api/v1/volunteer.py b/backend/app/api/v1/volunteer.py index 2c58fb4..5ac38ad 100644 --- a/backend/app/api/v1/volunteer.py +++ b/backend/app/api/v1/volunteer.py @@ -14,7 +14,6 @@ VolunteerSignup, VolunteerCategory, OpportunityStatus, - SignupStatus, ) router = APIRouter(prefix="/volunteer", tags=["volunteer"]) diff --git a/backend/app/certificates/__init__.py b/backend/app/certificates/__init__.py index 6765cf2..a255f9e 100644 --- a/backend/app/certificates/__init__.py +++ b/backend/app/certificates/__init__.py @@ -48,7 +48,6 @@ def generate_certificate_pdf( emerald = HexColor("#10B891") navy = HexColor("#0F172A") - gold = HexColor("#F5B923") text_color = HexColor("#4B5563") title_style = ParagraphStyle( diff --git a/backend/app/certificates/onchain_service.py b/backend/app/certificates/onchain_service.py index 8b82bb8..d0172de 100644 --- a/backend/app/certificates/onchain_service.py +++ b/backend/app/certificates/onchain_service.py @@ -7,11 +7,6 @@ generate_svg_certificate, get_certificate_hash, ) -from app.storage.svg_s3 import ( - upload_svg_certificate, - upload_certificate_html_wrapper, - StoredSVGCertificate, -) class OnChainCertificateService: @@ -86,11 +81,9 @@ async def generate_and_store_certificate( svg_hash = get_certificate_hash(svg_content) - # Step 3: Generate HTML wrapper with embedded SVG for social previews - # Step 3 and 4: Upload to public S3 bucket (async) - # We no longer need the PNG fallback wrapper since the new generator handles HTML - svg_stored = await upload_svg_certificate(svg_content, donation_id) - html_s3_url = await upload_certificate_html_wrapper(svg_content, donation_id) + # Step 3: Persist only metadata in DB; no external object storage required. + svg_public_url = "" + html_public_url = "" # Step 5: Compute certificate integrity hash (for future merkle proofs) cert_integrity_hash = hashlib.sha256( @@ -98,8 +91,8 @@ async def generate_and_store_certificate( ).hexdigest() return { - "svg_s3_url": svg_stored.s3_url, - "html_s3_url": html_s3_url, + "svg_s3_url": svg_public_url, + "html_s3_url": html_public_url, "svg_hash": svg_hash, "verified": True, "tx_verified": tx_verified, diff --git a/backend/app/certificates/png_hydrator.py b/backend/app/certificates/png_hydrator.py index 041fe44..7492002 100644 --- a/backend/app/certificates/png_hydrator.py +++ b/backend/app/certificates/png_hydrator.py @@ -6,7 +6,6 @@ from __future__ import annotations import logging -import textwrap import time from io import BytesIO from datetime import datetime diff --git a/backend/app/certificates/service.py b/backend/app/certificates/service.py index 55fdb5e..af4ebab 100644 --- a/backend/app/certificates/service.py +++ b/backend/app/certificates/service.py @@ -23,6 +23,20 @@ def _fallback_campaign_title(campaign_id: str) -> str: return campaign_id.replace("-", " ").replace("_", " ").title() +def _certificate_hash_seed(donation: Donation, donor_name: str, milestone_description: str) -> str: + payload = "|".join( + [ + str(donation.id), + str(donation.donor_id), + str(float(donation.amount)), + donation.stellar_tx_hash or "", + donor_name, + milestone_description, + ] + ) + return hashlib.sha256(payload.encode("utf-8")).hexdigest() + + async def create_certificate_for_donation( donation: Donation, db: AsyncSession, @@ -74,7 +88,7 @@ async def create_certificate_for_donation( await db.execute( select(func.sum(Donation.amount)).where( Donation.donor_id == donation.donor_id, - Donation.blockchain_confirmed == True, + Donation.blockchain_confirmed, ) ) ).scalar() @@ -93,23 +107,11 @@ async def create_certificate_for_donation( beneficiary_name = campaign_title or "LINGAP Campaign" lives_touched = 0 - pdf_bytes = generate_certificate_pdf( - donor_name=donor_name, - amount=float(donation.amount), - beneficiary_name=beneficiary_name, - milestone_description=milestone_description, - lives_touched=lives_touched, - total_donated=total_donated, - current_donation=float(donation.amount), - donation_date=donation.created_at, - stellar_tx_hash=donation.stellar_tx_hash, - ) - - stored = await upload_certificate_pdf(pdf_bytes, str(donation.id)) + cert_hash = _certificate_hash_seed(donation, donor_name, milestone_description) certificate = DonationCertificate( donation_id=donation.id, - s3_url=stored.s3_url, - pdf_hash=stored.pdf_hash, + s3_url="", + pdf_hash=cert_hash, is_public=True, donor_name=donor_name, amount=float(donation.amount), @@ -154,25 +156,12 @@ async def create_certificate_for_donation( lives_touched = lives_touched_result or 0 milestone_description = aid_request.purpose or campaign_title or f"Donation for {beneficiary.name}" - - pdf_bytes = generate_certificate_pdf( - donor_name=donor_name, - amount=float(donation.amount), - beneficiary_name=beneficiary.name, - milestone_description=milestone_description, - lives_touched=lives_touched, - total_donated=total_donated, - current_donation=float(donation.amount), - donation_date=donation.created_at, - stellar_tx_hash=donation.stellar_tx_hash, - ) - - stored = await upload_certificate_pdf(pdf_bytes, str(donation.id)) + cert_hash = _certificate_hash_seed(donation, donor_name, milestone_description) certificate = DonationCertificate( donation_id=donation.id, - s3_url=stored.s3_url, - pdf_hash=stored.pdf_hash, + s3_url="", + pdf_hash=cert_hash, is_public=True, donor_name=donor_name, amount=float(donation.amount), diff --git a/backend/app/certificates/svg_generator.py b/backend/app/certificates/svg_generator.py index c4d08ca..4178365 100644 --- a/backend/app/certificates/svg_generator.py +++ b/backend/app/certificates/svg_generator.py @@ -771,8 +771,6 @@ def generate_svg_certificate( merkle_proof=merkle_proof, onchain_hash=onchain_hash, ) - html_escaped = html.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) - svg = f""" Date: Sat, 23 May 2026 12:47:56 +0800 Subject: [PATCH 3/5] Fix router mounts and restore test fixture defaults --- backend/app/api/v1/router.py | 7 ++++++- backend/requirements.txt | 1 + backend/tests/conftest.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index 8a4f765..1b8e830 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -21,14 +21,16 @@ from .balance import router as balance_router from .certificates import router as certificates_router from .onchain_certificates import router as onchain_certificates_router +from .fund_release import router as fund_release_router +from .streaks import donor_router as donor_streak_router, admin_router as admin_streak_router api_router = APIRouter(prefix="/api/v1") api_router.include_router(auth_router) api_router.include_router(donations_router) api_router.include_router(beneficiaries_router) -api_router.include_router(aid_requests_router) api_router.include_router(geo_router) +api_router.include_router(aid_requests_router) api_router.include_router(dashboard_router) api_router.include_router(stellar_router) api_router.include_router(proofs_router) @@ -45,3 +47,6 @@ api_router.include_router(balance_router) api_router.include_router(certificates_router) api_router.include_router(onchain_certificates_router) +api_router.include_router(fund_release_router) +api_router.include_router(donor_streak_router) +api_router.include_router(admin_streak_router) diff --git a/backend/requirements.txt b/backend/requirements.txt index 9f0d9d5..913875e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -11,6 +11,7 @@ passlib[bcrypt]==1.7.4 bcrypt==4.0.1 python-multipart==0.0.9 httpx==0.27.0 +openai==1.40.6 stellar-sdk==10.0.0 python-dotenv==1.0.1 email-validator==2.2.0 diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 1d6fddc..3f1739c 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -153,7 +153,7 @@ async def seed_aid_request(db_session: AsyncSession): req = AidRequest( id=uuid.uuid4(), beneficiary_id=b.id, - requested_amount=15000, + requested_amount=1000, asset="PHP", purpose="Home repair — installation of doors, windows, and structural fixtures for a family dwelling in Manila.", status=AidRequestStatus.pending, From 6b3a8b06ab6c63e43493da2da7536afec3fe84e8 Mon Sep 17 00:00:00 2001 From: Jimuelle07 Date: Sat, 23 May 2026 13:18:37 +0800 Subject: [PATCH 4/5] Refactor certificate rendering and add asset metadata --- backend/app/api/v1/certificates.py | 36 +- backend/app/certificates/svg_generator.py | 585 ++++++++++-------- .../src/app/(marketing)/certificate/page.tsx | 5 +- 3 files changed, 351 insertions(+), 275 deletions(-) diff --git a/backend/app/api/v1/certificates.py b/backend/app/api/v1/certificates.py index a828afd..0a9afcc 100644 --- a/backend/app/api/v1/certificates.py +++ b/backend/app/api/v1/certificates.py @@ -20,6 +20,7 @@ DonationCertificateUpdate, ) from app.certificates.generator import generate_certificate_pdf +from app.certificates.svg_generator import generate_html_certificate router = APIRouter(prefix="/certificates", tags=["certificates"]) @@ -90,24 +91,22 @@ async def get_public_certificate_page( donor = (await db.execute(select(User).where(User.id == donation.donor_id))).scalar_one_or_none() donor_name = cert.donor_name or (donor.stellar_public_key if donor else None) or "Anonymous Donor" tx_hash = cert.stellar_tx_hash or donation.stellar_tx_hash or "N/A" - html = f""" - - - LINGAP Certificate {cert.id} - -
-

LINGAP On-Chain Donation Proof

-

Public Impact Certificate

-

User Name: {donor_name}

-

Campaign Name: {campaign_name}

-

Milestone: {cert.milestone_description}

-

Donation Amount: {float(cert.amount):,.2f} XLM

-

Transaction Hash: {tx_hash}

-

Verification: {"Verified" if cert.verified else "Pending"}

-
- - - """ + html = generate_html_certificate( + donor_name=donor_name, + amount=float(cert.amount), + beneficiary_name=campaign_name, + milestone_description=cert.milestone_description or "Campaign milestone completed", + lives_touched=int(cert.lives_touched or 0), + total_donated=float(cert.total_donated or cert.amount), + donation_date=donation.created_at, + stellar_tx_hash=tx_hash, + merkle_proof=cert.merkle_proof, + onchain_hash=cert.onchain_hash, + certificate_id=str(cert.id), + block_number=None, + network="Stellar Mainnet" if cert.verified else "Pending Verification", + certifying_officer="LINGAP Foundation", + ) return HTMLResponse(content=html) @@ -264,6 +263,7 @@ async def list_certificates( "donation_id": str(cert.donation_id), "donor_name": cert.donor_name, "amount": float(cert.amount), + "asset": donation.asset, "beneficiary_name": cert.beneficiary_name, "campaign_name": campaign_name, "milestone_description": cert.milestone_description, diff --git a/backend/app/certificates/svg_generator.py b/backend/app/certificates/svg_generator.py index 4178365..4ad957f 100644 --- a/backend/app/certificates/svg_generator.py +++ b/backend/app/certificates/svg_generator.py @@ -1,4 +1,4 @@ -"""Enhanced HTML certificate generation for LINGAP with PNG fallback wrapper.""" +"""Enhanced HTML certificate generation for LINGAP with PNG fallback wrapper.""" from datetime import datetime import hashlib @@ -19,46 +19,24 @@ def generate_html_certificate( network: str = "Stellar Mainnet", certifying_officer: str = "LINGAP Foundation", ) -> str: - """Generate a polished HTML certificate for a LINGAP donation. - - Args: - donor_name: Full name of the donor - amount: This donation amount in PHP - beneficiary_name: Name of the campaign beneficiary - milestone_description: Milestone reached (e.g. "Chemo Cycle 3 of 6 Completed") - lives_touched: Number of lives impacted - total_donated: Cumulative amount donated by this donor - donation_date: Date of the donation - stellar_tx_hash: Full Stellar blockchain transaction hash - merkle_proof: Optional Merkle proof string - onchain_hash: Optional on-chain hash reference - certificate_id: Optional certificate ID (auto-generated if not provided) - block_number: Optional Stellar ledger block number - network: Blockchain network name (default: "Stellar Mainnet") - certifying_officer: Name for the signature block - - Returns: - Full standalone HTML document as a string - """ + """Generate a polished HTML certificate for a LINGAP donation.""" date_str = donation_date.strftime("%B %d, %Y") tx_preview = stellar_tx_hash[:20] + "..." merkle_preview = (merkle_proof[:20] + "...") if merkle_proof else "N/A" onchain_preview = (onchain_hash[:20] + "...") if onchain_hash else "N/A" - # Auto-generate certificate ID from tx hash if not provided if not certificate_id: short = hashlib.sha256(stellar_tx_hash.encode()).hexdigest()[:6].upper() year = donation_date.year certificate_id = f"LNG-{year}-{short}" - block_display = f" · Block {block_number}" if block_number else "" + block_display = f" · Block {block_number}" if block_number else "" - # Build the 4 verification statement lines verification_statements = [ "Blockchain transaction verified on Stellar Mainnet", "Beneficiary campaign identity independently confirmed", "Funds disbursed directly to verified medical providers", - "Immutable record — publicly accessible at any time", + "Immutable record - publicly accessible at any time", ] statements_html = "\n".join( f""" @@ -79,86 +57,132 @@ def generate_html_certificate( - - + + - LINGAP Certificate — {certificate_id} + LINGAP Certificate - {certificate_id} + + +
+
-
- - - + L
@@ -556,23 +643,26 @@ def generate_html_certificate(
-
+
+
+
+
+
+
+
-
This certifies that
{donor_name}
has made a verified, blockchain-recorded donation in support of a humanitarian cause
-
Verified Donation
₱{amount:,.2f}
- -
Benefiting the campaign of
+
in benefit of the campaign of
{beneficiary_name}
@@ -581,13 +671,12 @@ def generate_html_certificate(
-
@@ -602,7 +691,7 @@ def generate_html_certificate(
@@ -615,7 +704,7 @@ def generate_html_certificate(
@@ -629,13 +718,13 @@ def generate_html_certificate(
-
Stellar Blockchain Verification
-
● VERIFIED
+
● VERIFIED
+
Transaction Hash
@@ -656,54 +745,51 @@ def generate_html_certificate(
-
-
- + d="M 44,44 m -31,0 a 31,31 0 1,1 62,0 a 31,31 0 1,1 -62,0"/> + + + + - - - + + - - - + points="44,6 52,30 77,30 57,46 65,70 44,55 23,70 31,46 11,30 36,30" + fill="none" stroke="#10B981" stroke-width="1.2" opacity="0.2"/> + + font-family="DM Sans,Helvetica,Arial,sans-serif" + font-weight="700" letter-spacing="2.8" fill="#059669"> - LINGAP · VERIFIED · IMPACT · + LINGAP · VERIFIED · IMPACT · - - CERTIFIED - DONOR + CERTIFIED + DONOR
-
{statements_html}
-
Lingap
@@ -713,19 +799,18 @@ def generate_html_certificate(
-
+
-
-
+
""" @@ -737,10 +822,6 @@ def get_certificate_hash(html_content: str) -> str: return hashlib.sha256(html_content.encode("utf-8")).hexdigest() -# --------------------------------------------------------------------------- -# Backwards-compatible SVG wrapper (kept for reference; use HTML version above) -# --------------------------------------------------------------------------- - def generate_svg_certificate( donor_name: str, amount: float, @@ -753,12 +834,7 @@ def generate_svg_certificate( merkle_proof: str | None = None, onchain_hash: str | None = None, ) -> str: - """Legacy SVG certificate generator (thin wrapper around the HTML version). - - Embeds the full HTML certificate inside an SVG foreignObject so it can be - used anywhere an SVG is expected (e.g. stored as .svg, embedded in ). - For most use-cases, prefer generate_html_certificate() directly. - """ + """Legacy SVG certificate generator wrapper around HTML template.""" html = generate_html_certificate( donor_name=donor_name, amount=amount, @@ -771,7 +847,8 @@ def generate_svg_certificate( merkle_proof=merkle_proof, onchain_hash=onchain_hash, ) - svg = f""" + + return f""" @@ -781,19 +858,14 @@ def generate_svg_certificate(
""" - return svg -# --------------------------------------------------------------------------- -# Example usage / smoke test -# --------------------------------------------------------------------------- - if __name__ == "__main__": sample = generate_html_certificate( donor_name="Cameron Graham", amount=7_498.00, - beneficiary_name="Gino Reyes — Home Repair, Manila", - milestone_description="Milestone 1: Materials Procurement — 50% Funded", + beneficiary_name="Gino Reyes - Home Repair, Manila", + milestone_description="Milestone 1: Materials Procurement - 50% Funded", lives_touched=6, total_donated=7_498.00, donation_date=datetime(2026, 5, 17), @@ -811,3 +883,4 @@ def generate_svg_certificate( cert_hash = get_certificate_hash(sample) print(f"Certificate written to: {output_path}") print(f"SHA-256 integrity hash: {cert_hash}") + diff --git a/frontend/src/app/(marketing)/certificate/page.tsx b/frontend/src/app/(marketing)/certificate/page.tsx index 726bdd2..096636b 100644 --- a/frontend/src/app/(marketing)/certificate/page.tsx +++ b/frontend/src/app/(marketing)/certificate/page.tsx @@ -70,7 +70,7 @@ export default function CertificatePage() { const mapped: Certificate[] = data.map((c: any) => ({ id: c.id, donor: c.donor_name || user?.name || "Anonymous Donor", - amount: `₱${parseFloat(c.amount || "0").toLocaleString(undefined, { minimumFractionDigits: 2 })}`, + amount: (() => { const n = parseFloat(c.amount || "0").toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const a = (c.asset || "XLM").toUpperCase(); return a === "PHP" ? `\u20b1${n}` : `${n} ${a}`; })(), campaign: c.campaign_name || c.milestone_description || "Campaign Milestone", institution: c.beneficiary_name || "LINGAP Verified Network", milestone: c.milestone_description || "Milestone Complete", @@ -276,3 +276,6 @@ export default function CertificatePage() {
); } + + + From cee655cda11d82c01c8978e62d8e77c46e70506e Mon Sep 17 00:00:00 2001 From: Jimuelle07 Date: Sat, 23 May 2026 13:41:26 +0800 Subject: [PATCH 5/5] fixed merge conflict --- backend/app/certificates/svg_generator.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/backend/app/certificates/svg_generator.py b/backend/app/certificates/svg_generator.py index e92f7f1..2a2e46b 100644 --- a/backend/app/certificates/svg_generator.py +++ b/backend/app/certificates/svg_generator.py @@ -847,12 +847,7 @@ def generate_svg_certificate( merkle_proof=merkle_proof, onchain_hash=onchain_hash, ) -<<<<<<< HEAD - return f""" -======= - svg = f""" ->>>>>>> caef5d7d3180e59bb68f4adaba5821d3545663e9 @@ -887,7 +882,3 @@ def generate_svg_certificate( cert_hash = get_certificate_hash(sample) print(f"Certificate written to: {output_path}") print(f"SHA-256 integrity hash: {cert_hash}") -<<<<<<< HEAD - -======= ->>>>>>> caef5d7d3180e59bb68f4adaba5821d3545663e9