Chat about this codebase

AI-powered code exploration

Online

Project Overview

This repository delivers a minimal React + TypeScript content site scaffold powered by Vite. It includes essential pages, development tooling, and linting presets to help you build or customize an ImpactSOS-themed blog or informational site.

Purpose

Provide a lightweight, extensible starter for ImpactSOS content sites:

  • Bootstraps a React + TS app with Vite for fast builds
  • Ships Home and About templates aligned with ImpactSOS branding
  • Offers plugin-driven Fast Refresh and ESLint setups

Key Features

  • Vite + React + TypeScript: Zero-config build, HMR, and production optimizations
  • Fast Refresh: Instant component reloads during development
  • ESLint Integration: Extendable, type-aware linting with @typescript-eslint and React plugins
  • Prebuilt Pages:
    • Home.tsx: Main landing page with styled header
    • about/About.tsx: Mission, impact metrics, and team profiles

When to Choose This Codebase

Use this starter if you need to:

  • Launch an ImpactSOS or nonprofit microsite with minimal setup
  • Prototype content-driven pages and team showcases
  • Integrate custom layouts or data sources on top of a solid React + TS foundation

Getting Started

1. Install & Run

git clone https://github.com/Just-Lend/impact-blog.git
cd impact-blog
npm install
npm run dev      # starts Vite dev server on localhost:3000

2. Enable Fast Refresh

vite.config.ts:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'  // includes Fast Refresh

export default defineConfig({
  plugins: [react()],
})

3. Extend ESLint for Production

.eslintrc.js:

module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',  // TS rules
    'plugin:react/recommended',              // React-specific rules
  ],
  plugins: ['@typescript-eslint', 'react'],
  settings: { react: { version: 'detect' } },
}

4. Customize Pages

Home.tsx (src/pages/Home.tsx):

import React from 'react'
import './Home.css'

export default function Home() {
  return (
    <main className="home-container">
      <h1>Welcome to ImpactSOS Blog</h1>
      <p>Share stories, metrics, and achievements here.</p>
      {/* Add featured posts or components */}
    </main>
  )
}

About.tsx (src/pages/about/About.tsx):

import React from 'react'
import teamData from './team.json'

export default function About() {
  return (
    <section className="about-page">
      <h2>Our Mission</h2>
      <p>Empowering communities through data-driven impact.</p>
      <div className="team-grid">
        {teamData.map(member => (
          <article key={member.id}>
            <img src={member.avatar} alt={member.name} />
            <h3>{member.name}</h3>
            <p>{member.role}</p>
          </article>
        ))}
      </div>
    </section>
  )
}

Use these examples to tailor the site’s look, fetch live data, or integrate CMS feeds.

Getting Started

Fast-track to a running local instance: install dependencies, launch the dev server, and verify your first success screen.

Prerequisites

• Node.js >= 16
• npm >= 8 or Yarn >= 1.22
• Git

Installation

  1. Clone the repository
    git clone https://github.com/Just-Lend/impact-blog.git
    cd impact-blog
    
  2. Install dependencies
    npm install
    # or
    yarn
    

Start the Development Server

Launch Vite’s hot-reload server:

npm run dev
# or
yarn dev

By default, Vite serves on http://localhost:5173. Open that URL to view your app. The server reloads instantly on file changes.

Verify Your First Success Screen

After npm run dev:

  • Browser tab title reads ImpactSOS Zines (from public/index.html).
  • The DOM contains <div id="root"></div>.
  • No errors appear in the console.

If you have a minimal AppContainer rendering (e.g., a “Hello, world!”), you’ll see it in the viewport. This confirms React, TypeScript, TailwindCSS and Vite are configured correctly.

Build and Preview (Optional)

  1. Create a production build

    npm run build
    # or
    yarn build
    

    Outputs optimized files to dist/.

  2. Preview the production build locally

    npm run preview
    # or
    yarn preview
    

    Open http://localhost:4173 (default) to smoke-test your built assets.

With these steps you have a fully operational local environment ready for development and testing.

Architecture & Project Structure

This section outlines the codebase organization, client-side routing setup, and the flow of data from API calls to rendered pages.

Project Directory Layout

