diff --git a/README.md b/README.md index 6ad148c..8f1d5c7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,48 @@ # Mashov Parent Command Center -A mobile-first household dashboard built on top of the [Mashov](https://web.mashov.info) Israeli school API. Gives parents a single, aggregated briefing screen: what changed across all children, which child needs attention, and what school messages are waiting. +> The school portal a busy parent can actually keep up with — every kid's messages, homework & behavior in one daily glance, on top of Israel's [Mashov](https://web.mashov.info) system. + +**🔗 Live app: [mashov.mitya.dev](https://mashov.mitya.dev)** + +A mobile-first, Hebrew/RTL household dashboard that sits on top of the Mashov Israeli school API. Sign in once with your existing Mashov account and get a single, aggregated briefing across all your children — what changed, who needs attention, and which school messages are waiting. + +## What is this? + +The school portal already has all your kids' data — it's just exhausting to use day to day. You log in, navigate a heavy legacy interface, switch context between children, and reassemble "what changed" from a half-dozen screens. Every time. + +This app removes that. It's a calm, low-click **summary and triage layer** over Mashov that answers one question fast: *what changed, who it affects, and what needs attention right now.* The goal is to understand your whole household's school status in **under 30 seconds**, without hunting through menus. + +## What you get + +- **📋 One daily briefing** — a single mixed feed of what changed across all your children, newest first. +- **🔔 Attention first** — surfaces who needs you: new unread messages, behavior and attendance events, and recently assigned homework. +- **✉️ Clean inbox** — read school messages without the legacy clutter, with exact unread counts and attachment indicators. +- **🧒 Per-child workspace** — homework history, a behavior & attendance timeline, subjects & teachers, and the weekly timetable for each child. +- **🔑 No new account** — sign in with your existing Mashov school credentials; nothing extra to remember. +- **📱 Mobile-first & RTL** — designed for the phone and built for Hebrew (including mixed Hebrew/English) from the ground up. +- **🔒 Private by default** — each parent gets their own read/handled state, and stored credentials are encrypted at rest. + +## How it works + +1. **Sign in** — pick your school and log in with your Mashov username and password (the school year is detected automatically). +2. **First sync** — the app pulls your household's latest data once in the background. +3. **Glance** — open it whenever, see what's new across all your kids, and drill into a single message or child only if you need to. + +## Good to know + +This is a reading and triage companion, **not** a rebuild of the school portal. It's read-only by design and intentionally scoped — it does **not** include grades, report cards, or performance analytics. Think "daily briefing," not "admin portal." + +## Tech stack + +| Layer | Tech | +|-------|------| +| Frontend | Next.js + TypeScript (mobile-first, RTL) | +| Backend | NestJS + TypeScript + per-household sync engine | +| Database | PostgreSQL | +| Cache / rate limiting | Redis | +| Infrastructure | Docker Compose | + +--- ## Prerequisites diff --git a/docker-compose.yml b/docker-compose.yml index a256b87..2b1ca0c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -79,6 +79,7 @@ services: environment: BACKEND_URL: http://backend:3000 NEXT_PUBLIC_API_URL: /api/v1 + NEXT_PUBLIC_GA_MEASUREMENT_ID: ${NEXT_PUBLIC_GA_MEASUREMENT_ID:-} ENABLE_E2E_AUTH_BYPASS: "1" ports: - "3000:3000" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7958943..726320b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "mashov-frontend", "version": "0.1.0", "dependencies": { + "@next/third-parties": "^16.2.4", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.4", @@ -986,6 +987,19 @@ "node": ">= 10" } }, + "node_modules/@next/third-parties": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/third-parties/-/third-parties-16.2.4.tgz", + "integrity": "sha512-FhDDX02cAr0WIo3la+QHP3XaAAV6twCfFk/y8pHikFT8MHwNpB3XgEdaT0omLrIWBORhM5wkbbUJFq+pBqZzmw==", + "license": "MIT", + "dependencies": { + "third-party-capital": "1.0.20" + }, + "peerDependencies": { + "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7093,6 +7107,12 @@ "node": ">=0.8" } }, + "node_modules/third-party-capital": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/third-party-capital/-/third-party-capital-1.0.20.tgz", + "integrity": "sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==", + "license": "ISC" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/frontend/package.json b/frontend/package.json index ef15847..cd336c6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@next/third-parties": "^16.2.4", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-dialog": "^1.1.4", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 369b853..0d35041 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata, Viewport } from 'next'; import { Assistant } from 'next/font/google'; +import { GoogleAnalytics } from '@next/third-parties/google'; import './globals.css'; import Providers from './providers'; @@ -96,6 +97,9 @@ export default function RootLayout({ children }: RootLayoutProps) { {children} + {process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID && ( + + )} ); diff --git a/tests/e2e/seo.spec.ts b/tests/e2e/seo.spec.ts index fa6a961..4518c83 100644 --- a/tests/e2e/seo.spec.ts +++ b/tests/e2e/seo.spec.ts @@ -22,13 +22,13 @@ test.describe('SEO metadata and crawler policy', () => { expect(text).toContain('Disallow: /auth/syncing'); expect(text).toContain('Allow: /auth/sign-in'); expect(text).toContain('Allow: /'); - expect(text).toContain('Sitemap: https://mashov.mitya/sitemap.xml'); + expect(text).toContain('Sitemap: https://mashov.mitya.dev/sitemap.xml'); }); test('sitemap.xml includes only the public sign-in URL', async ({ request }) => { const xml = await getResponseBody(request, '/sitemap.xml'); - expect(xml).toContain('https://mashov.mitya/auth/sign-in'); + expect(xml).toContain('https://mashov.mitya.dev/auth/sign-in'); expect(xml).not.toContain('/home'); expect(xml).not.toContain('/children'); expect(xml).not.toContain('/inbox'); @@ -38,11 +38,11 @@ test.describe('SEO metadata and crawler policy', () => { test('sign-in page exposes canonical, description, and social tags', async ({ request }) => { const html = await getResponseBody(request, '/auth/sign-in'); - expect(html).toContain('כניסה | מרכז הורים'); - expect(html).toContain('name="description" content="ממשק מאוחד לנתוני בית הספר — ציונים, שיעורי בית, ומסרים"'); - expect(html).toContain('rel="canonical" href="https://mashov.mitya/auth/sign-in"'); - expect(html).toContain('property="og:title" content="מרכז הורים"'); - expect(html).toContain('property="og:description" content="ממשק מאוחד לנתוני בית הספר"'); + expect(html).toContain('כניסה למרכז הורים | מרכז הורים'); + expect(html).toContain('name="description" content="התחברו למרכז הורים — צפו בציונים, שיעורי בית והודעות של ילדכם דרך מערכת משוב"'); + expect(html).toContain('rel="canonical" href="https://mashov.mitya.dev/auth/sign-in"'); + expect(html).toContain('property="og:title" content="כניסה | מרכז הורים"'); + expect(html).toContain('property="og:description" content="התחברו למרכז הורים — צפו בציונים, שיעורי בית והודעות של ילדכם דרך מערכת משוב"'); expect(html).toContain('name="twitter:card" content="summary"'); expect(html).toContain('name="twitter:title" content="מרכז הורים"'); });