event qr code updated
Some checks failed
CI / Brand-leak check (push) Has been cancelled
CI / flake8 / pylint-odoo / manifest completeness (push) Has been cancelled
CI / Install all modules with --test-enable (push) Has been cancelled

This commit is contained in:
Selvi 2026-10-05 22:12:08 +05:30
parent 23b3075a3b
commit b04e39b202
8 changed files with 498 additions and 92 deletions

View File

@ -3,13 +3,13 @@
<record id="paperformat_membership_card" model="report.paperformat"> <record id="paperformat_membership_card" model="report.paperformat">
<field name="name">Membership Card (85x54mm)</field> <field name="name">Membership Card (85x54mm)</field>
<field name="format">custom</field> <field name="format">custom</field>
<field name="page_width">85</field> <field name="page_width">86</field>
<field name="page_height">54</field> <field name="page_height">54</field>
<field name="orientation">Landscape</field> <field name="orientation">Portrait</field>
<field name="margin_top">2</field> <field name="margin_top">0</field>
<field name="margin_bottom">2</field> <field name="margin_bottom">0</field>
<field name="margin_left">2</field> <field name="margin_left">0</field>
<field name="margin_right">2</field> <field name="margin_right">0</field>
<field name="disable_shrinking" eval="True"/> <field name="disable_shrinking" eval="True"/>
<field name="dpi">96</field> <field name="dpi">96</field>
</record> </record>
@ -29,37 +29,141 @@
<template id="report_membership_card"> <template id="report_membership_card">
<t t-call="web.basic_layout"> <t t-call="web.basic_layout">
<t t-foreach="docs" t-as="partner"> <t t-foreach="docs" t-as="partner">
<div style="width: 100%; height: 100%; padding: 6pt; box-sizing: border-box; font-family: monospace; <style>
border: 1pt solid #cccccc; border-radius: 6pt;"> html, body {
<table style="width: 100%; height: 100%; border-collapse: collapse;"> margin: 0 !important;
<tr> padding: 0 !important;
<td style="width: 65%; vertical-align: top;"> width: 100% !important;
height: 100% !important;
background: #ffffff !important;
font-family: Arial, Helvetica, sans-serif;
}
header, footer, .o_report_layout_standard,
#wrapwrap, main, .container, .container-fluid, .page {
margin: 0 !important;
padding: 0 !important;
min-height: 0 !important;
background: transparent !important;
}
</style>
<!-- Full-bleed outer page wrapper -->
<div style="width: 100%; height: 100%; margin: 0; padding: 2mm; box-sizing: border-box; background: #ffffff;">
<!-- Card outer container with borders on all 4 sides -->
<div style="
width: 100%;
height: 100%;
box-sizing: border-box;
margin: 0;
padding: 2pt;
background-color: #ffffff;
border: 2pt solid #0d3d2a;
border-radius: 6pt;
">
<!-- Card inner decorative gold border on all 4 sides -->
<div style="
width: 100%;
height: 100%;
box-sizing: border-box;
border: 0.75pt solid #c9a227;
border-radius: 4pt;
padding: 6pt 8pt;
text-align: center;
">
<!-- 1. Center top: Logo -->
<t t-if="partner.company_id.logo"> <t t-if="partner.company_id.logo">
<img t-att-src="image_data_uri(partner.company_id.logo)" style="max-height: 26pt; max-width: 90%;" alt="Logo"/> <div style="text-align: center; margin-bottom: 4pt;">
</t> <img t-att-src="image_data_uri(partner.company_id.logo)"
<div style="font-family: Arial, sans-serif; font-size: 9pt; font-weight: bold; margin-top: 4pt;"> style="max-height: 20pt; max-width: 70pt; display: inline-block;"
<span t-out="partner.membership_org_name or partner.company_id.name or ''"/> alt="Logo"/>
</div> </div>
<div style="font-family: Arial, sans-serif; font-size: 11pt; font-weight: bold; margin-top: 6pt;"> </t>
<!-- 2. Center top: Organization Name -->
<div style="
font-size: 7pt;
font-weight: bold;
color: #0d3d2a;
text-transform: uppercase;
letter-spacing: 0.4pt;
line-height: 1.2;
text-align: center;
margin-bottom: 4pt;
">
<span t-out="partner.membership_org_name or partner.company_id.name or 'TAMIL NADU CULTURAL SOCIETY OF CANADA'"/>
</div>
<!-- Divider line -->
<div style="
width: 50mm;
height: 0.75pt;
background-color: #c9a227;
margin: 4pt auto 6pt auto;
"/>
<!-- 3. Under that: Member Name -->
<div style="
font-size: 12pt;
font-weight: bold;
color: #111827;
line-height: 1.2;
text-align: center;
margin-bottom: 5pt;
">
<span t-out="partner.name"/> <span t-out="partner.name"/>
</div> </div>
<div style="font-size: 9pt; margin-top: 2pt;">
<span t-out="partner.membership_member_id or ''"/> <!-- 4. Under that: Membership Tier Name -->
<div style="
font-size: 8pt;
font-weight: bold;
color: #b48316;
text-transform: uppercase;
letter-spacing: 0.6pt;
line-height: 1.2;
text-align: center;
margin-bottom: 5pt;
">
<span t-out="partner.membership_tier_id.name or 'MEMBER'"/>
</div> </div>
<div style="font-family: Arial, sans-serif; font-size: 8pt; margin-top: 4pt;">
<span t-out="partner.membership_tier_id.name or ''"/> <!-- 5. Under that: Valid Date -->
<div style="
font-size: 6.5pt;
color: #4b5563;
line-height: 1.2;
text-align: center;
letter-spacing: 0.3pt;
">
<span style="font-weight: 600; color: #374151;">VALID UNTIL: </span>
<span t-out="partner.membership_expiry or 'LIFETIME'"/>
</div>
<!-- 6. Member ID (Optional) -->
<!-- <div style="
font-size: 5.5pt;
color: #374151;
font-family: 'Courier New', Courier, monospace;
letter-spacing: 0.5pt;
line-height: 1.15;
text-align: center;
margin-top: 4pt;
">
<span>ID: </span>
<span style="font-weight: bold;" t-out="partner.membership_member_id or '&#8212;'"/>
</div> -->
<!-- 7. QR Code (Scan to Verify) -->
<t t-if="partner.membership_card_qr">
<div style="text-align: center; margin-top: 5pt;">
<img t-att-src="image_data_uri(partner.membership_card_qr)"
style="width: 20pt; height: 20pt; display: inline-block;"
alt="QR"/>
</div>
</t>
</div> </div>
<div style="font-family: Arial, sans-serif; font-size: 7pt; color: #666666;">
Valid until:
<span t-out="partner.membership_expiry or 'Lifetime'"/>
</div> </div>
</td>
<td style="width: 35%; text-align: center; vertical-align: middle;">
<img t-if="partner.membership_card_qr" t-att-src="image_data_uri(partner.membership_card_qr)"
style="width: 60pt; height: 60pt;" alt="QR"/>
</td>
</tr>
</table>
</div> </div>
</t> </t>
</t> </t>

