Files
profen/README.md
2026-07-08 09:08:17 +00:00

272 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Profen Engineering — React SPA
A React single-page application that recreates [profeneng.com](https://profeneng.com) (WordPress + Elementor + Rakar theme) as a fast, portable frontend.
## Architecture
```
src/
├── main.jsx Entry — imports CSS (variables → kit → theme → layout → pages → services → dynamic), mounts React
├── 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/
│ ├── Layout.jsx Shared layout (Header + Outlet + Footer + WhatsAppButton + preloader + pageid body class)
│ ├── SafeHtml.jsx Parses HTML strings into React elements via html-react-parser (replaces dangerouslySetInnerHTML)
│ ├── ErrorBoundary.jsx Catches render crashes, shows Try Again button
│ ├── 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/
│ ├── Home.jsx Page: / — uses SafeHtml (parses home-content.html)
│ ├── About.jsx Page: /about — uses SafeHtml (parses about-content.html)
│ ├── Services.jsx Page: /service — uses SafeHtml (parses service-content.html)
│ ├── ServiceDetail.jsx Page: /service/:slug — fully componentized, reads services.json
│ ├── Project.jsx Page: /project — fully componentized, uses ProjectFilterGrid
│ └── Contact.jsx Page: /contact — fully componentized, uses section components
├── hooks/
│ ├── useLayoutInit.js Re-initializes Elementor theme JS on route change (requestAnimationFrame polling)
│ ├── useNavFix.js Intercepts links inside a ref for proper SPA routing
│ └── useThemeAssets.js Injects <meta> and <link> tags (OG, canonical, favicon) on mount
├── data/ Content sources
│ ├── home-content.html Home page HTML content (parsed by SafeHtml)
│ ├── about-content.html About page HTML content (parsed by SafeHtml)
│ ├── service-content.html Services listing HTML content (parsed by SafeHtml)
│ └── services.json Structured data for 9 service detail pages (contentBlocks, images, metadata)
└── css/ Build-time CSS imports (all bundled into one index-*.css)
├── variables.css :root CSS custom properties for brand colors (imported first)
├── kit.css Elementor kit CSS (global styles)
├── theme.css Rakar theme overrides
├── layout.css Header (elementor-945) + Footer (elementor-1098 / elementor-128) CSS
├── pages.css Combined CSS for all 5 main pages
├── services.css Combined CSS for all 9 service detail pages
├── dynamic.css Dynamic overrides using var(--brand-*) with fallbacks
└── pages/ Individual page CSS files (development reference — edit these, then regenerate combined)
├── page-23.css Home page
├── page-31.css About page
├── page-32.css Services listing
├── page-36.css Project page
├── page-48.css Contact page
└── service-*.css Individual service detail pages (9 files)
```
## Strategy: Hybrid React + WordPress Content
The app uses a **hybrid approach**:
1. **Componentized pages** (Contact, Project, ServiceDetail) use proper React section components with structured data — no HTML strings.
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** comes from two places:
- Build-time CSS imports (`src/css/*.css`) — Elementor's page-specific CSS bundled by Vite
- 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`
5. **Theme re-initialization** on route change is handled by `useLayoutInit.js` via `requestAnimationFrame` polling
### Why This Works
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
| Issue | Solution |
|-------|----------|
| 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 |
| 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`; Header nav uses `<Link>` components |
| 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` |
## 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
```bash
cd react-app
npm install
npm run dev # Vite dev server (HMR enabled)
```
## Build & Deploy
```bash
npm run build # Outputs to dist/ — 0 errors, 0 warnings
```
The site is served via a **Python3 HTTP server** on port 8011:
```bash
cd dist && python3 -m http.server 8011
```
No CI/CD pipeline is configured — deploy is manual.
### Production File Structure
```
dist/
├── index.html 7.6 KB │ gzip: 2.1 KB
├── assets/index-*.css 74.0 KB │ gzip: 5.4 KB
├── assets/index-*.js 257.3 KB │ gzip: 80.9 KB (main bundle: router, components, CSS)
├── assets/Home-*.js 69.7 KB │ gzip: 10.8 KB
├── assets/SafeHtml-*.js 27.8 KB │ gzip: 10.1 KB (html-react-parser chunk)
├── assets/Services-*.js 29.8 KB │ gzip: 4.5 KB
├── assets/About-*.js 26.5 KB │ gzip: 3.9 KB
├── assets/ServiceDetail-*.js 25.1 KB │ gzip: 7.0 KB
├── assets/Contact-*.js 9.2 KB │ gzip: 2.3 KB
├── 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/`.
The build produces **0 errors, 0 warnings**. If you see warnings, fix them before deploying.
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| react | ^19.2.6 | UI framework |
| react-dom | ^19.2.6 | DOM renderer |
| react-router-dom | ^7.17.0 | SPA routing |
| html-react-parser | ^6.1.4 | Safe HTML-to-React parsing |
| @fortawesome/fontawesome-free | ^7.3.0 | Icon library |
| vite | ^8.0.12 | Build tool |
| @vitejs/plugin-react | ^6.0.1 | Vite React plugin |