Puncak Nilam (PNMS)
- Company
- Puncak Nilam Management Services Sdn. Bhd., a governance, risk and compliance consultancy
- My role
- Design, build and maintenance: the redesigned website and the entire admin system
- Status
- Live, maintained by me
- Stack
- Blazor Web App (.NET 8) · MariaDB/MySQL · EF Core · Cloudflare Workers
Puncak Nilam Management Services (PNMS) is a governance, risk and compliance consultancy established in 2016. I redesigned its website from the previous PHP site, rebuilt it in Blazor, and built the whole admin system behind it, so staff manage services, articles, downloads and enquiries themselves. I design, build and maintain all of it.
The public website
I redesigned the site from the previous PHP version. Content pages render on the server for speed and search, and the pages with forms and filters are interactive.
Layout and navigation
A more modern shell around every page.
- Sticky header that changes on scroll, with a services mega-menu linking straight to every service and landing page
- Mobile hamburger menu that closes on Escape or an outside tap, and resets itself when the window is resized
- Scroll-triggered reveal animations and responsive layouts for phones and tablets
- Home, About, Services, ERM Software, Resources, Insights and Contact pages
Services and landing pages
Where an enquiry usually starts.
- One Services page built from the database: each service has a label, title, description, icon, image and checklist, and its order sets the alternating light and dark layout
- 11 service lines, from starting a company and ISO 31000 risk management to anti-bribery, business continuity, data and AI, and Halal compliance support
- Dedicated landing pages for ISO 27001, PDPA, Cyber Security and ISMS, Assurance and Internal Audit, and Cybersecurity Audit, each with an embedded enquiry form
- An ERM software page with a demo request
Insights
An articles section that feeds the rest of the site.
- Article list with a category filter (ISO and Certification, Compliance, Risk, Cyber, Anti-Bribery) and article pages with a Keep Reading section
- Topic tags that surface relevant articles on the matching service pages
- Article schema markup plus canonical and Open Graph tags on every article
Resources
Documents and downloads.
- Brochures and case studies viewable in the page: PDFs inline, PowerPoint through the Office viewer
- Category filter, featured items pinned to the top, and a tracked download link that counts real downloads rather than page views
Enquiries
One pipeline behind every form.
- The contact page and the landing-page forms share the same pipeline, with the service pre-selected from the link the visitor came from
- Inline validation with error states, a sending state and a honeypot field
- Every enquiry is stored with the visitor's country and referrer and whether the team email went out, then emailed to the team, with an auto-reply to the enquirer
Search and sharing
How the site shows up outside itself.
- Generated sitemap.xml with last-modified dates for articles, falling back to the static pages if the database is down
- robots.txt that keeps the admin, login and download endpoints out of search
- ProfessionalService schema markup on the home page, plus canonical and Open Graph tags on the landing pages
The admin system
I built all of it: 12 routes, each behind its own sign-in guard. A sidebar badge counts unread enquiries, and every change is written to an audit log.
Dashboard
What needs attention at a glance.
- Stat cards for services (live and hidden), resource documents, enquiries with the unread count, and when content was last updated
- Enquiries-per-week chart and a Top Requested Services breakdown
- A Recent Admin Activity feed
Enquiries
From first message to closed.
- Search, plus filters for status and for the service the enquiry was about
- New, Contacted and Closed workflow, with read and unread states
- Detail drawer with reply-by-email and tap-to-call links, and private team notes that are never sent to the enquirer
- Captured visitor details: IP and country, hostname, browser and referrer
- Email delivery status with the failure reason and a Retry Send button, so a bounced notification never loses an enquiry
- Delete with confirmation, and export to CSV
Services
The catalogue behind the public page.
- Add, edit and delete services with an anchor (cleaned automatically), label, title, description, icon, image and checklist
- Move services up and down to change the page order, and switch each between live and hidden
- A preview link that opens the service on the live page
- I moved the 11 services out of a hardcoded page into the database with a one-time seed
Insights
A small publishing tool.
- Article editor with a slug (cleaned automatically), category, excerpt, HTML body, image and topics
- SEO title and description, publish date, and a Live or Draft switch with a link to view the live article
Resources
Files with a stable public link.
- Upload PDF or PowerPoint files up to 25 MB, with a category, description and a featured pin
- Replace a file and keep the same public link, title and category
- Download count for every document
Admin users
Who can sign in.
- Add admins, edit their email, and set another admin's password
- Disable or re-enable an account
Settings
Contact details without a redeploy.
- Notification email, phone and address are edited here instead of living in code
- SMTP settings shown read-only, with a Send Test Email button to confirm they work
Audit log
Who changed what.
- Search, plus filters for area (logins, logouts, services, insights, resources, enquiries, admin users, account, settings) and date range
- Every entry records the user, action, record, a plain-language summary and the IP address
Patterns every page shares
So the admin feels like one product.
- Slide-over drawers and confirmation dialogs that move focus inside when they open and close on Escape
- A “Still there?” warning before an idle session is signed out
- A My Account page for changing your own password
Under the hood
The engineering that does not show up on a screenshot.
Security
Sign-in, sessions and password resets.
- Passwords hashed with BCrypt, and a lockout after five failed attempts, tracked per IP and username
- Sessions that expire, plus an inactivity timeout with a sliding window driven by an activity tracker in the browser
- Self-service forgot-password: single-use links that expire after 30 minutes, with only a hash stored, the same response whether or not the account exists, and rate limiting per hour
- Every admin page checks sign-in before it loads any data
Behind two Cloudflare hops
Getting the real visitor.
- The public domain is a Cloudflare Worker proxy in front of the real app, and Cloudflare overwrites its own IP and country headers on the second hop
- The Worker forwards the original values as separate headers, which the app reads first, with a reverse-DNS lookup that times out after three seconds
Resilience
Degrading instead of failing.
- Startup seeding is wrapped so an unreachable database cannot stop the site from starting
- The Services and Insights pages show an empty state, and the sitemap falls back to static pages, when the database is down
Data and email
The plumbing.
- EF Core on MariaDB/MySQL with migrations across seven tables, and an in-memory cache for services
- The document catalogue lives in a JSON manifest with an in-memory cache and a write lock, so it needs no table
- SignalR message limit raised to 30 MB so 25 MB uploads work on the Blazor circuit
- Table-based HTML emails with inline styles and an inline logo, built to survive email clients that strip CSS: enquiry notification, customer auto-reply, password reset and a test email