View File

@ -31,11 +31,13 @@ OS product module required.
'data': [ 'data': [
'data/ir_sequence.xml', 'data/ir_sequence.xml',
'report/event_ticket_report.xml', 'report/event_ticket_report.xml',
'views/event_registration_views.xml',
'views/checkin_templates.xml', 'views/checkin_templates.xml',
], ],
'demo': [], 'demo': [],
'assets': { 'assets': {
'web.assets_frontend': [ 'web.assets_frontend': [
'event_qr_ticketing/static/src/lib/jsQR.js',
'event_qr_ticketing/static/src/js/event_checkin.js', 'event_qr_ticketing/static/src/js/event_checkin.js',
], ],
}, },

View File

@ -44,10 +44,12 @@ class EventCheckinController(http.Controller):
result = {'status': status} result = {'status': status}
if registration: if registration:
names = [r.name or r.partner_id.name or 'Unknown' for r in registration]
display_name = names[0] if len(names) == 1 else f"{names[0]} + {len(names)-1} others"
result.update( result.update(
name=registration.name or registration.partner_id.name, name=display_name,
event=registration.event_id.name, event=registration[0].event_id.name,
ticket_ref=registration.ticket_ref, ticket_ref=registration[0].ticket_ref,
) )
if status == 'ok': if status == 'ok':
registration.action_check_in() registration.action_check_in()