src/
 ┣ api/
 ┃ ┣ campaignArticleService.ts
 ┃ ┗ otherService.ts
 ┣ components/
 ┃ ┣ alerts/
 ┃ ┣ Header.tsx
 ┃ ┣ Footer.tsx
 ┃ ┣ ScrollToTop.tsx
 ┃ ┗ LayoutContainer.tsx
 ┣ pages/
 ┃ ┣ Magazine.tsx
 ┃ ┣ Article.tsx
 ┃ ┣ CategorisedArticles.tsx
 ┃ ┣ UNSDGArticles.tsx
 ┃ ┣ LocalAuthorityArticles.tsx
 ┃ ┣ about/
 ┃ ┃ ┗ About.tsx
 ┃ ┣ RegisterInterest.tsx
 ┃ ┗ …  
 ┣ App.tsx
 ┣ AppContainer.tsx
 ┗ index.tsx

Entry Point: src/index.tsx

Mounts the application within a full-screen container.

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import AppContainer from './AppContainer';

const root = ReactDOM.createRoot(
  document.getElementById('root')!
);

root.render(
  <AppContainer>
    <App />
  </AppContainer>
);

Layout Wrapper: src/AppContainer.tsx

Provides a full-viewport flex layout for header, content, and footer.

import styled from 'styled-components';

const AppContainer = styled.div`
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  width: 100vw;
  background-color: #fff;
`;

export default AppContainer;

Routing Configuration: src/App.tsx

Defines client-side routes and global layout components.

import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ScrollToTop from './components/ScrollToTop';
import Header from './components/Header';
import LayoutContainer from './components/LayoutContainer';
import Footer from './components/Footer';

// Page imports
import Magazine from './pages/Magazine';
import ArticlePage from './pages/Article';
// …other page imports

const App: React.FC = () => (
  <Router>
    <ScrollToTop />
    <Header />
    <LayoutContainer>
      <Routes>
        <Route path="/" element={<Magazine />} />
        <Route path="/articles/:slug" element={<ArticlePage />} />
        <Route path="/categories/:id" element={<CategorisedArticles />} />
        <Route path="/unsdgs/:unsdgNumber" element={<UNSDGArticles />} />
        <Route path="/local-authorities/:laId" element={<LocalAuthorityArticles />} />
        <Route path="/about" element={<About />} />
        <Route path="/register-interest" element={<RegisterInterest />} />
        <Route path="/support" element={<Support />} />
        {/* …product and static routes */}
      </Routes>
    </LayoutContainer>
    <Footer />
  </Router>
);

export default App;

Key Points

  • Wrap the app in <BrowserRouter> once, at the root.
  • Place <Header> and <Footer> outside <Routes> to persist across pages.
  • Use <LayoutContainer> to constrain content width and add padding.
  • Use <ScrollToTop> to reset scroll on navigation.

Data Flow: API → Page → UI

  1. Route Activation
    React Router matches URL (e.g. /articles/:slug) and mounts the corresponding page component.

  2. Parameter Retrieval
    The page uses useParams() to extract dynamic segments (slug, id, etc.).

  3. Data Fetching
    In useEffect, the page calls a service function from src/api/....
    Example for article details:

    import React, { useEffect, useState } from 'react';
    import { useParams } from 'react-router-dom';
    import { getCampaignArticleBySlug } from '@/api/campaignArticleService';
    import ArticleDetail from '../components/ArticleDetail';
    
    const ArticlePage: React.FC = () => {
      const { slug } = useParams<{ slug: string }>();
      const [article, setArticle] = useState<Article | null>(null);
    
      useEffect(() => {
        if (slug) {
          getCampaignArticleBySlug(slug)
            .then(setArticle)
            .catch(console.error);
        }
      }, [slug]);
    
      if (!article) return <p>Loading...</p>;
      return <ArticleDetail article={article} />;
    };
    
    export default ArticlePage;
    
  4. Service Layer
    Service functions in src/api/… use Axios with import.meta.env.VITE_API_URL as base URL. They return typed data (e.g., Article[] or Article | null).

  5. State & Rendering
    The page sets state with fetched data and renders UI components (e.g., ArticleDetail, ArticleList).

  6. Consistent Layout
    Header, footer, and layout container remain static, isolating pages to the central <LayoutContainer>.

