Skip to content
· 8 min read · 0 views

Prototype Showcase: Turn Any App Into a Live Portfolio Demo

How to clone real projects into standalone interactive prototypes with mock authentication, dummy data, and zero backend dependencies for portfolio demos.

// table of contents (17 sections)

Try it live — The Community App demo is running at abduarrahman.com/prototype/community-app and the real app is at semesagrande.web.app. Source code on GitHub.


Live Demo

Log in with any email and password — the prototype accepts all credentials and assigns a role. Explore both the Pengurus (admin) and Warga (member) views:


The Portfolio Problem

Here’s the dilemma every developer faces when building a portfolio:

  1. Your best work is proprietary — Client projects, employer codebases, internal tools. You can’t share the source, and you definitely can’t deploy it.
  2. Screenshots are dead — A gallery of static images tells someone what an app looks like, not how it works. Navigation flows, form interactions, state transitions — these are invisible in screenshots.
  3. Videos are better but passive — Screen recordings show the app in action, but the viewer can’t explore at their own pace. They’re stuck on your timeline.
  4. Side projects are a time sink — Building portfolio-only projects from scratch means less time for real work.

What if you could take the actual UI/UX from a proprietary project, strip out the backend and real data, and deploy it as a fully interactive demo? That’s the Prototype Showcase Pattern.


The Pattern

Real App → Clone → Rebrand → Mock → Deploy → Showcase
  1. Clone — Copy the frontend code into a new project
  2. Rebrand — Remove proprietary names, logos, and identifying details
  3. Mock — Replace real services (Firebase, API calls) with client-side mocks
  4. Seed — Populate with realistic dummy data
  5. Deploy — Build as a static SPA, host on any file server
  6. Showcase — Embed as iframe on portfolio, link from project page

The result is a zero-backend, fully interactive demo that anyone can click through without credentials, API keys, or setup.


Mock Firebase Deep-Dive

The most critical part of the pattern is mocking the backend services. For my community management app, the real version uses Firebase Authentication and Firestore. Here’s how I replaced them:

Auth Mocking with Observer Pattern

Firebase auth uses an observer pattern — you call onAuthStateChanged(callback) and get notified when the user changes. The mock replicates this exactly:

// mocks/firebase.js
const observers = new Set();
let currentUser = null;

export function onAuthStateChanged(callback) {
  observers.add(callback);
  callback(currentUser);
  return () => observers.delete(callback);
}

function notifyObservers(user) {
  currentUser = user;
  observers.forEach(cb => cb(user));
}

export async function signInWithEmailAndPassword(email, password) {
  // Accept ANY credentials for the prototype
  const role = determineRole(email);
  const user = { uid: generateId(), email, displayName: email.split('@')[0] };
  notifyObservers(user);
  return { user };
}

Components that use onAuthStateChanged don’t know (or care) whether they’re talking to real Firebase or the mock. This is the power of programming to an interface.

Firestore Mocking with localStorage

Real Firestore is async and document-based. The mock uses localStorage as the persistence layer:

const STORAGE_KEY = 'mock_firestore';

function getCollection(collectionName) {
  const data = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
  return data[collectionName] || [];
}

function setCollection(collectionName, documents) {
  const data = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
  data[collectionName] = documents;
  localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}

This means data survives page refreshes — the prototype feels like a real app because state persists across navigation.

Dummy Data Seeding

On first load, the prototype checks if localStorage is empty and seeds it with realistic data:

function seedData() {
  if (localStorage.getItem(STORAGE_KEY)) return; // Already seeded

  const warga = [
    { id: 'w001', nama: 'Budi Santoso', alamat: 'Jl. Melati No. 5', rt: '003', rw: '001' },
    { id: 'w002', nama: 'Siti Aminah', alamat: 'Jl. Melati No. 12', rt: '003', rw: '001' },
    { id: 'w003', nama: 'Ahmad Fauzi', alamat: 'Jl. Dahlia No. 8', rt: '004', rw: '001' },
    // ... 20+ more realistic entries
  ];

  const kas = [
    { id: 'k001', jenis: 'iuran_bulanan', jumlah: 25000, tanggal: '2026-04-01', wargaId: 'w001', status: 'lunas' },
    { id: 'k002', jenis: 'iuran_bulanan', jumlah: 25000, tanggal: '2026-04-01', wargaId: 'w002', status: 'belum_bayar' },
    // ... with various states for demo purposes
  ];

  setCollection('warga', warga);
  setCollection('kas', kas);
}

