readme
This commit is contained in:
296
README.md
296
README.md
@@ -6,56 +6,76 @@ A React single-page application that recreates [profeneng.com](https://profeneng
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── main.jsx Entry point — imports all CSS, mounts React
|
├── main.jsx Entry — imports CSS (variables → kit → theme → layout → pages → services → dynamic), mounts React
|
||||||
├── App.jsx Router setup (BrowserRouter, Routes, lazy pages)
|
├── App.jsx Router (BrowserRouter, Suspense, ErrorBoundary, lazy-loaded pages, 404 route)
|
||||||
|
├── config.js Central config: WhatsApp phone, brand colors, PAGE_IDS, SPA_ROUTES, POST_ID_TO_SLUG, SERVICE_SIDEBAR_ITEMS, MENU_ITEM_IDS
|
||||||
├── components/
|
├── components/
|
||||||
│ ├── Layout.jsx Shared layout wrapper (Header + Footer + Outlet)
|
│ ├── Layout.jsx Shared layout (Header + Outlet + Footer + WhatsAppButton + preloader + page‑id body class)
|
||||||
│ ├── Header.jsx Injects raw Elementor header HTML
|
│ ├── SafeHtml.jsx Parses HTML strings into React elements via html-react-parser (replaces dangerouslySetInnerHTML)
|
||||||
│ ├── Footer.jsx Injects raw Elementor footer HTML
|
│ ├── ErrorBoundary.jsx Catches render crashes, shows Try Again button
|
||||||
│ └── WhatsAppButton.jsx Custom floating WhatsApp widget (replaces broken plugin)
|
│ ├── Loading.jsx Spinner for Suspense fallback
|
||||||
|
│ ├── WhatsAppButton.jsx Floating WhatsApp widget
|
||||||
|
│ └── sections/
|
||||||
|
│ ├── Header.jsx Proper React component with <Link> nav, active state from useLocation()
|
||||||
|
│ ├── Footer.jsx Proper React component with contact data, social links, service links
|
||||||
|
│ ├── Breadcrumb.jsx Reusable breadcrumb trail
|
||||||
|
│ ├── SectionTitle.jsx Section heading with subtitle & icon
|
||||||
|
│ ├── Button.jsx Reusable styled button
|
||||||
|
│ ├── PhoneWidget.jsx Phone call widget
|
||||||
|
│ ├── FeatureCards.jsx Feature card grid
|
||||||
|
│ ├── ContactForm.jsx "Get a Quote" form
|
||||||
|
│ ├── ContactInfo.jsx Address/phone/email display
|
||||||
|
│ ├── GoogleMap.jsx Embedded Google Maps iframe
|
||||||
|
│ ├── Divider.jsx Section divider
|
||||||
|
│ ├── ProjectFilterGrid.jsx Filterable project grid
|
||||||
|
│ ├── ServiceContentBlocks.jsx Renders structured service content (paragraphs, headings, lists)
|
||||||
|
│ ├── ServiceGridSection.jsx Service listing grid
|
||||||
|
│ ├── ServiceBreadcrumb.jsx Service detail breadcrumb
|
||||||
|
│ ├── ServiceImage.jsx Service detail featured image
|
||||||
|
│ ├── ServiceBody.jsx Service detail body (content blocks)
|
||||||
|
│ └── ServiceSidebar.jsx Service detail sidebar nav
|
||||||
├── pages/
|
├── pages/
|
||||||
│ ├── Home.jsx Page: /
|
│ ├── Home.jsx Page: / — uses SafeHtml (parses home-content.html)
|
||||||
│ ├── About.jsx Page: /about
|
│ ├── About.jsx Page: /about — uses SafeHtml (parses about-content.html)
|
||||||
│ ├── Services.jsx Page: /service
|
│ ├── Services.jsx Page: /service — uses SafeHtml (parses service-content.html)
|
||||||
│ ├── ServiceDetail.jsx Page: /service/:slug
|
│ ├── ServiceDetail.jsx Page: /service/:slug — fully componentized, reads services.json
|
||||||
│ ├── Project.jsx Page: /project
|
│ ├── Project.jsx Page: /project — fully componentized, uses ProjectFilterGrid
|
||||||
│ └── Contact.jsx Page: /contact
|
│ └── Contact.jsx Page: /contact — fully componentized, uses section components
|
||||||
├── hooks/
|
├── hooks/
|
||||||
│ ├── useLayoutInit.js Re-initializes theme JS on route change
|
│ ├── useLayoutInit.js Re-initializes Elementor theme JS on route change (requestAnimationFrame polling)
|
||||||
│ └── useNavFix.js Intercepts links for proper SPA routing
|
│ ├── useNavFix.js Intercepts links inside a ref for proper SPA routing
|
||||||
├── data/ Raw Elementor HTML content (fetched from WP)
|
│ └── useThemeAssets.js Injects <meta> and <link> tags (OG, canonical, favicon) on mount
|
||||||
│ ├── home-content.html
|
├── data/ Content sources
|
||||||
│ ├── about-content.html
|
│ ├── home-content.html Home page HTML content (parsed by SafeHtml)
|
||||||
│ ├── service-content.html
|
│ ├── about-content.html About page HTML content (parsed by SafeHtml)
|
||||||
│ ├── project-content.html
|
│ ├── service-content.html Services listing HTML content (parsed by SafeHtml)
|
||||||
│ ├── contact-content.html
|
│ └── services.json Structured data for 9 service detail pages (contentBlocks, images, metadata)
|
||||||
│ ├── layout-header.html
|
└── css/ Build-time CSS imports (all bundled into one index-*.css)
|
||||||
│ ├── layout-footer.html
|
├── variables.css :root CSS custom properties for brand colors (imported first)
|
||||||
│ └── servicePages.js Maps slugs to service detail page HTML
|
├── kit.css Elementor kit CSS (global styles)
|
||||||
├── css/ Page-specific Elementor CSS (extracted from WP)
|
├── theme.css Rakar theme overrides
|
||||||
│ ├── kit.css Elementor kit CSS (global styles)
|
├── layout.css Header (elementor-945) + Footer (elementor-1098 / elementor-128) CSS
|
||||||
│ ├── theme.css Rakar theme overrides
|
├── pages.css Combined CSS for all 5 main pages
|
||||||
│ ├── layout.css Header (elementor-945) + Footer (elementor-128) CSS
|
├── services.css Combined CSS for all 9 service detail pages
|
||||||
│ ├── pages.css Combined CSS for all 5 main pages
|
├── dynamic.css Dynamic overrides using var(--brand-*) with fallbacks
|
||||||
│ ├── services.css Combined CSS for all 9 service detail pages
|
└── pages/ Individual page CSS files (development reference — edit these, then regenerate combined)
|
||||||
│ └── pages/ Individual page CSS files (development reference)
|
├── page-23.css Home page
|
||||||
│ ├── page-23.css Home page
|
├── page-31.css About page
|
||||||
│ ├── page-31.css About page
|
├── page-32.css Services listing
|
||||||
│ ├── page-32.css Services listing
|
├── page-36.css Project page
|
||||||
│ ├── page-36.css Project page
|
├── page-48.css Contact page
|
||||||
│ ├── page-48.css Contact page
|
└── service-*.css Individual service detail pages (9 files)
|
||||||
│ └── service-*.css Individual service detail pages
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Strategy: WordPress-in-React
|
## Strategy: Hybrid React + WordPress Content
|
||||||
|
|
||||||
This app uses a "render what WordPress built" approach:
|
The app uses a **hybrid approach**:
|
||||||
|
|
||||||
1. **Page content** is raw Elementor HTML from WordPress (`src/data/*.html`)
|
1. **Componentized pages** (Contact, Project, ServiceDetail) use proper React section components with structured data — no HTML strings.
|
||||||
2. **Injected via** `dangerouslySetInnerHTML` in page components
|
2. **Transitional pages** (Home, About, Services) render raw Elementor HTML from `src/data/*.html` files, parsed at runtime by `html-react-parser` via `SafeHtml.jsx` — **no `dangerouslySetInnerHTML`** anywhere in the app.
|
||||||
3. **Styling** arrives from two places:
|
3. **Styling** comes from two places:
|
||||||
- Build-time CSS imports (`src/css/*.css`) — Elementor's page-specific CSS
|
- Build-time CSS imports (`src/css/*.css`) — Elementor's page-specific CSS bundled by Vite
|
||||||
- Runtime `<link>` tags (`index.html`) — Theme stylesheets from `public/wp-content/themes/rakar/`
|
- Runtime assets from `public/wp-content/` — theme stylesheets, fonts, images
|
||||||
4. **Behavior** comes from the original Rakar theme JS loaded via `<script>` tags in `index.html`
|
4. **Behavior** comes from the original Rakar theme JS loaded via `<script>` tags in `index.html`
|
||||||
5. **Theme re-initialization** on route change is handled by `useLayoutInit.js` via `requestAnimationFrame` polling
|
5. **Theme re-initialization** on route change is handled by `useLayoutInit.js` via `requestAnimationFrame` polling
|
||||||
|
|
||||||
@@ -63,6 +83,66 @@ This app uses a "render what WordPress built" approach:
|
|||||||
|
|
||||||
Elementor pages are self-contained: each page has a unique CSS class (`.elementor-<postId>`) and the HTML references it. By loading ALL page CSS at once and the correct page HTML per route, we get pixel-perfect WordPress output without running PHP.
|
Elementor pages are self-contained: each page has a unique CSS class (`.elementor-<postId>`) and the HTML references it. By loading ALL page CSS at once and the correct page HTML per route, we get pixel-perfect WordPress output without running PHP.
|
||||||
|
|
||||||
|
### Migration Path
|
||||||
|
|
||||||
|
The codebase is in active migration from "raw Elementor HTML" to "proper React components":
|
||||||
|
|
||||||
|
| Page | Approach | Status |
|
||||||
|
|------|----------|--------|
|
||||||
|
| Contact | Full React components (ContactForm, ContactInfo, GoogleMap) | ✅ Complete |
|
||||||
|
| Project | Full React components (ProjectFilterGrid) | ✅ Complete |
|
||||||
|
| ServiceDetail | Structured data from `services.json` + React components | ✅ Complete |
|
||||||
|
| Home | `SafeHtml` parsing `home-content.html` | 🔄 Transitional |
|
||||||
|
| About | `SafeHtml` parsing `about-content.html` | 🔄 Transitional |
|
||||||
|
| Services | `SafeHtml` parsing `service-content.html` | 🔄 Transitional |
|
||||||
|
|
||||||
|
## Section Components & Data Flow
|
||||||
|
|
||||||
|
### Componentized Pages (Contact, Project, ServiceDetail)
|
||||||
|
|
||||||
|
These pages compose reusable section components rather than rendering raw HTML:
|
||||||
|
|
||||||
|
```
|
||||||
|
Contact.jsx
|
||||||
|
├── Breadcrumb props: title, items[]
|
||||||
|
├── SectionTitle props: subtitle, heading, alignment
|
||||||
|
├── ContactInfo props: items[] ({ icon, label, value, link })
|
||||||
|
├── ContactForm props: heading
|
||||||
|
├── GoogleMap props: src, title
|
||||||
|
└── Divider
|
||||||
|
|
||||||
|
ServiceDetail.jsx
|
||||||
|
├── ServiceBreadcrumb props: title
|
||||||
|
├── ServiceImage props: src, srcSet, width, height
|
||||||
|
├── ServiceBody props: blocks[] (from services.json → contentBlocks)
|
||||||
|
└── ServiceSidebar props: activeSlug (renders SERVICE_SIDEBAR_ITEMS from config.js)
|
||||||
|
|
||||||
|
Project.jsx
|
||||||
|
└── ProjectFilterGrid (self-contained, reads project data from public/wp-content/)
|
||||||
|
```
|
||||||
|
|
||||||
|
All section components return null/empty gracefully when props are missing — no hard crashes.
|
||||||
|
|
||||||
|
### SafeHtml Pages (Home, About, Services)
|
||||||
|
|
||||||
|
```
|
||||||
|
Home.jsx
|
||||||
|
└── SafeHtml
|
||||||
|
└── html-react-parser(require('home-content.html'))
|
||||||
|
└── useNavFix(ref) — intercepts <a> clicks for SPA navigation
|
||||||
|
|
||||||
|
About.jsx (same pattern)
|
||||||
|
Services.jsx (same pattern)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Central Configuration (`config.js`)
|
||||||
|
|
||||||
|
All magic values live in one file:
|
||||||
|
- **Brand colors**: `BRAND_PRIMARY`, `BRAND_DARK`, `BRAND_NAVY` (from `.env` or defaults)
|
||||||
|
- **WhatsApp**: `WHATSAPP_PHONE` (from `.env`)
|
||||||
|
- **Routes**: `SPA_ROUTES`, `PAGE_IDS` (path → WordPress post ID)
|
||||||
|
- **Services**: `POST_ID_TO_SLUG`, `SERVICE_SIDEBAR_ITEMS`, `MENU_ITEM_IDS`
|
||||||
|
|
||||||
## Key Fixes & Workarounds
|
## Key Fixes & Workarounds
|
||||||
|
|
||||||
| Issue | Solution |
|
| Issue | Solution |
|
||||||
@@ -70,10 +150,71 @@ Elementor pages are self-contained: each page has a unique CSS class (`.elemento
|
|||||||
| Elementor lazyload hides background images | Removed `.e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded)` CSS that applies `background-image: none !important` (was in `theme.css` — already cleaned) |
|
| Elementor lazyload hides background images | Removed `.e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded)` CSS that applies `background-image: none !important` (was in `theme.css` — already cleaned) |
|
||||||
| Elfsight WhatsApp plugin crashes page | Replaced with custom `WhatsAppButton.jsx` component |
|
| Elfsight WhatsApp plugin crashes page | Replaced with custom `WhatsAppButton.jsx` component |
|
||||||
| Theme JS not found on route change | `useLayoutInit.js` polls with `requestAnimationFrame` until `window.rakar_content_load_scripts` is available |
|
| Theme JS not found on route change | `useLayoutInit.js` polls with `requestAnimationFrame` until `window.rakar_content_load_scripts` is available |
|
||||||
| SPA links reload page | `useNavFix.js` intercepts same-site links, uses `react-router-dom`'s `useNavigate` |
|
| SPA links reload page | `useNavFix.js` intercepts same-site links, uses `react-router-dom`'s `useNavigate`; Header nav uses `<Link>` components |
|
||||||
| Missing Elementor JS dependencies | Added `webpack.runtime.min.js` and `frontend-modules.min.js` to `index.html` |
|
| Missing Elementor JS dependencies | Added `webpack.runtime.min.js` and `frontend-modules.min.js` to `index.html` |
|
||||||
| Waypoint plugin 404 | Created empty shim at `public/wp-includes/js/waypoint.min.js` |
|
| Waypoint plugin 404 | Created empty shim at `public/wp-includes/js/waypoint.min.js` |
|
||||||
|
|
||||||
|
## Style Organization
|
||||||
|
|
||||||
|
### CSS Import Order (in `main.jsx`)
|
||||||
|
|
||||||
|
```js
|
||||||
|
import './css/variables.css' // 1. Brand color variables (first so everything else can use them)
|
||||||
|
import './css/kit.css' // 2. Elementor kit global styles
|
||||||
|
import './css/theme.css' // 3. Rakar theme overrides
|
||||||
|
import './css/layout.css' // 4. Header + Footer layout
|
||||||
|
import './css/pages.css' // 5. Main page styles
|
||||||
|
import './css/services.css' // 6. Service detail styles
|
||||||
|
import './css/dynamic.css' // 7. Dynamic overrides using var(--brand-*) with fallbacks
|
||||||
|
```
|
||||||
|
|
||||||
|
**Order matters** — `variables.css` is first so `dynamic.css` (which uses `var(--brand-*)`) resolves correctly.
|
||||||
|
|
||||||
|
### Brand Color System
|
||||||
|
|
||||||
|
Colors are defined once in `.env`, flow through `config.js`, and land in CSS custom properties:
|
||||||
|
|
||||||
|
```
|
||||||
|
.env → config.js → variables.css
|
||||||
|
VITE_BRAND_PRIMARY → BRAND_PRIMARY → --brand-primary
|
||||||
|
VITE_BRAND_DARK → BRAND_DARK → --brand-dark
|
||||||
|
VITE_BRAND_NAVY → BRAND_NAVY → --brand-navy
|
||||||
|
```
|
||||||
|
|
||||||
|
`dynamic.css` uses these variables with fallbacks (e.g., `var(--brand-primary, #FA2D39)`) so it works even if variables.css is somehow absent.
|
||||||
|
|
||||||
|
### Regenerating Combined CSS
|
||||||
|
|
||||||
|
After editing individual page files in `src/css/pages/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat src/css/pages/page-*.css > src/css/pages.css
|
||||||
|
cat src/css/pages/service-*.css > src/css/services.css
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updating Content
|
||||||
|
|
||||||
|
### HTML-based pages (Home, About, Services)
|
||||||
|
1. Download updated HTML from WordPress
|
||||||
|
2. Replace the corresponding file in `src/data/`
|
||||||
|
3. Update the CSS in `src/css/pages/` if Elementor page CSS changed
|
||||||
|
4. Rebuild: `npm run build`
|
||||||
|
|
||||||
|
### Componentized pages (Contact, Project, ServiceDetail)
|
||||||
|
1. Edit the relevant section component in `src/components/sections/`
|
||||||
|
2. For service detail content, edit `src/data/services.json`
|
||||||
|
3. For contact data, edit `Contact.jsx` directly
|
||||||
|
4. Rebuild: `npm run build`
|
||||||
|
|
||||||
|
### Brand Colors
|
||||||
|
Edit `.env` (or create from `.env.example`), then rebuild:
|
||||||
|
```
|
||||||
|
VITE_WHATSAPP_PHONE=971564148980
|
||||||
|
VITE_BRAND_PRIMARY=#FA2D39
|
||||||
|
VITE_BRAND_DARK=#101840
|
||||||
|
VITE_BRAND_NAVY=#0a004c
|
||||||
|
```
|
||||||
|
|
||||||
## Setup & Development
|
## Setup & Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -85,7 +226,7 @@ npm run dev # Vite dev server (HMR enabled)
|
|||||||
## Build & Deploy
|
## Build & Deploy
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build # Outputs to dist/
|
npm run build # Outputs to dist/ — 0 errors, 0 warnings
|
||||||
```
|
```
|
||||||
|
|
||||||
The site is served via a **Python3 HTTP server** on port 8011:
|
The site is served via a **Python3 HTTP server** on port 8011:
|
||||||
@@ -100,52 +241,31 @@ No CI/CD pipeline is configured — deploy is manual.
|
|||||||
|
|
||||||
```
|
```
|
||||||
dist/
|
dist/
|
||||||
├── index.html 7.6 KB
|
├── index.html 7.6 KB │ gzip: 2.1 KB
|
||||||
├── assets/index-*.css 94 KB (all CSS bundled)
|
├── assets/index-*.css 74.0 KB │ gzip: 5.4 KB
|
||||||
├── assets/index-*.js 262 KB (main bundle)
|
├── assets/index-*.js 257.3 KB │ gzip: 80.9 KB (main bundle: router, components, CSS)
|
||||||
├── assets/Home-*.js 70 KB
|
├── assets/Home-*.js 69.7 KB │ gzip: 10.8 KB
|
||||||
├── assets/About-*.js 27 KB
|
├── assets/SafeHtml-*.js 27.8 KB │ gzip: 10.1 KB (html-react-parser chunk)
|
||||||
├── assets/Services-*.js 30 KB
|
├── assets/Services-*.js 29.8 KB │ gzip: 4.5 KB
|
||||||
├── assets/ServiceDetail-*.js 69 KB
|
├── assets/About-*.js 26.5 KB │ gzip: 3.9 KB
|
||||||
├── assets/Project-*.js 6 KB
|
├── assets/ServiceDetail-*.js 25.1 KB │ gzip: 7.0 KB
|
||||||
├── assets/Contact-*.js 9 KB
|
├── assets/Contact-*.js 9.2 KB │ gzip: 2.3 KB
|
||||||
└── assets/*.wasm (if any)
|
├── assets/Project-*.js 4.0 KB │ gzip: 1.5 KB
|
||||||
|
└── assets/Breadcrumb-*.js 0.6 KB │ gzip: 0.3 KB
|
||||||
```
|
```
|
||||||
|
|
||||||
All other assets (theme CSS/JS, Elementor assets, images) are served from `public/wp-content/` and `public/wp-includes/`.
|
All other assets (theme CSS/JS, Elementor assets, images) are served from `public/wp-content/` and `public/wp-includes/`.
|
||||||
|
|
||||||
## CSS Organization
|
The build produces **0 errors, 0 warnings**. If you see warnings, fix them before deploying.
|
||||||
|
|
||||||
CSS files in `src/css/` are imported at build time via `main.jsx`. They become part of the single `index-*.css` bundle:
|
## Dependencies
|
||||||
|
|
||||||
```js
|
| Package | Version | Purpose |
|
||||||
// main.jsx
|
|---------|---------|---------|
|
||||||
import './css/kit.css'
|
| react | ^19.2.6 | UI framework |
|
||||||
import './css/theme.css'
|
| react-dom | ^19.2.6 | DOM renderer |
|
||||||
import './css/layout.css'
|
| react-router-dom | ^7.17.0 | SPA routing |
|
||||||
import './css/pages.css'
|
| html-react-parser | ^6.1.4 | Safe HTML-to-React parsing |
|
||||||
import './css/services.css'
|
| @fortawesome/fontawesome-free | ^7.3.0 | Icon library |
|
||||||
```
|
| vite | ^8.0.12 | Build tool |
|
||||||
|
| @vitejs/plugin-react | ^6.0.1 | Vite React plugin |
|
||||||
To update styles:
|
|
||||||
- **Theme overrides** → edit `theme.css`
|
|
||||||
- **Header/Footer layout** → edit `layout.css`
|
|
||||||
- **Individual page styles** → edit `pages/page-<id>.css`, then regenerate `pages.css`
|
|
||||||
- **Service detail styles** → edit `pages/service-<id>.css`, then regenerate `services.css`
|
|
||||||
|
|
||||||
### Regenerating Combined CSS
|
|
||||||
|
|
||||||
After editing individual page files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat src/css/pages/page-*.css > src/css/pages.css
|
|
||||||
cat src/css/pages/service-*.css > src/css/services.css
|
|
||||||
```
|
|
||||||
|
|
||||||
## Updating Content
|
|
||||||
|
|
||||||
1. Download updated HTML from WordPress
|
|
||||||
2. Replace the corresponding file in `src/data/`
|
|
||||||
3. Update the CSS in `src/css/pages/` if Elementor page CSS changed
|
|
||||||
4. Rebuild: `npm run build`
|
|
||||||
5. Deploy: restart Python server with new `dist/`
|
|
||||||
|
|||||||
Reference in New Issue
Block a user