View File

@ -19,14 +19,16 @@ class EventRegistration(models.Model):
check_in_time = fields.Datetime(string='Check-in Time', readonly=True, copy=False) check_in_time = fields.Datetime(string='Check-in Time', readonly=True, copy=False)
check_in_user_id = fields.Many2one('res.users', string='Checked In By', readonly=True, copy=False) check_in_user_id = fields.Many2one('res.users', string='Checked In By', readonly=True, copy=False)
_ticket_ref_uniq = models.Constraint('unique(ticket_ref)', 'Ticket reference must be unique.')
@api.model_create_multi @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
registrations = super().create(vals_list) registrations = super().create(vals_list)
event_ticket_refs = {}
for registration in registrations: for registration in registrations:
if not registration.ticket_ref: if not registration.ticket_ref:
registration.ticket_ref = registration._generate_ticket_ref() if registration.event_id.id not in event_ticket_refs:
event_ticket_refs[registration.event_id.id] = registration._generate_ticket_ref()
registration.ticket_ref = event_ticket_refs[registration.event_id.id]
return registrations return registrations
def _generate_ticket_ref(self): def _generate_ticket_ref(self):
@ -39,9 +41,9 @@ class EventRegistration(models.Model):
return secret.encode() return secret.encode()
def _compute_ticket_token(self): def _compute_ticket_token(self):
"""HMAC of event_id:registration_id:ticket_ref, signed with the per-database secret.""" """HMAC of event_id:ticket_ref, signed with the per-database secret."""
self.ensure_one() self.ensure_one()
payload = f"{self.event_id.id}:{self.id}:{self.ticket_ref}".encode() payload = f"{self.event_id.id}:{self.ticket_ref}".encode()
return hmac.new(self._get_signing_secret(), payload, hashlib.sha256).hexdigest()[:16] return hmac.new(self._get_signing_secret(), payload, hashlib.sha256).hexdigest()[:16]
def _get_ticket_qr_value(self): def _get_ticket_qr_value(self):
@ -65,7 +67,7 @@ class EventRegistration(models.Model):
def _verify_scanned_token(self, scanned_value): def _verify_scanned_token(self, scanned_value):
"""Verify a scanned QR payload ('ticket_ref|token'). """Verify a scanned QR payload ('ticket_ref|token').
Returns (status, registration) where status is one of: Returns (status, registrations) where status is one of:
'ok', 'already', 'invalid' (bad/forged token), 'not_found'. 'ok', 'already', 'invalid' (bad/forged token), 'not_found'.
""" """
parts = (scanned_value or '').split('|') parts = (scanned_value or '').split('|')
@ -73,36 +75,55 @@ class EventRegistration(models.Model):
return 'invalid', self.env['event.registration'] return 'invalid', self.env['event.registration']
ticket_ref, token = parts ticket_ref, token = parts
registration = self.search([('ticket_ref', '=', ticket_ref)], limit=1) registrations = self.search([('ticket_ref', '=', ticket_ref)])
if not registration: if not registrations:
return 'not_found', registration return 'not_found', registrations
expected_token = registration._compute_ticket_token() expected_token = registrations[0]._compute_ticket_token()
if not hmac.compare_digest(expected_token, token): if not hmac.compare_digest(expected_token, token):
return 'invalid', registration return 'invalid', registrations
if registration.checked_in: if all(r.checked_in for r in registrations):
return 'already', registration return 'already', registrations
return 'ok', registration return 'ok', registrations
@api.model @api.model
def _lookup_by_ticket_ref(self, ticket_ref): def _lookup_by_ticket_ref(self, ticket_ref):
"""Manual entry lookup: staff-trusted, no signature check required.""" """Manual entry lookup: staff-trusted, no signature check required."""
registration = self.search([('ticket_ref', '=', ticket_ref)], limit=1) registrations = self.search([('ticket_ref', '=', ticket_ref)])
if not registration: if not registrations:
return 'not_found', registration return 'not_found', registrations
if registration.checked_in: if all(r.checked_in for r in registrations):
return 'already', registration return 'already', registrations
return 'ok', registration return 'ok', registrations
def action_check_in(self): def action_check_in(self):
self.ensure_one() for reg in self:
self.write({ reg.write({
'checked_in': True, 'checked_in': True,
'check_in_time': fields.Datetime.now(), 'check_in_time': fields.Datetime.now(),
'check_in_user_id': self.env.user.id, 'check_in_user_id': self.env.user.id,
}) })
if self.state != 'done': if reg.state != 'done':
self.action_set_done() reg.action_set_done()
return True return True
def action_set_done(self):
"""Override to check in all registrations sharing the same ticket_ref."""
res = super().action_set_done()
for reg in self:
if reg.ticket_ref:
siblings = self.env['event.registration'].search([
('ticket_ref', '=', reg.ticket_ref),
('id', '!=', reg.id),
('state', 'not in', ['cancel', 'done']),
])
for sib in siblings:
sib.write({
'checked_in': True,
'check_in_time': fields.Datetime.now(),
'check_in_user_id': self.env.user.id,
})
sib.action_set_done()
return res

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Event Registration Form: Show Ticket Reference, Status & QR Code -->
<record id="view_event_registration_form_inherit_qr" model="ir.ui.view">
<field name="name">event.registration.form.inherit.qr</field>
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_form"/>
<field name="priority">20</field>
<field name="arch" type="xml">
<!-- Add Ticket Reference & QR Info in Event Information -->
<xpath expr="//group[@name='event']" position="inside">
<field name="ticket_ref" string="Ticket Reference" readonly="1" class="fw-bold text-primary"/>
<field name="checked_in" readonly="1"/>
<field name="check_in_time" invisible="not checked_in" readonly="1"/>
<field name="check_in_user_id" invisible="not checked_in" readonly="1"/>
</xpath>
<!-- Add Dedicated Ticket QR Tab in Notebook -->
<xpath expr="//notebook" position="inside">
<page string="Ticket &amp; QR Code" name="ticket_qr_tab">
<group>
<group string="Ticket Details">
<field name="ticket_ref" readonly="1"/>
<field name="checked_in" readonly="1"/>
<field name="check_in_time" invisible="not checked_in" readonly="1"/>
<field name="check_in_user_id" invisible="not checked_in" readonly="1"/>
</group>
<group string="QR Code (Scan for Check-in)">
<field name="ticket_qr" widget="image" class="oe_avatar" style="width: 140px; height: 140px;" readonly="1"/>
</group>
</group>
</page>
</xpath>
</field>
</record>
<!-- Event Registration List: Add Ticket Ref & Checked-in Badge -->
<record id="view_event_registration_tree_inherit_qr" model="ir.ui.view">
<field name="name">event.registration.list.inherit.qr</field>
<field name="model">event.registration</field>
<field name="inherit_id" ref="event.view_event_registration_tree"/>
<field name="priority">20</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="before">
<field name="ticket_ref" optional="show" string="Ticket Ref"/>
</xpath>
<xpath expr="//field[@name='state']" position="before">
<field name="checked_in" optional="show" widget="boolean_toggle"/>
</xpath>
</field>
</record>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