The key is realism — “Budi Santoso, Jl. Melati No. 5” is infinitely more convincing than “Test User 1, Address 1”. The data tells a story.


The Community App Example

The prototype showcases a residential community management system (Sistem Pengelolaan Rukun Tetangga) with two distinct views:

Pengurus (Admin) View

  • Dashboard with kas summary and pending items
  • Kas management — create dues, record payments, track outstanding
  • Audit workflow — submit entries for approval, approve with notes
  • Warga directory — full resident list with contact info
  • Export to Excel — download kas data as .xlsx

Warga (Member) View

  • Personal payment status — see what’s paid and what’s owed
  • Community documents — view announcements and files
  • Paguyuban info — RT/RW structure and community hierarchy

The role is determined at login time — use any email containing “pengurus” for admin access, or any other email for the member view.


Deployment: Subdirectory Hosting

The prototype lives at /prototype/community-app/ on my portfolio site, not on its own domain. This requires two configuration changes:

Vite Base Path

// vite.config.js
export default defineConfig({
  plugins: [react()],
  base: '/prototype/community-app/',
});

This makes all asset references relative to the subdirectory: <script src="/prototype/community-app/assets/index.js">.

React Router Basename

<Router basename="/prototype/community-app">
  <Routes>
    <Route path="/" element={<Tentang />} />
    <Route path="/login" element={<Login />} />
    {/* ... */}
  </Routes>
</Router>

Without basename, React Router would try to match /login instead of /prototype/community-app/login.

Nginx SPA Fallback

Single-page apps need all routes to fall back to index.html so client-side routing works:

location /prototype/ {
    alias /var/www/abduarrahmancom/dist/prototype/;
    try_files $uri $uri/ /prototype/community-app/index.html;
}

This ensures that navigating directly to /prototype/community-app/pengurus serves the SPA’s index.html instead of a 404.


What Makes a Good Prototype

Not all prototypes are created equal. Here’s what separates a live portfolio demo from a dead prototype:

FactorDead PrototypeLive Showcase
Data”Lorem ipsum” test dataRealistic, contextual data
AuthHardcoded single userMock auth accepting any credentials
NavigationOnly homepage worksAll routes functional
StateResets on every refreshPersists across navigation
Deploymentlocalhost onlyPublicly accessible URL
EmbeddingNoIframe-ready for portfolio

The goal is to make the prototype indistinguishable from a real app during a 5-minute click-through. If someone has to ask “is this broken or is it a demo?” — you’ve failed.


The /proto:clone Automation

I automated the entire clone-and-mock process into a Claude Code command: /proto:clone. Given a GitHub repository, it:

  1. Clones the repo into a new prototype project
  2. Identifies backend dependencies (Firebase, API calls, databases)
  3. Generates mock implementations matching the real interfaces
  4. Creates realistic seed data based on the app’s domain
  5. Configures Vite for subdirectory deployment
  6. Sets up React Router with proper basename

What used to take a full day of manual work now runs in about 15 minutes. The command produces a standalone project that builds to static files and deploys anywhere.


Takeaways

  1. Live demos beat everything — A recruiter who clicks through your prototype understands your work 10x better than one who reads bullet points
  2. Mock the interface, not the implementation — Components should use the same API shape as the real backend. The mock is a drop-in replacement
  3. Data realism is worth the effort — Spend time crafting good seed data. It’s the difference between “nice UI” and “wow, this feels real”
  4. Subdirectory hosting is underused — You don’t need a separate domain for every demo. /prototype/project-name/ works great
  5. Automate the pattern — Once you’ve done it manually, codify it. The /proto:clone command turns any project into a showcase in minutes

If you’re building a developer portfolio, stop screenshotting. Start prototyping.


The prototype showcases my Semesa Grande community management app — a full-stack PWA with role-based dashboards, kas payment tracking, and push notifications for residential communities. For another approach to building impressive portfolio demos, see my Event Photo Platform with OCR project.

You might also like

Enjoyed This Post?

Want to discuss the topic, have questions, or looking to collaborate on something similar? Drop a comment below or reach out directly.

Discussion