Laravel 11 Tailwind CSS PostgreSQL 16 Multi-Tenant

Common Portal Platform

A white-label, multi-tenant portal framework built with Laravel, Tailwind CSS, and PostgreSQL.

View on GitHub Request Access

Quick Start

Prerequisites

  • Docker & Docker Compose
  • Git

Setup

# 1. Clone repository
git clone https://github.com/common-portal/platform.git
cd platform

# 2. Install Laravel + dependencies
make setup

# 3. Start containers
make up

# 4. Run migrations
make migrate

# 5. Visit app
open http://localhost:8080

Tech Stack

LayerTechnology
BackendPHP 8.3, Laravel 11, Jetstream
FrontendTailwind CSS, Alpine.js
DatabasePostgreSQL 16
CacheRedis 7

Commands

make setup     # Initial installation
make up        # Start containers
make down      # Stop containers
make migrate   # Run migrations
make fresh     # Fresh migrate + seed
make shell     # Shell into container
make logs      # View logs
make test      # Run tests

Environment

Copy .env.example to .env:

cp .env.example .env
VariableDefault
APP_URLhttp://localhost:8080
APP_BASE_DOMAINcommon-portal.nsdb.com
DB_HOSTpostgres
DB_DATABASEplatform
MAIL_MAILERsmtp
OPENAI_API_KEY(required for translator)

Project Structure

├── docker/ # Docker configuration ├── scripts/ # Setup scripts ├── src/ # Laravel application │ ├── app/ │ │ ├── Http/Controllers/ # Route controllers │ │ ├── Http/Middleware/ # Custom middleware │ │ ├── Models/ # Eloquent models │ │ ├── Providers/ # Service providers │ │ ├── Services/ # Business logic services │ │ └── Traits/ # Reusable traits │ ├── database/ │ ├── resources/views/ │ │ ├── layouts/ # Master layouts │ │ ├── components/ # Blade components │ │ └── pages/ # Page views │ └── routes/ ├── docker-compose.yml # Base services (tracked) ├── docker-compose.override.yml # Dev ports/names (gitignored) ├── docker-compose.prod.yml # Prod ports/names (gitignored) ├── docker-compose.prod.example.yml # Prod template (tracked) ├── Dockerfile └── Makefile

Page Layouts

Guest Layout (layouts/guest.blade.php)

All public/unauthenticated pages use a shared guest layout with:

SectionContent
HeaderLogo + Platform Name (gold #e3be3b), Support link, Login/Register buttons
Main FrameCentered content area with @yield('content')
FooterLanguage selector (100+ languages), "Powered by NSDB.COM", CC0 license

Public pages using this layout:

PageRouteView
Homepage/pages/homepage-guest.blade.php
Support/supportpages/support.blade.php
Login/Register/login-registerpages/login-register.blade.php
OTP Verify/verifypages/otp-verify.blade.php
Login/loginauth/login.blade.php
Register/registerauth/register.blade.php
Forgot Password/forgot-passwordauth/forgot-password.blade.php
Reset Password/reset-password/{token}auth/reset-password.blade.php
Confirm Password/user/confirm-passwordauth/confirm-password.blade.php
Verify Email/email/verifyauth/verify-email.blade.php
Two-Factor/two-factor-challengeauth/two-factor-challenge.blade.php

Platform Layout (layouts/platform.blade.php)

Authenticated pages use the platform layout with sidebar navigation.



File Uploads (Avatars & Logos)

Storage Paths

TypeStorage PathDatabase Column
Member Avatarstorage/app/public/uploads/members/icons/platform_members.profile_avatar_image_path
Account Logostorage/app/public/uploads/accounts/icons/tenant_accounts.branding_logo_image_path

Files are accessible via /storage/uploads/... after running php artisan storage:link.

Filename Format

{sanitized_original_name}_{hash_prefix}_{datetime}.{extension}
ComponentDescriptionExample
sanitized_original_nameOriginal filename (alphanumeric, -, _ only)profile-photo
hash_prefixFirst 8 chars of record_unique_identifiera1b2c3d4
datetimeUpload timestamp Ymd_His20260108_063500
extensionOriginal file extensionjpg

Example: profile-photo_a1b2c3d4_20260108_063500.jpg

Upload Constraints

  • Max file size: 2MB
  • Allowed types: JPEG, PNG, GIF, WebP (+ SVG for account logos)
  • Validation: Server-side via Laravel validation rules

Database Storage

// Member avatar
$member->update([
    'profile_avatar_image_path' => 'uploads/members/icons/filename.jpg',
]);

// Account logo
$account->update([
    'branding_logo_image_path' => 'uploads/accounts/icons/filename.png',
]);

Displaying Uploaded Images

{{-- Member Avatar --}}
@if(auth()->user()->profile_avatar_image_path)
    <img src="{{ asset('storage/' . auth()->user()->profile_avatar_image_path) }}" alt="Avatar">
@endif

{{-- Account Logo --}}
@if($account->branding_logo_image_path)
    <img src="{{ asset('storage/' . $account->branding_logo_image_path) }}" alt="Logo">
@endif

Key Concepts

ConceptSectionStatus
Data Model→ Data Model (Consolidated)✅ Implemented
Authentication→ Authentication UX (OTP-primary)✅ Implemented
Permissions→ Permissions System✅ Implemented
Branding→ Branding Hierarchy✅ Implemented
Admin Panel→ Administrator Panel✅ Implemented
Sidebar→ Sidebar Menu Structure✅ Implemented
Multi-Tenant Subdomains→ Account-Level Branding✅ Implemented
Public Page Layout→ Guest Layout (header/footer)✅ Implemented

Document Cross-References

DocumentPurpose
COMMON-PORTAL-BRAINSTORMING-WISH-LIST-003.md📋 Full requirements (source of truth)
COMMON-PORTAL-DEVELOPMENT-ROADMAP-002.mdPhase-by-phase implementation plan
COMMON-PORTAL-DATABASE-SCHEMA-002.mdPostgreSQL table definitions
COMMON-PORTAL-TRANSLATOR-CORE-CODE-001.md🔴 Translator framework (follow exactly)
COMMON-PORTAL-MAILER-CODE-002.md🔴 Mailer framework (follow exactly)

Production Deployment

# Configure managed PostgreSQL
cp .env.example .env.production
# Edit .env.production with managed DB credentials

# Deploy
make prod-up

License

MIT License — see LICENSE for details.

An NSDB Group product — © 2026 NSDB Group