View File

@ -4741,43 +4741,260 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<t t-name="tncsc_deployment.page_membership_thank_you"> <t t-name="tncsc_deployment.page_membership_thank_you">
<t t-call="website.layout"> <t t-call="website.layout">
<div id="wrap" class="py-5" style="background-color: #f7f8fc; min-height: 80vh;"> <!-- Membership Thank You Page Styles -->
<div class="container py-4"> <style>
<div class="row justify-content-center"> .tncsc-thankyou-page {
<div class="col-md-8 col-lg-7"> min-height: 80vh;
<div class="card border-0 shadow-sm rounded-4 p-4 p-md-5 text-center bg-white"> background: linear-gradient(135deg, #f0ede6 0%, #e8e3d8 50%, #ede8df 100%);
<div class="mb-4"> display: flex;
<span class="d-inline-flex align-items-center justify-content-center rounded-circle" align-items: center;
style="width: 80px; height: 80px; background-color: #e8f5e9; color: #2e7d32; font-size: 2.5rem;"> padding: 48px 0;
<i class="fa fa-check"></i> }
</span> .tncsc-thankyou-card {
</div> position: relative;
<h2 class="fw-bold mb-2" style="color: #120735;">Application Received!</h2> background-image: url('/tncsc_deployment/static/src/img/membership-bg.png');
<h5 class="mb-3 text-muted">விண்ணப்பம் பெறப்பட்டது - நன்றி!</h5> background-size: cover;
background-position: center;
background-repeat: no-repeat;
border: none;
border-radius: 24px;
padding: 48px 44px 44px;
text-align: center;
box-shadow: 0 20px 60px rgba(18, 7, 53, 0.18), 0 4px 16px rgba(18, 7, 53, 0.10);
overflow: hidden;
}
.tncsc-thankyou-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: 24px;
border: 2px solid rgba(180, 140, 60, 0.25);
pointer-events: none;
}
/* Check icon with radiating lines */
.tncsc-check-wrap {
position: relative;
display: inline-block;
margin-bottom: 20px;
}
.tncsc-check-circle {
width: 88px;
height: 88px;
border-radius: 50%;
background: #e8f5ef;
border: 3px solid #0d5c3a;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 2.4rem;
color: #0d5c3a;
position: relative;
z-index: 2;
}
/* Title */
.tncsc-ty-title {
font-family: 'Georgia', 'Times New Roman', serif;
font-size: 2.2rem;
font-weight: 800;
color: #0d3d2a;
margin-bottom: 8px;
letter-spacing: -0.5px;
}
/* Tamil subtitle */
.tncsc-ty-tamil {
font-size: 1.3rem;
font-weight: 700;
color: #0d5c3a;
margin-bottom: 6px;
}
/* Gold lotus divider */
.tncsc-gold-divider {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin: 12px 0 20px;
}
.tncsc-gold-divider .tncsc-div-line {
height: 2px;
width: 70px;
background: linear-gradient(90deg, transparent, #c9a227, transparent);
border-radius: 2px;
}
.tncsc-gold-divider .tncsc-div-icon {
color: #c9a227;
font-size: 1.1rem;
}
/* Thank you text */
.tncsc-ty-body {
color: #2c2c2c;
font-size: 1rem;
line-height: 1.7;
margin-bottom: 24px;
}
/* What Happens Next box */
.tncsc-next-box {
background: rgba(255, 255, 255, 0.70);
border: 1.5px solid rgba(13, 92, 58, 0.18);
border-radius: 14px;
padding: 20px 24px;
text-align: left;
margin-bottom: 28px;
backdrop-filter: blur(4px);
}
.tncsc-next-box h6 {
font-size: 1rem;
font-weight: 700;
color: #0d3d2a;
margin-bottom: 14px;
}
.tncsc-next-items {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 12px;
}
.tncsc-next-item {
display: flex;
align-items: flex-start;
gap: 12px;
color: #3a3a3a;
font-size: 0.93rem;
line-height: 1.6;
}
.tncsc-next-icon {
flex-shrink: 0;
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
margin-top: 1px;
}
.tncsc-next-icon.icon-email {
background: #e6f4ea;
color: #0d5c3a;
}
.tncsc-next-icon.icon-review {
background: #fff8e1;
color: #b8860b;
}
.tncsc-next-icon.icon-card {
background: #e8eaf6;
color: #3949ab;
}
/* Buttons */
.tncsc-btn-home {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 11px 28px;
border: 2px solid #c9a227;
border-radius: 50px;
background: transparent;
color: #8a6b00;
font-weight: 600;
font-size: 0.95rem;
text-decoration: none;
transition: all 0.22s ease;
}
.tncsc-btn-home:hover {
background: #c9a227;
color: #fff;
text-decoration: none;
}
.tncsc-btn-portal {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 11px 28px;
border-radius: 50px;
background: #0d3d2a;
color: #fff;
font-weight: 600;
font-size: 0.95rem;
text-decoration: none;
transition: all 0.22s ease;
border: 2px solid #0d3d2a;
}
.tncsc-btn-portal:hover {
background: #0d5c3a;
border-color: #0d5c3a;
color: #fff;
text-decoration: none;
}
@media (max-width: 576px) {
.tncsc-thankyou-card { padding: 36px 20px 30px; }
.tncsc-ty-title { font-size: 1.6rem; }
}
</style>
<p class="text-secondary mb-4"> <div id="wrap" class="tncsc-thankyou-page">
Thank you, <b t-esc="partner.name if partner else 'Applicant'"/>! Your application for <div class="container">
<b class="text-primary"><t t-esc="tier.name if tier else 'TNCSC Membership'"/></b> <div class="row justify-content-center">
<div class="col-md-9 col-lg-7 col-xl-6">
<div class="tncsc-thankyou-card">
<!-- Check icon -->
<div class="tncsc-check-wrap">
<div class="tncsc-check-circle">
<i class="fa fa-check"></i>
</div>
</div>
<!-- Title -->
<h1 class="tncsc-ty-title">Application Received!</h1>
<!-- Tamil subtitle -->
<div class="tncsc-ty-tamil">விண்ணப்பம் பெறப்பட்டது – நன்றி!</div>
<!-- Gold lotus divider -->
<div class="tncsc-gold-divider">
<div class="tncsc-div-line"></div>
<span class="tncsc-div-icon">&#10038;</span>
<div class="tncsc-div-line"></div>
</div>
<!-- Body text -->
<p class="tncsc-ty-body">
Thank you, <strong t-esc="partner.name if partner else 'Applicant'"/>! Your application for
<strong><t t-esc="tier.name if tier else 'TNCSC Membership'"/></strong>
has been successfully submitted to the Tamil Nadu Cultural Society of Canada. has been successfully submitted to the Tamil Nadu Cultural Society of Canada.
</p> </p>
<div class="p-3 bg-light rounded-3 border text-start mb-4"> <!-- What Happens Next -->
<h6 class="fw-bold mb-2 text-dark"><i class="fa fa-info-circle text-primary me-2"></i>What Happens Next:</h6> <div class="tncsc-next-box">
<ul class="text-muted small mb-0 ps-3"> <h6><i class="fa fa-info-circle me-2" style="color:#0d5c3a;"></i>What Happens Next:</h6>
<li class="mb-1"><b>Confirmation Email:</b> An acknowledgment email has been dispatched to your registered email address (<t t-esc="partner.email if partner else 'your email'"/>).</li> <div class="tncsc-next-items">
<li class="mb-1"><b>Board Review:</b> In accordance with the TNCSC Constitution, your application is pending review and approval by the Board of Directors.</li> <div class="tncsc-next-item">
<li><b>Portal &amp; Membership Card:</b> Once approved by the Board, your membership will be officially activated. You will receive an approval email and can log in with username <code t-esc="username or 'your chosen username'"/> to view your digital membership card.</li> <div class="tncsc-next-icon icon-email"><i class="fa fa-envelope"></i></div>
</ul> <div><b>Confirmation Email:</b> An acknowledgment email has been dispatched to your registered email address (<t t-esc="partner.email if partner else 'your email'"/>).</div>
</div>
<div class="tncsc-next-item">
<div class="tncsc-next-icon icon-review"><i class="fa fa-users"></i></div>
<div><b>Board Review:</b> In accordance with the TNCSC Constitution, your application is pending review and approval by the Board of Directors.</div>
</div>
<div class="tncsc-next-item">
<div class="tncsc-next-icon icon-card"><i class="fa fa-id-card"></i></div>
<div><b>Portal &amp; Membership Card:</b> Once approved by the Board, your membership will be officially activated. You will receive an approval email and can log in with username <strong><t t-esc="username or 'your chosen username'"/></strong> to view your digital membership card.</div>
</div>
</div>
</div> </div>
<!-- Action Buttons -->
<div class="d-flex flex-wrap gap-3 justify-content-center"> <div class="d-flex flex-wrap gap-3 justify-content-center">
<a href="/" class="btn btn-outline-secondary px-4 py-2 rounded-pill"> <a href="/" class="tncsc-btn-home">
<i class="fa fa-home me-2"></i>Return to Home <i class="fa fa-home"></i> Return to Home
</a> </a>
<a href="/my" class="btn px-4 py-2 rounded-pill fw-bold text-white" style="background-color: #120735;"> <a href="/my" class="tncsc-btn-portal">
<i class="fa fa-user me-2"></i>Member Login / Portal <i class="fa fa-user"></i> Member Login / Portal &#8594;
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>