Dol.

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.

12admin routes
5service landing pages
7data models

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