Arvutiabi, Linuxile üleminek ja kontrollitud kasutatud arvutid – üle Eesti.
LinuxAbi Eesti is a nationwide Estonian Web2 platform concept that connects ordinary computer users with independent local helpers, and offers inspected refurbished business laptops with Linux Mint preinstalled. It also contains a small community knowledge area where visitors can read and ask questions.
The editorial voice and every user-facing string are Estonian. The audience is not Linux enthusiasts: it is people moving away from Windows, people who need general computer help, and people looking for an affordable reliable laptop.
This repository is a fully functional demonstration, not a live service.
- Every provider, review, question, laptop, price, warranty and transaction is fictional mock data. Locations are real Estonian cities; the businesses and people are invented.
- Nothing is transmitted. Forms validate and store their input only in the
visitor's own browser (
localStorage). No email is sent, no request reaches a provider, no order is placed and no payment is processed. - There is no backend, no database, no accounts and no analytics.
- Every form explicitly tells the visitor that the action was a demo action.
- A global demo notice is always visible until the visitor dismisses it.
Do not present this data as real businesses, and do not deploy it publicly without keeping the demo notice in place.
| Area | Choice |
|---|---|
| Framework | Next.js 16.3.5 (App Router, Turbopack, output: "export") |
| UI | React 19, TypeScript strict mode |
| Styling | Tailwind CSS 4 (CSS-first @theme tokens in src/app/globals.css) |
| Icons | lucide-react |
| Map | Leaflet 1.9 + React Leaflet 5, OpenStreetMap-compatible tiles |
| Unit tests | Vitest (jsdom) |
| End-to-end tests | Playwright (system Chrome channel) |
No proprietary backend, no paid APIs and no API keys are required.
- Node.js 20.9+ (Node 22 LTS recommended)
- npm 10+
- Optional: Docker for container deployment
- Optional: Google Chrome installed locally for the Playwright e2e suite
(the config uses the
chromechannel instead of downloading a browser)
npm install
cp .env.example .env.local # optional – defaults work without itnpm run dev # http://localhost:3000The build exports every mock route to out/. That folder is the whole
application: Cloudflare Pages serves it directly and no Node process is
required at runtime. npm run start is only a local preview helper for the
exported files (clean URLs and the exported 404 page):
npm run build
npm run start # http://localhost:3000
PORT=8080 npm run start # custom port
HOSTNAME=127.0.0.1 npm run startnpm run typecheck # next typegen + tsc --noEmit
npm run lint # eslint .
npm run test # vitest run (unit tests)
npm run test:e2e # playwright test (builds and starts the app automatically)
npm run check # typecheck + lint + test + build
npm run art:laptops # regenerate the local laptop SVG artworknpm run check is the gate used before considering a change complete. The
Playwright suite is separate because it is slower: it builds the app and drives a
real browser. It contains two specs:
e2e/demo-flows.spec.ts— the key visitor flows (homepage search, provider filters and profiles, demo service request, laptop filtering/detail/inquiry, Q&A, creating demo content, mobile navigation, 404).e2e/accessibility.spec.ts— automated WCAG 2.2 A/AA checks (axe-core) on every page. All checked pages currently report zero violations.
If Chrome is not installed, use the bundled browser instead:
npx playwright install chromium
PLAYWRIGHT_CHANNEL= npm run test:e2eTo iterate against an already running server:
npm run build && PORT=3100 npm run start
PLAYWRIGHT_SKIP_WEBSERVER=1 E2E_BASE_URL=http://127.0.0.1:3100 npm run test:e2eConnect the GitHub repository and select production branch main:
- Project name:
linuxabi-eesti(if available) - Framework: Next.js (Static HTML Export)
- Build command:
npm run build - Output directory:
out - Build environment:
NODE_VERSION=22,NEXT_PUBLIC_SITE_URL=https://linuxabi-eesti.pages.dev
Use the assigned Pages URL if the requested name is unavailable. Public values
must be set before building. Pushes to main then publish automatically.
No server runtime is required. Pages serves the static files in out/
(including the .txt RSC payloads the App Router uses for client-side
navigation); the build is a plain folder upload.
Mock detail routes use generateStaticParams with dynamicParams = false.
Query-string state (directory and marketplace filters, /abi/soov
preselection) is resolved in the browser with useSearchParams, so deep links
such as /abi?asukoht=Tartu keep working. Visitor-created questions use
/kusimused/kohalik?slug=… and the existing localStorage viewer, so new
questions also survive page refresh on a static host.
npm run start is a convenience helper that serves the exported out/ directory
locally. It is not used by Cloudflare Pages and no Node process is needed in
production.
npm ci
npm run build
PORT=3000 npm run start # http://localhost:3000Because the output is a plain folder of static files, any static host or web
server (nginx, Caddy, Apache, object storage) can serve out/ directly.
docker build -t linuxabi-eesti:demo .
docker run --rm -p 3000:3000 linuxabi-eesti:demoor:
docker compose up --buildNEXT_PUBLIC_* values are inlined at build time, so pass them as build args:
docker build \
--build-arg NEXT_PUBLIC_SITE_URL=https://example.ee \
-t linuxabi-eesti:demo .The image is a multi-stage Alpine build that runs as a non-root user and only
serves the exported out/ directory.
src/
app/ App Router routes (see docs/ARCHITECTURE.md)
components/
brand/ Wordmark / logo mark
forms/ Service request, laptop inquiry, question, provider signup
home/ Homepage search bar
laptops/ Marketplace cards, filters, gallery, marketplace view
layout/ Header, mobile nav, footer, demo banner, page shell
providers/ Provider cards/filters/directory/map/reviews
questions/ Question list, thread, answer form
ui/ Small reusable primitives (Button, Badge, Rating, …)
data/ Authored mock datasets (providers, laptops, reviews, …)
lib/
repositories/ Data-access contracts + mock implementations
filters.ts Provider/laptop filtering, sorting, scoring
geo.ts Haversine distance helpers
query.ts URL <-> filter mapping (deep-linkable searches)
storage.ts Safe, namespaced localStorage helpers
demo-store.ts External store for visitor-created demo content
types.ts Domain model
docs/
ARCHITECTURE.md Design decisions, boundaries, data flow
PRODUCTION.md What must change to turn the demo into a real service
e2e/ Playwright end-to-end flows
public/laptops/ Generated local laptop illustrations (SVG)
scripts/ Laptop art generator, static preview helper
All datasets live under src/data/ and are typed by src/lib/types.ts:
- 20 providers covering all 15 Estonian counties (
src/data/providers.ts). Real city coordinates, completely invented identities. Provider ratings are never stored on the provider record — they are derived from the review dataset insrc/lib/repositories/mock.ts, so a card, a profile and the review list can never disagree. - 60 reviews with varied ratings (roughly 3.4–5.0), titles, bodies, service
types, dates and occasional provider responses (
src/data/reviews.ts). - 20 laptops — ThinkPad, Latitude, EliteBook, ProBook and similar
business-class machines, mostly with Linux Mint 22.3 (Cinnamon or XFCE), one
dual-boot unit (
src/data/laptops.ts). Prices, battery health, cosmetic grades and warranty periods vary deliberately. - 20 questions / 30 answers across 10 categories, three intentionally
unanswered so the "vastamata" filter is meaningful (
src/data/questions.ts). - Locations — Estonian cities and counties with approximate coordinates
(
src/data/locations.ts).
Names, phone numbers and emails are avoided on purpose: profiles use obviously fictional contact placeholders and push visitors toward the request form instead of publishing details that could accidentally belong to a real person.
Product imagery is not downloaded from the internet. npm run art:laptops
generates stable, repository-local SVG illustrations (three views per laptop) so
the marketplace looks believable without external dependencies or licensing risk.
Map settings are isolated in src/lib/map-config.ts so the tile provider can
be changed without touching a single map component:
MAP_TILE_URL // NEXT_PUBLIC_MAP_TILE_URL, defaults to tile.openstreetmap.org
MAP_TILE_ATTRIBUTION
MAP_MAX_ZOOM
MAP_SELECTED_ZOOMLeaflet is imported only through a dynamically loaded, client-only component
(ProviderMap → ProviderMapInner with ssr: false), so it never ships to
routes that do not render a map.
The demo uses the public OpenStreetMap tile server. That is fine for a low-traffic demonstration with visible attribution, but a real commercial or high-traffic deployment must select its own tile provider or self-hosted tiles according to that provider's usage policy (no bulk downloading, no offline prefetching, respect normal browser caching). No form contents or personal data are ever sent to map services.
Only interactive demo state is persisted, always under a namespaced prefix
(linuxabi-eesti.v1.):
| Key | Contents |
|---|---|
…saved-providers / …saved-laptops |
Visitor's saved (favourite) items |
…reviews |
Demo reviews the visitor added |
…questions / …answers |
Demo questions and answers the visitor added |
…service-requests |
The last demo service request (not transmitted) |
…notice-dismissed |
Whether the global demo notice was hidden |
Implementation notes:
- All access goes through
src/lib/storage.ts, which probes availability and degrades to safe no-ops during server rendering, in private browsing or when storage is full. - Visitor-created content is read through an external store
(
src/lib/demo-store.ts) consumed withuseSyncExternalStore. Server render and first client render see the same empty state, so hydration never mismatches. - User text is rendered as normal React text nodes — never with
dangerouslySetInnerHTML. - "Lähtesta demoandmed" (in the footer) removes exactly the keys above and nothing else on the origin.
Pages never import the datasets directly; they go through repository interfaces
in src/lib/repositories/types.ts, resolved in src/lib/repositories/index.ts.
To introduce a real backend:
- Implement the same interfaces (
ProviderRepository,LaptopRepository,ReviewRepository,QuestionRepository) against your API, PostgreSQL or Supabase client. - Change the single wiring point in
src/lib/repositories/index.ts. - Keep the functions returning the domain types from
src/lib/types.ts.
No UI component needs to change. docs/PRODUCTION.md lists the logical entities,
the fields that move to the server, and the recommended migration order.
- Keep the demo notice unless the dataset is replaced with verified real data.
- Replace the tile provider before real traffic (see above).
- Add a real request/review/question pipeline, moderation and notifications.
- Get Estonian/EU marketplace, consumer-protection, privacy and commercial terms reviewed before operating commercially.
- Reconsider
robots/noindexmetadata before the content becomes real (fictional provider and laptop pages are currentlynoindexon purpose). - Review security headers and consider a CSP once third-party services are added.
LinuxAbi Eesti is an independent demonstration project. It is not affiliated with, sponsored by or endorsed by:
- Linux Mint or the Linux Mint project. The visual design is only loosely inspired by a cheerful green palette; the official Linux Mint logo and website are not reproduced. The official site is linked for reference: https://linuxmint.com
- Qortal. Qortal is mentioned only as a neutral secondary reference to decentralized publishing technology, and the official project site is linked at https://qortal.org. This is not a cryptocurrency promotion and the demo is not affiliated with Qortal.