From 2046b8bf33567bff8193519712ff9ce4746a7de1 Mon Sep 17 00:00:00 2001 From: Jacques Prieur Du Plessis Date: Tue, 28 Oct 2025 21:15:37 +0200 Subject: [PATCH 1/5] Added Multi-Language Support and Commented Code along with CICD improvements --- .github/workflows/deploy.yml | 101 +++++++++++++++-- README.md | 57 +++++----- eslint.config.js | 1 + package-lock.json | 88 ++++++++++++++- package.json | 2 + playwright-report/index.html | 2 +- src/App.jsx | 3 + src/components/common/ErrorBoundary.test.jsx | 12 +- src/components/common/Footer.css | 1 - src/components/common/Footer.jsx | 3 + src/components/common/Navbar.jsx | 4 + src/components/common/Navbar.test.jsx | 16 ++- src/components/ui/AboutSection.jsx | 20 ++-- src/components/ui/CommunityCard.jsx | 2 + src/components/ui/FAQSection.jsx | 98 +++++----------- src/components/ui/HeroSection.css | 1 - src/components/ui/HeroSection.jsx | 19 ++-- src/components/ui/IconCard.jsx | 2 + src/components/ui/InviteEmailSection.jsx | 79 ++++++------- src/components/ui/SponsorshipCard.jsx | 38 +++++-- src/components/ui/TextCard.jsx | 2 + src/config/csp.js | 4 +- src/i18n.js | 43 +++++++ src/locales/en/aboutSection.json | 10 ++ src/locales/en/faq.json | 58 ++++++++++ src/locales/en/hero.json | 6 + src/locales/en/home.json | 43 +++++++ src/locales/en/inviteSection.json | 26 +++++ src/locales/en/sponsorship.json | 101 +++++++++++++++++ src/locales/en/sponsorshipSection.json | 4 + src/main.jsx | 5 + src/pages/Home.jsx | 67 +++++------ src/pages/Sponsorship.jsx | 111 +++---------------- src/utils/scrollToSection.js | 4 + tests/e2e/browser-compatibility.spec.js | 7 +- 35 files changed, 713 insertions(+), 327 deletions(-) create mode 100644 src/i18n.js create mode 100644 src/locales/en/aboutSection.json create mode 100644 src/locales/en/faq.json create mode 100644 src/locales/en/hero.json create mode 100644 src/locales/en/home.json create mode 100644 src/locales/en/inviteSection.json create mode 100644 src/locales/en/sponsorship.json create mode 100644 src/locales/en/sponsorshipSection.json diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 347e748..8fcb7ce 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,17 +1,95 @@ -name: Deploy to GitHub Pages +name: CI on: + pull_request: + branches: + - main push: branches: - main -permissions: - contents: write - jobs: - build-and-deploy: + lint: + name: Lint Code + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint + + unit-tests: + name: Unit Tests runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:run + + e2e-tests: + name: E2E Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + - name: Build site + env: + NODE_OPTIONS: --max_old_space_size=4096 + run: npm run build + + - name: Run E2E tests + run: npm run test:e2e + + - name: Upload test results + if: failure() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 + + build: + name: Build Check + runs-on: ubuntu-latest + steps: - name: Checkout uses: actions/checkout@v4 @@ -30,11 +108,10 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 run: npm run build - - name: Deploy to gh-pages - uses: JamesIves/github-pages-deploy-action@v4 + - name: Upload build artifacts + continue-on-error: true + uses: actions/upload-artifact@v4 with: - branch: gh-pages - folder: dist - clean: true - clean-exclude: | - pr-preview/ + name: build-output + path: dist/ + retention-days: 1 \ No newline at end of file diff --git a/README.md b/README.md index d31ce2f..2909ee1 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ A modern, responsive community platform built with React 19 and Vite, featuring comprehensive testing, cross-browser compatibility, and enterprise-grade security. -## โœจ Features +## Features -- ๐ŸŒ **Responsive Design** โ€“ Mobile-first layout with smooth in-page navigation -- ๐Ÿงช **Automated Testing** โ€“ Unit and Playwright coverage for critical journeys -- ๐Ÿ“ฑ **Cross-Browser Support** โ€“ Chrome, Firefox, Safari (desktop + mobile) -- โšก **Modern Stack** โ€“ React 19, Vite, Playwright, and Vitest -- ๐Ÿ›ก๏ธ **Built-In Security** โ€“ Strict Content Security Policy and lazy loading for heavy sections +- Responsive Design โ€“ Mobile-first layout with smooth in-page navigation +- Automated Testing โ€“ Unit and Playwright coverage for critical journeys +- Cross-Browser Support โ€“ Chrome, Firefox, Safari (desktop + mobile) +- Modern Stack โ€“ React 19, Vite, Playwright, and Vitest +- Built-In Security โ€“ Strict Content Security Policy and lazy loading for heavy sections -## ๐Ÿš€ Quick Start +## Quick Start ### Prerequisites - Node.js 18+ @@ -32,8 +32,7 @@ npm run dev ### Environment Variables None required for local development. -## ๐Ÿ—๏ธ Project Structure - +## Project Structure ``` zatech-website/ โ”œโ”€โ”€ public/ # Static assets served directly @@ -58,7 +57,7 @@ zatech-website/ โ””โ”€โ”€ README.md # This file ``` -## ๐Ÿ”ง Development +## Development ### Available Scripts ```bash @@ -90,22 +89,22 @@ npm run dev # - Rate limiting status ``` -## ๐Ÿงช Testing Cheat Sheet +## Testing Cheat Sheet ### Quick Test Commands ```bash -# ๐Ÿš€ FULL TEST SUITE (Run this before commits) +# FULL TEST SUITE (Run this before commits) npm run test:run && npm run test:e2e && npm run lint && npm audit -# ๐Ÿƒโ€โ™‚๏ธ QUICK DEV CHECKS (During development) +# QUICK DEV CHECKS (During development) npm run test:run # Unit tests only (fast ~1-2 seconds) npm run lint # Code quality check (~1 second) npm run dev # Start dev server -# ๐ŸŒ CROSS-BROWSER TESTING (Before releases) +# CROSS-BROWSER TESTING (Before releases) npm run test:e2e # Full browser compatibility (~10-30 seconds) -# ๐Ÿ”’ SECURITY CHECK +# SECURITY CHECK npm audit # Check for vulnerabilities ``` @@ -115,7 +114,7 @@ npm audit # Check for vulnerabilities | **Add new component** | `npm run test:run` | Verify existing tests still pass | | **Change existing code** | `npm run test:run` | Check for regressions | | **Add new CSS/styles** | `npm run lint` | Catch style issues early | -| **Before committing** | Full test suite โฌ†๏ธ | Ensure nothing is broken | +| **Before committing** | Full test suite | Ensure nothing is broken | | **Add new dependencies** | `npm audit` | Security check | | **Test mobile/responsive** | `npm run dev -- --host 0.0.0.0` | Test on phone | | **Before deployment** | `npm run test:e2e` | Cross-browser verification | @@ -147,7 +146,7 @@ npm run dev -- --host 0.0.0.0 # (Check terminal output for exact IP address) ``` -## ๐Ÿ›ก๏ธ Security & Quality +## Security & Quality ### Security Features - **Content Security Policy (CSP)**: Browser-level guardrails for third-party content @@ -167,7 +166,7 @@ Configured via `.browserslistrc`: - iOS Safari 14+, Android Chrome 88+ - Modern JavaScript features with graceful degradation -## ๐Ÿค Contributing +## Contributing 1. Fork the repository 2. Create a feature branch (`git checkout -b feature/amazing-feature`) @@ -184,23 +183,23 @@ Configured via `.browserslistrc`: - Update documentation for significant changes - Follow security patterns established in the codebase -## ๐Ÿ† Technical Achievements +## Technical Achievements This project demonstrates production-grade development practices: -- โœ… **Modern React Architecture** - Hooks, components, routing -- โœ… **Comprehensive Testing** - Unit, integration, E2E, cross-browser -- โœ… **Security First** - Multiple layers of protection -- โœ… **Performance Optimized** - Fast builds, optimized bundles -- โœ… **Mobile Ready** - Responsive design, touch-friendly -- โœ… **Developer Experience** - Hot reload, linting, type safety -- โœ… **Production Ready** - Build optimization, error handling +- Modern React Architecture - Hooks, components, routing +- Comprehensive Testing - Unit, integration, E2E, cross-browser +- Security First - Multiple layers of protection +- Performance Optimized - Fast builds, optimized bundles +- Mobile Ready - Responsive design, touch-friendly +- Developer Experience - Hot reload, linting, type safety +- Production Ready - Build optimization, error handling -## ๐Ÿ“„ License +## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## ๐Ÿ†˜ Support & Resources +## Support & Resources - **Documentation**: This README covers all major aspects - **Issues**: Report bugs via [GitHub Issues](https://github.com/Accompany-VC/zatech-website/issues) @@ -209,4 +208,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file --- -Built with โค๏ธ for the South African tech community +Built with love for the South African tech community \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index d56420f..77cbc17 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,6 +4,7 @@ import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import { defineConfig, globalIgnores } from 'eslint/config' +// ESLint configuration export default defineConfig([ globalIgnores(['dist']), { diff --git a/package-lock.json b/package-lock.json index bc7afa3..12e8b0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "zatech-website", "version": "0.0.0", "dependencies": { + "i18next": "^25.6.0", "lucide-react": "^0.544.0", "prop-types": "^15.8.1", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-i18next": "^16.2.1", "react-router-dom": "^7.9.1" }, "devDependencies": { @@ -334,7 +336,6 @@ "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3239,6 +3240,15 @@ "node": ">=18" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -3280,6 +3290,37 @@ "node": ">= 14" } }, + "node_modules/i18next": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz", + "integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -4225,6 +4266,33 @@ "react": "^19.1.1" } }, + "node_modules/react-i18next": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.2.1.tgz", + "integrity": "sha512-z7TVwd8q4AjFo2n7oOwzNusY7xVL4uHykwX1zZRvasUQnmnXlp7Z1FZqXvhK/6hQaCvWTZmZW1bMaUWKowtvVw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 25.5.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5035,6 +5103,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5213,6 +5290,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index f43a0fe..0d580bf 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,12 @@ "test:all": "npm run test:run && npm run test:e2e" }, "dependencies": { + "i18next": "^25.6.0", "lucide-react": "^0.544.0", "prop-types": "^15.8.1", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-i18next": "^16.2.1", "react-router-dom": "^7.9.1" }, "devDependencies": { diff --git a/playwright-report/index.html b/playwright-report/index.html index 92fd0d5..055fcbf 100644 --- a/playwright-report/index.html +++ b/playwright-report/index.html @@ -73,4 +73,4 @@
- \ No newline at end of file + \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index a2062fd..a252d9a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,9 +5,11 @@ import Footer from "./components/common/Footer"; import ErrorBoundary from "./components/common/ErrorBoundary"; import appStyles from "./App.css?inline"; +// Lazy load page components const Home = lazy(() => import("./pages/Home")); const SponsorshipPage = lazy(() => import("./pages/Sponsorship")); +// Inject app-specific styles into document head if (typeof document !== "undefined" && !document.querySelector('style[data-app-styles="true"]')) { const styleTag = document.createElement("style"); styleTag.setAttribute("data-app-styles", "true"); @@ -15,6 +17,7 @@ if (typeof document !== "undefined" && !document.querySelector('style[data-app-s document.head.appendChild(styleTag); } +// Main App component with routing and layout function App() { return ( diff --git a/src/components/common/ErrorBoundary.test.jsx b/src/components/common/ErrorBoundary.test.jsx index 1d3446a..4f307af 100644 --- a/src/components/common/ErrorBoundary.test.jsx +++ b/src/components/common/ErrorBoundary.test.jsx @@ -19,7 +19,7 @@ beforeEach(() => { afterEach(() => { console.error = originalError }) - +// Test suite for ErrorBoundary component describe('ErrorBoundary', () => { it('should render children when there is no error', () => { render( @@ -30,18 +30,21 @@ describe('ErrorBoundary', () => { expect(screen.getByText('No error')).toBeInTheDocument() }) - + + // Test case for default error message it('should render default error message when error occurs', () => { render( ) - + expect(screen.getByText('Something went wrong.')).toBeInTheDocument() expect(screen.getByText('Please refresh the page or try again later.')).toBeInTheDocument() }) + + // Test case for custom fallback it('should render custom fallback when provided', () => { const customFallback =
Custom error message
@@ -55,6 +58,7 @@ describe('ErrorBoundary', () => { expect(screen.queryByText('Something went wrong.')).not.toBeInTheDocument() }) + // Test case for logging errors it('should log error to console when error occurs', () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) @@ -73,6 +77,7 @@ describe('ErrorBoundary', () => { consoleSpy.mockRestore() }) + // Test case for styling of error message it('should have correct error boundary styling', () => { render( @@ -87,6 +92,7 @@ describe('ErrorBoundary', () => { }) }) + // Test case for multiple errors it('should handle multiple errors gracefully', () => { const { rerender } = render( diff --git a/src/components/common/Footer.css b/src/components/common/Footer.css index af9dc95..64449e8 100644 --- a/src/components/common/Footer.css +++ b/src/components/common/Footer.css @@ -1,4 +1,3 @@ -/* Footer Styles */ .main-footer { background: var(--footer-bg); color: var(--footer-fg); diff --git a/src/components/common/Footer.jsx b/src/components/common/Footer.jsx index a30ee4d..f092377 100644 --- a/src/components/common/Footer.jsx +++ b/src/components/common/Footer.jsx @@ -2,6 +2,7 @@ import PropTypes from "prop-types"; import { Github, Linkedin } from "lucide-react"; import footerStyles from "./Footer.css?inline"; +// Inject footer styles into the document head if (typeof document !== "undefined" && !document.querySelector('style[data-footer-styles="true"]')) { const styleTag = document.createElement("style"); styleTag.setAttribute("data-footer-styles", "true"); @@ -9,6 +10,7 @@ if (typeof document !== "undefined" && !document.querySelector('style[data-foote document.head.appendChild(styleTag); } +// Footer component with social links and legal information function Footer({ className }) { return (