Use this structure when adding new pages or API integrations: define a route in App.tsx, create a page component under src/pages, fetch data via a service in src/api, and compose UI within the existing layout.

Configuration & Environment

Centralize all application-wide settings: deployment commands, environment variables, Tailwind breakpoints, TypeScript compilation, and Vite build options.


1. Deployment Process (Procfile)

Define the web process for Heroku or any platform using a Procfile at project root:

web: npm start

Ensure your package.json has a matching start script:

// package.json
{
  "scripts": {
    "start": "vite preview --port $PORT"
  }
}

• During deploy, Heroku reads web and runs npm start.
• VITE_PORT and other $ENV vars propagate automatically.


2. Type-Safe Environment Variables

a. Define Vite globals (src/vite-env.d.ts)

Add at src/vite-env.d.ts:

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_DEBUG_MODE?: 'on' | 'off'
  // add more VITE_ vars here
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

b. Add custom .env files

Create .env.development and .env.production:

VITE_API_URL=https://api.dev.example.com
VITE_DEBUG_MODE=on

Vite merges and exposes only VITE_ prefixed vars via import.meta.env.

c. Access variables in code

// src/utils/api.ts
const apiUrl = import.meta.env.VITE_API_URL

export async function fetchStatus() {
  const res = await fetch(`${apiUrl}/status`)
  return res.json()
}

• Restart npm run dev after updating .env or vite-env.d.ts.


3. TailwindCSS Breakpoints & Fonts

a. Extend tailwind.config.js

// tailwind.config.js
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {
      fontFamily: {
        hanken: ["Hanken Grotesk", "sans-serif"],
      },
    },
    screens: {
      monitor: { max: "2560px" },
      desktop: { max: "1279px" },
      tablet:  { max: "1199px" },
      mobile:  { max: "820px" },
      // To add new breakpoint:
      // small: { max: "600px" }
    },
  },
  plugins: [],
}

b. Import base styles

/* src/index.css */
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

body {
  font-family: "Hanken Grotesk", sans-serif;
  margin: 0;
}

c. Usage examples

// src/components/Header.tsx
export function Header() {
  return (
    <header className="font-hanken text-5xl tablet:text-3xl mobile:text-2xl p-8 tablet:p-4">
      <h1>Impact Blog</h1>
    </header>
  )
}

tablet:text-3xl applies at ≤1199px, mobile:text-2xl at ≤820px.
• Add new screens by updating screens and restarting the dev server.


4. TypeScript Configuration

a. Project references (tsconfig.json)

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ]
}

b. App settings (tsconfig.app.json)

{
  "compilerOptions": {
    "target": "ES2020",
    "jsx": "react-jsx",
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "moduleDetection": "force",
    "strict": true,
    "noEmit": true
  },
  "include": ["src"]
}

c. Node/Vite config type-check (tsconfig.node.json)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true
  },
  "include": ["vite.config.ts"]
}

Add new paths: add "paths" under compilerOptions in tsconfig.app.json.
Run type-checks: npx tsc --build tsconfig.app.json tsconfig.node.json.


5. Vite Build & Plugin Configuration

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig(({ mode }) => ({
  plugins: [
    react(),
    tailwindcss(),
    // e.g., svgLoader(), // add safely here
  ],
  resolve: {
    alias: { '@': '/src' }
  },
  server: {
    port: Number(process.env.PORT) || 3000,
    open: true
  },
  define: {
    // expose runtime flags
    __DEV__: mode === 'development'
  }
}))

Conditional plugins: wrap based on mode.
Aliases: align with TS paths.
Server port: uses $PORT from Procfile in production.

With this setup, you manage deployment commands, type-safe environment variables, custom Tailwind breakpoints, strict TypeScript settings, and a flexible Vite build pipeline—all in sync.

Development & Contributing

This section describes how to set up your local environment, enforce code quality, follow branch and commit conventions, and prepare for testing.

Prerequisites

  • Node.js ≥ 14
  • npm ≥ 6

Setup

Install dependencies and start the dev server:

# Install all dependencies
npm install

# Start Vite dev server with React Fast Refresh
npm run dev

Available Scripts

Run these from the project root:

# Build production assets into dist/
npm run build

