An interactive proof-of-concept prepared for Turun Seudun Vuokravälitys Oy (TSVV) · Turku, Finland
A SAYANJALI NEXUS demonstration
Warning
This is a free custom demonstration / proof of concept, not a production integration with TSVV's systems. It is not connected to TSVV's real CRM, website, property portals, or internal databases. Every connector is a Demo / Simulated Integration, and every property record is fictional demo data. No claim of TSVV approval is made or implied.
git clone https://github.com/<your-username>/<your-repo>.git
cd syj-propertysync
npm install
npm run devThen open http://localhost:5173 — Finnish loads by default, no configuration needed. Full step-by-step instructions for every OS are in Installation — every device.
- Demo disclaimer
- 60-second quickstart
- What this demonstrates
- Live walkthrough (animated)
- System architecture
- The automation pipeline
- Data model
- Multilingual architecture
- Tech stack
- Project structure
- Installation — every device
- Running the project
- Building for production
- Pushing to GitHub
- Suggested demo recording script
- Troubleshooting
- Potential operational benefits
- Developer
- License & disclaimer
The core business proposition, made tangible:
Update a property once → automatically validate, normalize, and synchronize the information across every connected destination.
| Capability | Where to see it |
|---|---|
| 🏠 Live property portfolio around Turku, Kaarina, Raisio, Lieto, Paimio | Properties |
| ✏️ Edit a listing and trigger a full sync in one click | Properties → any listing → Edit & Sync Property |
| ⚙️ Animated, staged automation pipeline with live timing | Triggered from Save & Synchronize |
| ✅ Real validation engine (required fields, formats, missing data) | Validation Center |
| 🔤 Real normalization engine (city names, currency, units, phone/postal formats) | Pipeline run → Normalization section |
| 🧩 Duplicate detection with a confidence score | Pipeline run → Duplicate detection section |
| 🔌 Simulated connectors (CRM, Website, Portal, Email, Webhook, CSV, API) | Connectors |
| 🛠️ Controlled failure + one-click retry (error recovery) | Sync Center → DEMO-PAIMIO-5019 → Retry |
| 📜 Full before/after audit trail with correlation IDs | Audit Log |
| 📊 Portfolio-wide analytics | Analytics |
| 🌍 Six fully-translated languages, Finnish by default | Language switcher, top-right |
| ♻️ One-click reset back to seeded demo state | Settings → Reset demo, or the top strip |
Note
Everything runs entirely in the browser against an in-memory mock data layer — nothing to deploy, no database, no API keys, no backend to configure.
Tip
Record your own demo video by following the same steps — see the full script below. Just screen-record npm run dev in your browser; no cloud deployment needed.
Each stage below animates through Pending → Running → Completed (or Warning / Failed) in the live app, with a progress line that fills as stages complete:
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#E4EEEE', 'primaryTextColor':'#14213A', 'primaryBorderColor':'#1F6F78', 'lineColor':'#5B6B78', 'secondaryColor':'#F5F7F6', 'secondaryTextColor':'#14213A', 'secondaryBorderColor':'#DEE3E1', 'tertiaryColor':'#EEF0F3', 'tertiaryTextColor':'#14213A', 'tertiaryBorderColor':'#8A93A6', 'mainBkg':'#E4EEEE', 'clusterBkg':'#F5F7F6', 'clusterBorder':'#DEE3E1', 'edgeLabelBackground':'#F5F7F6', 'fontFamily':'Inter, Segoe UI, sans-serif', 'actorBkg':'#E4EEEE', 'actorBorder':'#1F6F78', 'actorTextColor':'#14213A', 'actorLineColor':'#8A93A6', 'signalColor':'#5B6B78', 'signalTextColor':'#14213A', 'labelBoxBkgColor':'#F5F7F6', 'labelBoxBorderColor':'#1F6F78', 'labelTextColor':'#14213A', 'loopTextColor':'#14213A', 'noteBkgColor':'#FBF0DD', 'noteTextColor':'#14213A', 'noteBorderColor':'#B9770E', 'activationBkgColor':'#E4EEEE', 'activationBorderColor':'#1F6F78' }}}%%
flowchart LR
A([📝 Change<br/>Detected]) --> B([✅ Data<br/>Validation])
B --> C([🔤 Data<br/>Normalization])
C --> D([🧩 Duplicate<br/>Detection])
D --> E([🗄️ Central Data<br/>Update])
E --> F([🔗 CRM<br/>Sync])
F --> G([🌐 Website<br/>Sync])
G --> H([🏢 Portal<br/>Sync])
H --> I([🔍 Verification])
I --> J([📜 Audit<br/>Log])
style A fill:#E4EEEE,stroke:#1F6F78,color:#14213A
style B fill:#E4EEEE,stroke:#1F6F78,color:#14213A
style C fill:#E4EEEE,stroke:#1F6F78,color:#14213A
style D fill:#FBF0DD,stroke:#B9770E,color:#14213A
style E fill:#E4EEEE,stroke:#1F6F78,color:#14213A
style F fill:#E4F4EC,stroke:#1E8E5A,color:#14213A
style G fill:#E4F4EC,stroke:#1E8E5A,color:#14213A
style H fill:#FBEAEA,stroke:#C33C3C,color:#14213A
style I fill:#E4F4EC,stroke:#1E8E5A,color:#14213A
style J fill:#E4F4EC,stroke:#1E8E5A,color:#14213A
(Colors mirror the live app: teal = neutral/running, green = completed, amber = warning, red = failed — exactly like the Portal Sync ⚠ example from the original brief.)
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#E4EEEE', 'primaryTextColor':'#14213A', 'primaryBorderColor':'#1F6F78', 'lineColor':'#5B6B78', 'secondaryColor':'#F5F7F6', 'secondaryTextColor':'#14213A', 'secondaryBorderColor':'#DEE3E1', 'tertiaryColor':'#EEF0F3', 'tertiaryTextColor':'#14213A', 'tertiaryBorderColor':'#8A93A6', 'mainBkg':'#E4EEEE', 'clusterBkg':'#F5F7F6', 'clusterBorder':'#DEE3E1', 'edgeLabelBackground':'#F5F7F6', 'fontFamily':'Inter, Segoe UI, sans-serif', 'actorBkg':'#E4EEEE', 'actorBorder':'#1F6F78', 'actorTextColor':'#14213A', 'actorLineColor':'#8A93A6', 'signalColor':'#5B6B78', 'signalTextColor':'#14213A', 'labelBoxBkgColor':'#F5F7F6', 'labelBoxBorderColor':'#1F6F78', 'labelTextColor':'#14213A', 'loopTextColor':'#14213A', 'noteBkgColor':'#FBF0DD', 'noteTextColor':'#14213A', 'noteBorderColor':'#B9770E', 'activationBkgColor':'#E4EEEE', 'activationBorderColor':'#1F6F78' }}}%%
graph TB
subgraph Browser["🖥️ Browser — everything runs here"]
subgraph UI["React UI Layer"]
Pages["Pages<br/>Dashboard · Properties · PropertyDetail<br/>SyncCenter · ValidationCenter · Connectors<br/>AuditLog · Analytics · Settings"]
Components["Shared Components<br/>PipelineVisualizer · StatusBadge<br/>ValidationList · NormalizationTable<br/>DuplicatePanel · LanguageSwitcher"]
end
subgraph State["Application State"]
Store["DemoStore<br/>(React Context + useReducer)<br/>properties · connectors · syncJobs · auditLog"]
I18n["I18nProvider<br/>(React Context)<br/>locale · t() · formatters"]
end
subgraph Services["Service / Engine Layer"]
Engine["engine.ts<br/>validateProperty()<br/>normalizeProperty()<br/>detectDuplicates()"]
Runner["usePipelineRunner.ts<br/>orchestrates the 10-stage<br/>automation pipeline"]
end
subgraph Mock["Mock Connector Layer (simulated)"]
CRM["CRM connector"]
Web["Website connector"]
Portal["Portal connector"]
Other["Email · Webhook · CSV · API"]
end
end
Pages --> Components
Pages --> Store
Pages --> I18n
Components --> I18n
PropertyDetail["PropertyDetail page"] -->|"Save & Synchronize"| Runner
Runner --> Engine
Runner --> Store
Runner -.->|"simulated calls"| CRM
Runner -.->|"simulated calls"| Web
Runner -.->|"simulated calls"| Portal
Store --> Other
style Mock fill:#E4EEEE,stroke:#1F6F78
Tip
Why this shape? Validation, normalization, duplicate detection, and the connector layer are kept in separate, isolated modules (src/services/engine.ts, src/services/usePipelineRunner.ts) specifically so a real backend (FastAPI + SQLite, per the original architecture brief) could later replace the mock layer without rewriting the UI.
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#E4EEEE', 'primaryTextColor':'#14213A', 'primaryBorderColor':'#1F6F78', 'lineColor':'#5B6B78', 'secondaryColor':'#F5F7F6', 'secondaryTextColor':'#14213A', 'secondaryBorderColor':'#DEE3E1', 'tertiaryColor':'#EEF0F3', 'tertiaryTextColor':'#14213A', 'tertiaryBorderColor':'#8A93A6', 'mainBkg':'#E4EEEE', 'clusterBkg':'#F5F7F6', 'clusterBorder':'#DEE3E1', 'edgeLabelBackground':'#F5F7F6', 'fontFamily':'Inter, Segoe UI, sans-serif', 'actorBkg':'#E4EEEE', 'actorBorder':'#1F6F78', 'actorTextColor':'#14213A', 'actorLineColor':'#8A93A6', 'signalColor':'#5B6B78', 'signalTextColor':'#14213A', 'labelBoxBkgColor':'#F5F7F6', 'labelBoxBorderColor':'#1F6F78', 'labelTextColor':'#14213A', 'loopTextColor':'#14213A', 'noteBkgColor':'#FBF0DD', 'noteTextColor':'#14213A', 'noteBorderColor':'#B9770E', 'activationBkgColor':'#E4EEEE', 'activationBorderColor':'#1F6F78' }}}%%
graph LR
A["React UI"] -->|"today"| B["In-memory mock store<br/>(this repo)"]
A -.->|"future"| C["REST API<br/>FastAPI + Pydantic"]
C -.-> D["SQLAlchemy + SQLite"]
C -.-> E["Real CRM / Website /<br/>Portal integrations"]
style B fill:#E4F4EC,stroke:#1E8E5A
style C fill:#EEF0F3,stroke:#8A93A6,stroke-dasharray: 5 5
style D fill:#EEF0F3,stroke:#8A93A6,stroke-dasharray: 5 5
style E fill:#EEF0F3,stroke:#8A93A6,stroke-dasharray: 5 5
What actually happens when you click "Save & Synchronize":
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#E4EEEE', 'primaryTextColor':'#14213A', 'primaryBorderColor':'#1F6F78', 'lineColor':'#5B6B78', 'secondaryColor':'#F5F7F6', 'secondaryTextColor':'#14213A', 'secondaryBorderColor':'#DEE3E1', 'tertiaryColor':'#EEF0F3', 'tertiaryTextColor':'#14213A', 'tertiaryBorderColor':'#8A93A6', 'mainBkg':'#E4EEEE', 'clusterBkg':'#F5F7F6', 'clusterBorder':'#DEE3E1', 'edgeLabelBackground':'#F5F7F6', 'fontFamily':'Inter, Segoe UI, sans-serif', 'actorBkg':'#E4EEEE', 'actorBorder':'#1F6F78', 'actorTextColor':'#14213A', 'actorLineColor':'#8A93A6', 'signalColor':'#5B6B78', 'signalTextColor':'#14213A', 'labelBoxBkgColor':'#F5F7F6', 'labelBoxBorderColor':'#1F6F78', 'labelTextColor':'#14213A', 'loopTextColor':'#14213A', 'noteBkgColor':'#FBF0DD', 'noteTextColor':'#14213A', 'noteBorderColor':'#B9770E', 'activationBkgColor':'#E4EEEE', 'activationBorderColor':'#1F6F78' }}}%%
sequenceDiagram
actor U as Demo Admin
participant UI as PropertyDetail page
participant PR as usePipelineRunner
participant EN as engine.ts
participant ST as DemoStore
participant CN as Simulated Connectors
U->>UI: Edit rent, availability, description, features
U->>UI: Click "Save & Synchronize"
UI->>ST: set syncStatus = "syncing"
UI->>PR: run(nextProperty, previousProperty, options)
PR->>PR: Stage 1 — Change Detected ✓
PR->>EN: validateProperty(property)
EN-->>PR: pass / warning / error per field
alt has blocking error
PR->>ST: syncStatus = "failed"
PR-->>UI: overallStatus = "failed"
else validation passes (warnings allowed)
PR->>EN: normalizeProperty(property)
EN-->>PR: original → normalized values + rule
PR->>EN: detectDuplicates(property, allProperties)
EN-->>PR: candidates + confidence score
PR->>ST: Stage — Central Data Update ✓
PR->>CN: Stage — CRM Sync ✓
PR->>CN: Stage — Website Sync ✓
PR->>CN: Stage — Portal Sync ✓ (or ⚠ / ✗ if simulated failure)
PR->>PR: Stage — Verification ✓
PR->>ST: UPDATE_PROPERTY (commit edits)
PR->>ST: ADD_AUDIT_ENTRY (before/after + correlation ID)
PR->>ST: ADD_SYNC_JOB × 3 (crm, website, portal)
PR-->>UI: overallStatus = "success"
end
UI-->>U: "Synchronization completed successfully."
| # | Stage | Simulated duration | Can warn? | Can fail? |
|---|---|---|---|---|
| 1 | Change Detected | ~0.2s | – | – |
| 2 | Data Validation | ~0.4s | ✅ | ✅ |
| 3 | Data Normalization | ~0.3s | – | – |
| 4 | Duplicate Detection | ~0.3s | ✅ (confidence ≥ 85%) | – |
| 5 | Central Data Update | ~0.5s | – | – |
| 6 | CRM Sync | ~0.8s | – | – |
| 7 | Website Sync | ~0.6s | – | – |
| 8 | Portal Sync | ~0.9s | – | ✅ (toggle "simulate failure" to demo) |
| 9 | Verification | ~0.4s | – | – |
| 10 | Audit Log | ~0.2s | – | – |
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#E4EEEE', 'primaryTextColor':'#14213A', 'primaryBorderColor':'#1F6F78', 'lineColor':'#5B6B78', 'secondaryColor':'#F5F7F6', 'secondaryTextColor':'#14213A', 'secondaryBorderColor':'#DEE3E1', 'tertiaryColor':'#EEF0F3', 'tertiaryTextColor':'#14213A', 'tertiaryBorderColor':'#8A93A6', 'mainBkg':'#E4EEEE', 'clusterBkg':'#F5F7F6', 'clusterBorder':'#DEE3E1', 'edgeLabelBackground':'#F5F7F6', 'fontFamily':'Inter, Segoe UI, sans-serif', 'actorBkg':'#E4EEEE', 'actorBorder':'#1F6F78', 'actorTextColor':'#14213A', 'actorLineColor':'#8A93A6', 'signalColor':'#5B6B78', 'signalTextColor':'#14213A', 'labelBoxBkgColor':'#F5F7F6', 'labelBoxBorderColor':'#1F6F78', 'labelTextColor':'#14213A', 'loopTextColor':'#14213A', 'noteBkgColor':'#FBF0DD', 'noteTextColor':'#14213A', 'noteBorderColor':'#B9770E', 'activationBkgColor':'#E4EEEE', 'activationBorderColor':'#1F6F78' }}}%%
erDiagram
PROPERTY ||--o{ SYNC_JOB : generates
PROPERTY ||--o{ AUDIT_ENTRY : generates
PROPERTY }o--o{ PROPERTY : "duplicate candidate of"
CONNECTOR ||--o{ SYNC_JOB : handles
PROPERTY {
string id PK
string address
string city
string postalCode
string propertyType
int bedrooms
int monthlyRent
string availabilityDate
object descriptions "fi, en, de, pl, es, pt"
object features
string syncStatus "synced or pending or syncing or failed or warning"
}
CONNECTOR {
string id PK "crm, website, portal, email, webhook, csv, api"
string status "connected or degraded or disconnected"
int successRate
int latencyMs
}
SYNC_JOB {
string id PK
string propertyId FK
string connectorId FK
string status "queued, running, completed, failed, retrying"
string errorMessage
}
AUDIT_ENTRY {
string id PK
string propertyId FK
string action
string previousValue
string newValue
string correlationId
string result "success or failure"
}
Finnish is the default language for the TSVV demo. Every user-facing string — navigation, forms, validation messages, pipeline statuses, connector statuses, audit entries, analytics, settings — comes from a translation dictionary.
Important
There are no hardcoded UI strings anywhere in the application — this was verified line-by-line against the full translation key set before release.
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#E4EEEE', 'primaryTextColor':'#14213A', 'primaryBorderColor':'#1F6F78', 'lineColor':'#5B6B78', 'secondaryColor':'#F5F7F6', 'secondaryTextColor':'#14213A', 'secondaryBorderColor':'#DEE3E1', 'tertiaryColor':'#EEF0F3', 'tertiaryTextColor':'#14213A', 'tertiaryBorderColor':'#8A93A6', 'mainBkg':'#E4EEEE', 'clusterBkg':'#F5F7F6', 'clusterBorder':'#DEE3E1', 'edgeLabelBackground':'#F5F7F6', 'fontFamily':'Inter, Segoe UI, sans-serif', 'actorBkg':'#E4EEEE', 'actorBorder':'#1F6F78', 'actorTextColor':'#14213A', 'actorLineColor':'#8A93A6', 'signalColor':'#5B6B78', 'signalTextColor':'#14213A', 'labelBoxBkgColor':'#F5F7F6', 'labelBoxBorderColor':'#1F6F78', 'labelTextColor':'#14213A', 'loopTextColor':'#14213A', 'noteBkgColor':'#FBF0DD', 'noteTextColor':'#14213A', 'noteBorderColor':'#B9770E', 'activationBkgColor':'#E4EEEE', 'activationBorderColor':'#1F6F78' }}}%%
graph TD
A["src/i18n/locales/en.ts<br/>master key set — 307 keys"] --> B["fi.ts 🇫🇮 default"]
A --> C["en.ts 🇬🇧"]
A --> D["de.ts 🇩🇪"]
A --> E["pl.ts 🇵🇱"]
A --> F["es.ts 🇪🇸"]
A --> G["pt.ts 🇵🇹"]
B & C & D & E & F & G --> H["I18nProvider<br/>React Context"]
H --> I["t key, params"]
H --> J["formatCurrency<br/>Intl.NumberFormat"]
H --> K["formatDate / formatDateTime<br/>Intl.DateTimeFormat"]
I --> L["Every page & component"]
J --> L
K --> L
style A fill:#14213A,color:#fff
style H fill:#1F6F78,color:#fff
Each property record also carries per-language descriptions (descriptions.fi, descriptions.en, …), previewable side-by-side in the Multilingual listing preview panel on any property's detail page — demonstrating the commercial value of centralized, multilingual listing data.
Language selection persists across sessions and instantly re-renders the entire UI, including number, date, and currency formatting per locale.
| Language | Code | Status |
|---|---|---|
| Suomi (Finnish) | fi |
🟢 Default |
| English | en |
🟢 Complete |
| Deutsch | de |
🟢 Complete |
| Polski | pl |
🟢 Complete |
| Español | es |
🟢 Complete |
| Português | pt |
🟢 Complete |
| Layer | Technology | Why |
|---|---|---|
| UI framework | React 18 + TypeScript | Type-safe, component-driven, industry standard |
| Build tool | Vite 5 | Instant HMR, zero-config, fast cold starts |
| Styling | Tailwind CSS | Custom Nordic design tokens — see tailwind.config.js |
| Routing | react-router-dom (HashRouter) |
Works from any static host, no server routing config |
| Charts | Recharts | Composable, React-native charting |
| Icons | lucide-react | Consistent, tree-shakeable icon set |
| State | React Context + useReducer |
No external state library needed at this scale |
| Backend | None required | In-memory mock service layer models the REST API surface from the original brief |
syj-propertysync/
├── index.html
├── package.json
├── vite.config.ts
├── tailwind.config.js
├── tsconfig.json
└── src/
├── main.tsx # entry point
├── App.tsx # router + providers
├── types.ts # shared TypeScript types
├── index.css # Tailwind + design system
│
├── data/ # seed demo data
│ ├── properties.ts # 7 fictional Turku-region listings
│ ├── connectors.ts # 7 simulated connectors
│ └── auditSeed.ts # seed audit trail
│
├── i18n/
│ ├── index.tsx # I18nProvider, useI18n(), formatters
│ └── locales/ # en · fi · de · pl · es · pt (307 keys each)
│
├── services/
│ ├── engine.ts # validation / normalization / duplicate detection
│ ├── usePipelineRunner.ts # orchestrates the 10-stage pipeline
│ └── demoStore.tsx # global state (Context + useReducer)
│
├── components/
│ ├── layout/ Nav.tsx · TopBar.tsx
│ ├── PipelineVisualizer.tsx # the signature animated stepper
│ ├── ValidationList.tsx · NormalizationTable.tsx · DuplicatePanel.tsx
│ ├── StatusBadge.tsx · KpiCard.tsx · LanguageSwitcher.tsx
│ └── DemoTopStrip.tsx · Footer.tsx · Layout.tsx
│
└── pages/
├── Welcome.tsx # guided 9-step demo intro
├── Dashboard.tsx · Properties.tsx · PropertyDetail.tsx
├── SyncCenter.tsx · ValidationCenter.tsx · Connectors.tsx
└── AuditLog.tsx · Analytics.tsx · Settings.tsx
| Requirement | Minimum version | Check with |
|---|---|---|
| Node.js | 18.x or newer (20.x recommended) | node -v |
| npm | 9.x or newer (bundled with Node) | npm -v |
| Git (optional, for GitHub) | any recent version | git --version |
| A modern browser | Chrome, Edge, Firefox, or Safari | — |
Note
No database, no Python, no Docker, no paid accounts, no API keys — ever.
Option A — official installer (recommended for most people)
- Download Node.js LTS from nodejs.org and run the installer (accept all defaults).
- Open PowerShell and confirm:
node -v npm -v
- Unzip the project (right-click the
.zip→ Extract All…), then:cd path\to\syj-propertysync npm install npm run dev - Open the URL shown in the terminal — typically http://localhost:5173.
Option B — winget
winget install OpenJS.NodeJS.LTSThen follow steps 2–4 from Option A above.
Option C — WSL (Windows Subsystem for Linux)
wsl --install # one-time, then restart
# inside your WSL Ubuntu shell:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
cd /mnt/c/path/to/syj-propertysync
npm install && npm run devOption A — Homebrew (recommended)
# if you don't have Homebrew yet:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install node
cd ~/path/to/syj-propertysync
npm install
npm run devOption B — official installer
- Download the macOS installer from nodejs.org and run it.
- Open Terminal (⌘ + Space → "Terminal") and run:
cd ~/Downloads/syj-propertysync npm install npm run dev
- Open http://localhost:5173 in Safari or Chrome.
Debian / Ubuntu
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejsFedora
sudo dnf install nodejsArch
sudo pacman -S nodejs npmThen, on any distro:
cd ~/syj-propertysync
npm install
npm run devOpen http://localhost:5173.
You can't run a Vite dev server natively on iOS or Android, but you can run this project entirely in the cloud from a phone or tablet browser — no laptop required:
| Option | Steps |
|---|---|
| GitHub Codespaces | Push the repo to GitHub (see below) → open it in the GitHub mobile app or a mobile browser → Code → Codespaces → Create codespace → run npm install && npm run dev in the built-in terminal → Codespaces auto-forwards port 5173 with a public preview link. |
| StackBlitz | Go to stackblitz.com → Import from GitHub → paste your repo URL → it installs and runs automatically in-browser. |
| Replit | Go to replit.com → Create Repl → Import from GitHub → paste your repo URL → click Run. |
| Chromebook | Chromebooks with Linux (Crostini) enabled can follow the Linux steps directly. |
All three cloud options give you a shareable public URL — useful if you want to record your demo video straight from a tablet.
npm install # first time only
npm run dev # starts Vite on http://localhost:5173Vite supports hot module reloading — edits to any file appear instantly without a full page reload. Stop the server anytime with Ctrl+C.
npm run build # outputs static files to dist/
npm run preview # serves the built dist/ folder locally, for a final checkThe dist/ folder is fully static and can be hosted anywhere — GitHub Pages, Netlify, Vercel, S3, or any plain web server — since the app uses HashRouter and requires no server-side routing configuration.
git init
git add .
git commit -m "SYJ PropertySync — interactive demo for TSVV"
git branch -M main
git remote add origin https://github.com/<your-username>/<your-repo>.git
git push -u origin main%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#E4EEEE', 'primaryTextColor':'#14213A', 'primaryBorderColor':'#1F6F78', 'lineColor':'#5B6B78', 'secondaryColor':'#F5F7F6', 'secondaryTextColor':'#14213A', 'secondaryBorderColor':'#DEE3E1', 'tertiaryColor':'#EEF0F3', 'tertiaryTextColor':'#14213A', 'tertiaryBorderColor':'#8A93A6', 'mainBkg':'#E4EEEE', 'clusterBkg':'#F5F7F6', 'clusterBorder':'#DEE3E1', 'edgeLabelBackground':'#F5F7F6', 'fontFamily':'Inter, Segoe UI, sans-serif', 'actorBkg':'#E4EEEE', 'actorBorder':'#1F6F78', 'actorTextColor':'#14213A', 'actorLineColor':'#8A93A6', 'signalColor':'#5B6B78', 'signalTextColor':'#14213A', 'labelBoxBkgColor':'#F5F7F6', 'labelBoxBorderColor':'#1F6F78', 'labelTextColor':'#14213A', 'loopTextColor':'#14213A', 'noteBkgColor':'#FBF0DD', 'noteTextColor':'#14213A', 'noteBorderColor':'#B9770E', 'activationBkgColor':'#E4EEEE', 'activationBorderColor':'#1F6F78' }}}%%
graph LR
A["Your machine"] -->|"git init / add / commit"| B["Local repo"]
B -->|"git push"| C["GitHub"]
C -->|"git clone"| D["Any teammate's machine"]
C -->|"Codespaces / StackBlitz"| E["Any browser, any device"]
style C fill:#14213A,color:#fff
Anyone who clones the repo can run npm install && npm run dev to get the exact same working demo — nothing else to configure.
- Open the app → Welcome screen → Start interactive demo.
- Go to Properties, open
DEMO-TURKU-1042(Linnankatu 14 A 3, Turku). - Click Edit & Sync Property — change the rent from €1,250 to €1,295, update availability, tick Sauna, edit the description.
- Click Save & Synchronize and watch the pipeline run stage by stage.
- Scroll down to see validation results, normalization changes, and duplicate detection.
- Go to Sync Center → find the pre-seeded failed job on
DEMO-PAIMIO-5019→ click Retry to show error recovery. - Visit Connectors, Audit Log, and Analytics to show the rest of the platform.
- Switch the language selector (top right) — watch the UI fully translate — then open the property's Multilingual listing preview.
- Visit Settings to show Potential operational benefits and the Reset Demo control.
| Symptom | Fix |
|---|---|
npm install fails with permission errors (macOS/Linux) |
Avoid sudo npm install; fix npm's default directory permissions, or use nvm to manage Node per-user. |
| Port 5173 already in use | Run npm run dev -- --port 5174, or stop the process using that port. |
Blank page after npm run build && npm run preview |
Open the URL npm run preview prints — don't open index.html directly via file://. |
| Styles look unstyled / broken | rm -rf node_modules package-lock.json && npm install |
command not found: npm |
Node.js isn't installed or isn't on your PATH — revisit Installation — every device for your OS. |
Illustrative example. Actual results depend on system architecture and workflow.
- ✅ Reduced manual data entry
- ✅ Fewer synchronization errors
- ✅ Centralized property data
- ✅ Faster listing updates
- ✅ Improved data consistency
- ✅ Better auditability
- ✅ Multilingual publishing capability
- ✅ Real-time synchronization visibility
Syed Ali Hasan Moosavi AI Engineer · Full Stack Developer · Blockchain Developer Founder & Managing Director, SAYANJALI NEXUS PRIVATE LIMITED
Built end-to-end — architecture, UI/UX, i18n, validation/normalization/duplicate-detection engine, and the automation pipeline — as a custom proof-of-concept for TSVV.
This is a demonstration system built as a free custom proof-of-concept. It is not connected to TSVV's production CRM, website, property portals, or internal databases, and does not imply TSVV's endorsement or approval. All property records, agents, and connector data are fictional and clearly labeled as demo data throughout the application.
SYJ PropertySync · a SAYANJALI NEXUS demonstration Interactive Proof of Concept · Demo Mode
English · Suomi · Deutsch · Polski · Español · Português