| Route | Purpose |
|---|
/blog | Blog list (user’s blogs) |
/blog/new | Create new blog |
/blog/[slug] | View blog |
/blog/[slug]/edit | Edit blog |
/hubs/[hubId]/blogs | Hub-scoped blog management |
| Route | Purpose |
|---|
/docs | Document grid (personal + hub docs) |
/docs/[id] | Document editor |
/hubs/[hubId]/docs | Hub-scoped document management |
| Field | Type | Required | Validation |
|---|
title | text | Yes | 1-200 chars |
slug | auto/text | Yes | 1-100 chars, [a-z0-9-] only, unique per hub |
content | EditorJS | No | Rich text blocks |
status | select | Yes | draft / published / archived |
visibility | select | Yes | public / private / unlisted |
seoKey | text | No | Max 100 chars (focus keyword) |
seoTitle | text | No | Max 70 chars |
seoDescription | textarea | No | Max 160 chars |
- Lowercase title
- Remove special characters (keep
[a-z0-9\s-])
- Replace spaces with hyphens
- Collapse multiple hyphens
- Remove leading/trailing hyphens
- Limit to 100 characters
Uniqueness: Checked with 500ms debounce against Firestore (same hub, excluding current blog).
| Tool | Description |
|---|
| Header | Heading levels |
| Paragraph | Text blocks |
| List | Ordered/unordered |
| Quote | Block quotes with captions |
| Code | Code blocks |
| Warning | Alert/warning blocks |
| Delimiter | Horizontal separators |
| Table | Data tables |
| Marker | Text highlighting (CMD+SHIFT+M) |
| Link | URL linking |
| Checklist | Checkbox lists |
| Image | Image upload with Firebase Storage |
| Setting | Value |
|---|
| Accepted formats | JPEG, PNG, GIF, WebP |
| Storage path | hubs/{hubId}/blogs/{blogSlug}/{fileId}.{ext} |
| Storage limit | Checked against subscription plan |
| Tracking | Creates StorageUserService entry |
Content stored as JSON string in Firestore (avoids nested array limitations). Parsed back via parseBlogContent() on retrieval.
- Read-only rendering with
BlogPreview component
- Article layout with author bar: avatar, name, created/updated dates, read time
- Read time calculation:
Math.ceil(totalWords / 200), minimum 1 minute
- Table: Title, Slug, Status, Visibility, Created Date, Actions
- Status badges: draft (yellow), published (green), archived (gray)
- Actions: View, Edit, Delete (with confirmation)
- GET endpoint proxying
https://mindhyv.com/wp-json/wp/v2/posts
- Optional
lang query parameter
- Returns WordPress posts for dashboard “What’s New” section
document, list, board, calendar
| Field | Type | Required |
|---|
| Title/Name | text | Yes (non-empty) |
| Description | textarea | No |
| Type | select | Auto (document) |
| Field | Type | Required |
|---|
| Name | text | Yes (non-empty) |
- Metadata: Firestore (
documents/{docId} or hubs/{hubId}/documents/{docId})
- Content: Firebase Realtime Database at
documents/{docId}
- Same EditorJS tools as blogs (minus image upload)
- Search personal
documents/{docId} in Firestore
- Connect to RTDB at
documents/{docId}
- If not found, search through all user’s hubs
- Try
hubs/{hubId}/documents/{docId} in Firestore
Soft delete: Sets status: 'deleted'. Records remain in Firestore, filtered from queries.
- Personal section: Docs from root
documents collection
- Hub sections: One per hub, docs from
hubs/{hubId}/documents
- Responsive grid: 2 cols (mobile) to 6 cols (xl)
- Cards show: type icon (color-coded), title, created date, status badge
| Type | Color |
|---|
| Document | Blue |
| List | Green |
| Board | Purple |
| Calendar | Orange |
| Blocker | Module | Description |
|---|
| Slug taken | Blogs | Must be unique per hub |
| Storage limit | Blogs | Image upload blocked if exceeded |
| Empty title | Both | Cannot save without title |
| Status deleted | Docs | Soft-deleted docs hidden from queries |