Skip to content

wahyurizky27/web-app-integration-sync-panel

Repository files navigation

Integration Sync Panel

A Web App Integration Sync Panel for a B2B SaaS platform that connects to multiple external services (Salesforce, HubSpot, Stripe, and more). The system supports bidirectional data synchronization with structured conflict detection and resolution.

Built as a take-home test for Portier's Product Engineer (Frontend) role.


Tech Stack

Tool Purpose
React 19 + TypeScript UI framework
Vite 8 Build tool & dev server
Tailwind CSS v3 Styling
React Router v7 Client-side routing
Lucide React Icon set
clsx + tailwind-merge Conditional class names

Getting Started

Prerequisites

  • Node.js 18+
  • npm 9+

1. Clone & Install

git clone <your-repo-url>
cd sync-panel-web
npm install

2. Set Up Environment Variables

Copy the example env file:

cp .env.example .env

Then open .env and set the API base URL:

VITE_API_BASE_URL=https://portier-takehometest.onrender.com/api/v1

3. Run

npm run dev

Open http://localhost:5173

Other Commands

npm run build      # Production build
npm run preview    # Preview production build locally
npm run lint       # Run ESLint
npx tsc --noEmit   # Type check without building

Environment Variables

Variable Required Description
VITE_API_BASE_URL ✅ Yes Base URL for the sync API (no trailing slash)

Why the VITE_ prefix? Vite only exposes environment variables prefixed with VITE_ to the browser bundle. Variables without this prefix will be undefined in your React code.

The variable is accessed in src/lib/api.ts via:

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;

Project Structure

src/
├── lib/
│   ├── types.ts        # All TypeScript interfaces & types
│   ├── utils.ts        # Pure helper functions (formatting, colors)
│   ├── api.ts          # Network calls — only file that uses fetch
│   └── mockData.ts     # Mock integrations, history, and conflict data
│
├── hooks/
│   ├── useSync.ts      # Sync flow state machine (5 phases)
│   └── useToast.ts     # Toast notification state
│
├── components/
│   ├── layout/
│   │   └── Header.tsx
│   └── ui/
│       ├── Button.tsx
│       ├── StatusBadge.tsx
│       ├── Toast.tsx
│       └── SyncPreviewModal.tsx
│
├── pages/
│   ├── IntegrationsPage.tsx        # /
│   ├── IntegrationDetailPage.tsx   # /integrations/:id
│   ├── SyncHistoryPage.tsx         # /integrations/:id/history
│   ├── VersionDetailPage.tsx       # /integrations/:id/history/:version
│   └── ConflictResolutionPage.tsx  # /integrations/:id/conflicts
│
├── App.tsx     # Router setup
└── main.tsx    # Entry point

Features

Integrations List (/)

  • Overview of all connected integrations
  • Status indicators: Synced, Syncing, Conflict, Error
  • Clickable stat cards to filter by status
  • Search by integration name
  • Shows last synced time, record count, version

Integration Detail (/integrations/:id)

  • Summary card with key metadata
  • Connection health indicators
  • Sync Now button calls the real API
  • Preview modal shows incoming changes before applying
  • Conflict and error banners with call-to-action

Sync History (/integrations/:id/history)

  • List of past sync events with timestamp, source, version
  • Expandable rows show event details
  • Links to version diff for events with recorded changes

Version Detail (/integrations/:id/history/:version)

  • Stats: added / updated / deleted / total
  • Field-level diff showing previous and new values
  • Color-coded by change type (ADD / UPDATE / DELETE)

Conflict Resolution (/integrations/:id/conflicts)

  • Side-by-side comparison of local vs. external values
  • Per-field selection with "Use This" toggle
  • Bulk "Accept All Local" / "Accept All External" actions
  • Progress bar tracking resolved vs. total fields
  • Merge button activates only when all fields are resolved

API

The Sync Now button calls:

GET {VITE_API_BASE_URL}/data/sync?application_id={integrationName}

All other data (integration list, history, conflicts) is mocked locally in src/lib/mockData.ts.

Error States Handled

Status Message Shown to User
400 Bad request — check integration configuration
404 Integration not found or misconfigured
500 Internal server error
502 Gateway error — integration client service unavailable

Design Decisions

Environment-based config — the API base URL lives in .env so switching between development, staging, and production requires no code changes, only a different .env value.

Preview before apply — Sync Now shows a modal with all incoming changes before committing them. In a bidirectional sync system, showing users what will change prevents accidental data loss.

State machine for sync flow — instead of boolean flags (isLoading, isError), the sync uses a discriminated union with phases: idle → loading → preview → applying → success/error. TypeScript narrows the type at each phase, preventing impossible states.

Single ResolutionMap for conflicts — all conflict decisions are stored as Record<fieldId, "local" | "external">. Progress, button state, and per-field indicators all derive from this one object — no duplicated state.

Strict layer separationlib/api.ts is the only file that calls fetch. Pages own state. Components are stateless. Hooks contain reusable logic.


Assumptions

  1. application_id query param uses the integration display name (e.g. "Slack", "HubSpot")
  2. Conflict resolution is simulated locally in production this would POST the resolution map to an endpoint
  3. Sync history and conflict data are mocked in production these would be API-driven
  4. The syncing status disables the Sync Now button (already in progress)

What to Try

Action Where
See conflict resolution UI Click HubSpot → Resolve Conflicts
Trigger real API call Click any integration → Sync Now
Browse version diffs Click Salesforce → View Sync History → View Changes
Test error state Click Zendesk
Test syncing state Click Stripe

About

**Web App Integration Sync Panel** for a B2B SaaS platform that connects to multiple external services (e.g., Salesforce, HubSpot, Stripe).

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors