For local development, copy .env.example to .env.local and set DISCORD_WEBHOOK_URL to a newly generated Discord webhook URL. For Docker deployment, create .env.discord with the same variable. Do not commit either file or expose the webhook URL in client-side code.
Docker Compose loads .env.discord into the portfolio-os container automatically when you run docker compose up --build.
Contact submissions are rate-limited server-side to 3 accepted messages per client IP every 10 minutes. The Discord webhook URL is never sent to the browser.
If a webhook is ever exposed, revoke it immediately and follow the history-cleanup steps in SECURITY.md. A new environment variable alone does not invalidate a previously leaked webhook.
I wanted to build something more interactive than a traditional scrolling portfolio.
So I built a browser-based desktop environment where projects, certifications, media, and personal information live inside an interactive OS-style interface.
The interesting part isn't just the UI — it's the architecture behind it.
- Interact with a desktop-style environment
- Open folders and applications
- Move, minimize, maximize, and restore windows
- Use a persistent sticky note
- Create and manage files within the browser
- View images and videos
- Open certifications through a custom PDF viewer
- Send messages through the contact application
- Interact with the trash and file system
- Persist application state across sessions
The application is structured around a small set of clearly separated layers.
┌─────────────────────┐
│ app/page.tsx │
│ App Shell │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Desktop Engine │
│ components/Desktop │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Global State │
│ context/App.tsx │
└──────┬───────┬──────┘
│ │
┌──────────────┘ └──────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Window System │ │ Persistence │
│ components/* │ │ localStorage │
│ │ │ IndexedDB │
└────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ API Layer │
│ app/api/* │
└────────┬────────┘
│
▼
┌─────────────────┐
│ External │
│ Integrations │
│ Discord Webhook │
└─────────────────┘
app/page.tsx
The entry point of the application.
Responsible for mounting the desktop environment, window layer, and taskbar.
context/App.tsx
Acts as the shared state layer for the desktop.
It manages things such as:
- Open windows
- Window state
- Desktop data
- User preferences
- File and folder state
- Persistence
The goal is to keep state that needs to be shared out of individual UI components.
components/Desktop/
Responsible for the behavior of the desktop itself.
This includes:
- Desktop icons
- Drag and drop
- Launching applications
- File interactions
- Desktop layout
- Window interactions
components/*Window
Each application is isolated into its own window component.
Examples include:
- Code Editor
- File Explorer
- Media Viewer
- PDF Viewer
- Sticky Notes
- Trash
This keeps individual application logic separate while allowing the global window manager to control their lifecycle.
app/api/*
Server-side routes handle functionality that should not run directly in the browser.
For example:
Contact Form
│
▼
/api/contact
│
▼
Discord Webhook
This keeps the webhook integration away from the client-side application.
One of the design decisions I focused on was choosing the right storage mechanism for different types of data.
Used for lightweight persistent data:
- UI preferences
- Wallpaper state
- Folder metadata
- Document information
- Sticky notes
Used for heavier browser-side data:
- Images
- Videos
- Uploaded media blobs
The idea is simple:
Lightweight data → localStorage
Heavy media → IndexedDB
This avoids putting large media objects into localStorage while keeping frequently accessed application state simple.
The desktop uses a shared window management system rather than treating each application as an isolated page.
The window manager handles:
- Opening windows
- Closing windows
- Minimize / restore
- Maximize
- Active window tracking
- Z-index / focus management
- Taskbar state
- Window positioning
Conceptually:
Window Manager
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Code Editor File Explorer Media Viewer
│ │ │
└──────────────┼──────────────┘
▼
Shared State
This allows multiple applications to behave consistently without duplicating window-management logic.
Interactive desktop functionality runs on the client while server-side integrations are handled through Next.js API routes.
Individual applications are separated into their own components instead of placing the entire desktop inside one large component.
State that affects multiple parts of the application is managed centrally rather than being duplicated across components.
Different browser storage technologies are used depending on the size and purpose of the data.
New applications can be added as independent window components without having to redesign the entire desktop.
.
├── app/
│ ├── api/
│ │ ├── contact/
│ │ └── certifications/
│ │
│ └── page.tsx
│
├── components/
│ ├── Desktop/
│ ├── *Window/
│ └── ...
│
├── context/
│ └── App.tsx
│
├── lib/
│ └── certifications.ts
│
└── public/
The structure keeps application logic, shared state, server-side routes, and UI surfaces separated.
Clone the repository:
git clone https://github.com/Sahal054/MyWebiste.git
cd MyWebisteInstall dependencies:
npm installStart the development server:
npm run devOpen:
http://localhost:3000
npm run build
npm run startBefore deploying, configure:
Set the Discord webhook through an environment variable rather than committing it to the repository.
Update:
lib/certifications.ts
with the certification records you want to display.
I'm Sahal, a Software Engineer interested in backend systems, APIs, automation, and building reliable software.
I enjoy learning by building things — especially projects where I have to figure out the architecture rather than simply follow a tutorial.
📧 Email: sahalmsachu@gmail.com
💼 LinkedIn: https://linkedin.com/in/Sahal054
💻 GitHub: https://github.com/Sahal054