# Preview the production build on localhost
npm run preview

# Lint all .ts/.tsx files
npm run lint

# Serve the static build (you need a simple HTTP server)
npm run start

Linting & Formatting

We use ESLint (configured in eslint.config.js) to enforce TypeScript and React best practices.

Run lint checks:

npm run lint

Auto-fix lint errors:

npm run lint -- --fix

Key ESLint rule for Fast Refresh compatibility:

// eslint.config.js
import reactRefresh from 'eslint-plugin-react-refresh'

export default {
  plugins: { 'react-refresh': reactRefresh },
  rules: {
    // Warn if you export non-components and break Fast Refresh
    'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
  },
}

To enable type-aware rules, ensure you have:

npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin typescript

And in your ESLint config:

export default {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
  ],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint', 'react', 'react-refresh'],
  // …other settings…
}

Branching Strategy

Use descriptive, prefixed branch names off main:

  • feature/<feature-name> – new feature
  • fix/<bug-fix> – bug fixes
  • chore/<task> – maintenance, tooling, docs
  • release/<version> – release preparation

Always rebase or merge from main before creating a PR.

Commit & Pull Request Guidelines

Follow Conventional Commits:

<type>(<scope>): <short description>

Common types:

  • feat: a new feature
  • fix: a bug fix
  • chore: build or tooling changes
  • docs: documentation only changes

Example:

feat(ui): add responsive sidebar toggle

When opening a PR against main:

  1. Link any related issue (e.g. “Closes #42”).
  2. Describe what and why, include screenshots if UI changes.
  3. Ensure npm run lint passes without errors.

Testing (Placeholder)

We plan to integrate a testing framework (e.g. Vitest or Jest) and React Testing Library:

  • Unit tests for components
  • Integration tests for pages and API interactions
  • CI workflow to run tests on each PR

Contributions adding tests are encouraged under a new __tests__/ folder.

Deployment & Hosting

This section covers building a production-ready image of your React + TailwindCSS app, configuring environment variables, and deploying on Heroku or any platform that runs npm start.

1. Build Production Assets

Generate optimized static files in the /dist folder:

# Install dependencies and build
npm ci
npm run build
  • tsc -b compiles TypeScript.
  • vite build outputs minified assets to dist/.

2. Configure Environment Variables

Vite exposes only variables prefixed with VITE_ at build time.

  1. Create .env.production in project root:
    VITE_API_URL=https://api.example.com
    VITE_BASE_PATH=/
    
  2. Access in code:
    // src/config.ts
    export const API_URL = import.meta.env.VITE_API_URL;
    
  3. Override on host platform:
    # Heroku example
    heroku config:set VITE_API_URL=https://api.prod.com
    

Base Path Configuration (vite.config.ts)

If you serve under a subpath, set base:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  base: process.env.VITE_BASE_PATH ?? '/',
  plugins: [react()],
});

3. Procfile & Runtime Server

Heroku (and similar) detect a Procfile at root:

Procfile

web: npm start
  • npm start runs serve -s dist—a static file server.
  • Ensure serve is in dependencies:
    "dependencies": {
      "serve": "^14.0.1",
      // ...
    }
    

4. Deploy to Heroku

  1. Login and create app:
    heroku login
    heroku create impact-blog
    
  2. Push and release:
    git push heroku main
    
  3. Set production env vars:
    heroku config:set VITE_API_URL=https://api.example.com
    
  4. Open in browser:
    heroku open
    

Heroku will automatically:

  • Install deps (npm ci)
  • Detect Procfile → run npm start
  • Serve /dist

5. Deploy to Other Platforms

Any host that runs npm start works:

Netlify

  • Connect Git repo
  • Build command: npm ci && npm run build
  • Publish directory: dist
  • Add VITE_ env vars in Netlify Dashboard

Vercel

  • Import project
  • Set Framework Preset: “Other”
  • Build command: npm run build
  • Output directory: dist
  • Define env vars under Settings → Environment Variables

Docker (optional)

FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
RUN npm install -g serve
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["serve", "-s", "dist", "-l", "3000"]

Build & run:

docker build -t impact-blog .
docker run -p 3000:3000 -e VITE_API_URL=https://api.example.com impact-blog