Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .config/mise/conf.d/tasks-dev.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Mise repairs missing/stale dependencies before every task in this checkout.
[deps.npm]
auto = true
sources = ["package-lock.json"]
outputs = ["node_modules/.package-lock.json"]
run = "npm ci --no-audit --no-fund"

[tasks.dev]
description = "Start the Astro development server (binds localhost:4321 by default)"
run = "npm run dev"
13 changes: 13 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,17 @@ export default defineConfig({
host: true,
allowedHosts: ['apphane.exe.xyz'],
},
// Cookieless PostHog needs a same-origin ingest proxy so the server can
// hash IP+user-agent. Same proxy in production via public/_worker.js.
vite: {
server: {
proxy: {
'/ingest': {
target: 'https://eu.i.posthog.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/ingest/, ''),
},
},
},
},
});
15 changes: 15 additions & 0 deletions public/_worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Cloudflare Pages advanced-mode worker (copied to dist/ by Astro).
// Serves static assets as usual and reverse-proxies /ingest/* to PostHog EU
// for cookieless tracking — the server-side IP+UA hash requires it.
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname.startsWith("/ingest/")) {
const target = new URL(request.url);
target.hostname = "eu.i.posthog.com";
target.pathname = url.pathname.slice("/ingest".length);
return fetch(new Request(target, request));
}
return env.ASSETS.fetch(request);
},
};
53 changes: 53 additions & 0 deletions src/components/Analytics.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
// Opt-in PostHog, default off.
//
// The ask lives where it has context: a checkbox in the directions dialog
// ("support the service by sharing anonymous usage statistics"). Until the
// visitor checks it, no analytics script loads and nothing is stored or
// sent. Checking it is explicit, informed consent (GDPR Art. 4(11) shape);
// unchecking stops capture and wipes all PostHog storage immediately.
//
// Init happens live (no reload) so the dialog flow is never interrupted:
// the checkbox handler calls window.__eczaneInitPosthog() on check.
//
// DNT/GPC is honored as a standing opt-out even over a stored "on".
const key = import.meta.env.PUBLIC_POSTHOG_KEY ?? "";
---

{
key && (
<script is:inline define:vars={{ key }}>
window.__eczaneInitPosthog = function () {
if (window.posthog) return;
const s = document.createElement("script");
s.src = "/ingest/static/array.js";
s.async = true;
s.onload = () => {
posthog.init(key, {
api_host: "/ingest",
persistence: "localStorage",
autocapture: true,
capture_pageview: true,
disable_session_recording: true,
disable_surveys: true,
person_profiles: "identified_only",
});
};
document.head.appendChild(s);
};

const dnt =
navigator.doNotTrack === "1" ||
window.doNotTrack === "1" ||
navigator.doNotTrack === "yes" ||
navigator.globalPrivacyControl === true;

let optedIn = false;
try {
optedIn = localStorage.getItem("eczane.telemetry") === "on";
} catch {}

if (!dnt && optedIn) window.__eczaneInitPosthog();
</script>
)
}
43 changes: 43 additions & 0 deletions src/components/CityPage.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
cityPath,
} from "../lib/i18n";
import PharmacyCard from "./PharmacyCard.astro";
import Analytics from "./Analytics.astro";
import MapView from "./MapView.astro";
import antalyaData from "../data/antalya.json";
import "../styles/global.css";
Expand Down Expand Up @@ -294,6 +295,17 @@ const runtimeStrings = {
{t.directionsOsm}
</a>
</div>
<div class="sheet__telemetry">
<input type="checkbox" role="switch" id="telemetry-consent" />
<label for="telemetry-consent" class="sheet__switch"></label>
<div class="sheet__telemetry__body">
<label for="telemetry-consent">{t.telemetryOptIn}</label>
<details class="sheet__telemetry__details">
<summary>{t.telemetryDetailsLabel}</summary>
<p>{t.telemetryDetails}</p>
</details>
</div>
</div>
</div>
</dialog>

Expand Down Expand Up @@ -484,6 +496,36 @@ const runtimeStrings = {
directionsTrigger = null;
});

// --- Telemetry opt-in ("support the service") ---
// Default off: Analytics.astro loads nothing until this flips on.
// Both directions happen live — no reload, the dialog stays open.
const telemetryInput = document.getElementById(
"telemetry-consent",
) as HTMLInputElement | null;
if (telemetryInput) {
try {
telemetryInput.checked =
localStorage.getItem("eczane.telemetry") === "on";
} catch {}
telemetryInput.addEventListener("change", () => {
try {
localStorage.setItem(
"eczane.telemetry",
telemetryInput.checked ? "on" : "off",
);
if (telemetryInput.checked) {
window.__eczaneInitPosthog?.();
} else {
// Stop captures now, then wipe everything PostHog stored.
window.posthog?.opt_out_capturing();
for (const key of Object.keys(localStorage)) {
if (key.startsWith("ph_")) localStorage.removeItem(key);
}
}
} catch {}
});
}

document.addEventListener("keydown", (ev) => {
if (ev.key === "Escape" && directionsDialog?.open) {
closeDirections();
Expand Down Expand Up @@ -657,5 +699,6 @@ const runtimeStrings = {
);
});
</script>
<Analytics />
</body>
</html>
24 changes: 24 additions & 0 deletions src/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export interface Strings {
mapSelectedAnnounce: (name: string) => string;
footer: string;
languageLabel: string;
/** Opt-in ask inside the directions dialog. */
telemetryOptIn: string;
telemetryDetailsLabel: string;
telemetryDetails: string;
}

export const translations: Record<Locale, Strings> = {
Expand Down Expand Up @@ -104,6 +108,11 @@ export const translations: Record<Locale, Strings> = {
footer:
"Veriler Antalya Eczacı Odası kaynaklıdır. Acil durumda eczaneyi arayarak teyit ediniz.",
languageLabel: "Dil",
telemetryOptIn:
"Hizmeti desteklemek için anonim kullanım istatistiklerini paylaşmak istiyorum.",
telemetryDetailsLabel: "Neler paylaşılır?",
telemetryDetails:
"Hangi sayfaların görüntülendiği ve hangi düğmelerin kullanıldığı, tarayıcı ve cihaz türü. Yazdığınız içerikler, ad, adres veya telefon toplanmaz. Veriler yalnızca bu sitenin istatistikleri için AB'deki PostHog sunucularında işlenir. Kapatınca cihazınızda saklanan her şey anında silinir.",
},
en: {
htmlLang: "en",
Expand Down Expand Up @@ -144,6 +153,11 @@ export const translations: Record<Locale, Strings> = {
footer:
"Data sourced from the Antalya Chamber of Pharmacists. In an emergency, call the pharmacy to confirm.",
languageLabel: "Language",
telemetryOptIn:
"I want to support the service by sharing anonymous usage statistics.",
telemetryDetailsLabel: "What is shared?",
telemetryDetails:
"Which pages are viewed and which buttons are used, plus browser and device type. Nothing you type, and no names, addresses or phone numbers. Data is processed on PostHog servers in the EU, only for this site's usage statistics. Switching off erases everything stored on your device immediately.",
},
ru: {
htmlLang: "ru",
Expand Down Expand Up @@ -184,6 +198,11 @@ export const translations: Record<Locale, Strings> = {
footer:
"Данные предоставлены Палатой фармацевтов Антальи. В экстренном случае позвоните в аптеку для подтверждения.",
languageLabel: "Язык",
telemetryOptIn:
"Хочу поддержать сервис, делясь анонимной статистикой использования.",
telemetryDetailsLabel: "Что передаётся?",
telemetryDetails:
"Какие страницы просмотрены и какие кнопки нажаты, тип браузера и устройства. Вводимый текст, имена, адреса и телефоны не собираются. Данные обрабатываются на серверах PostHog в ЕС только для статистики этого сайта. При выключении всё сохранённое на устройстве удаляется сразу.",
},
de: {
htmlLang: "de",
Expand Down Expand Up @@ -224,6 +243,11 @@ export const translations: Record<Locale, Strings> = {
footer:
"Daten der Antalya Apothekerkammer. Rufen Sie im Notfall die Apotheke zur Bestätigung an.",
languageLabel: "Sprache",
telemetryOptIn:
"Ich möchte den Service unterstützen, indem ich anonyme Nutzungsstatistiken teile.",
telemetryDetailsLabel: "Was wird geteilt?",
telemetryDetails:
"Welche Seiten angesehen und welche Schaltflächen benutzt werden, dazu Browser- und Gerätetyp. Keine Eingaben, keine Namen, Adressen oder Telefonnummern. Verarbeitung auf PostHog-Servern in der EU, ausschließlich für Nutzungsstatistik dieser Seite. Beim Ausschalten wird alles auf dem Gerät Gespeicherte sofort gelöscht.",
},
};

Expand Down
2 changes: 2 additions & 0 deletions src/pages/de/index.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { locales, type Locale, cityPath } from "../../lib/i18n";
import Analytics from "../../components/Analytics.astro";

// This page's locale.
const here: Locale = "de";
Expand Down Expand Up @@ -36,5 +37,6 @@ for (const loc of locales) {
</head>
<body>
<p><a href="/de/antalya">Antalya Notdienstapotheken</a></p>
<Analytics />
</body>
</html>
2 changes: 2 additions & 0 deletions src/pages/en/index.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { locales, type Locale, cityPath } from "../../lib/i18n";
import Analytics from "../../components/Analytics.astro";

// This page's locale.
const here: Locale = "en";
Expand Down Expand Up @@ -36,5 +37,6 @@ for (const loc of locales) {
</head>
<body>
<p><a href="/en/antalya">Antalya On-Duty Pharmacies</a></p>
<Analytics />
</body>
</html>
2 changes: 2 additions & 0 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Single-city site for now; send visitors straight to Antalya.
// Designed to become a city picker once more provinces are added.
import { locales, type Locale, cityPath } from "../lib/i18n";
import Analytics from "../components/Analytics.astro";

// This page's locale.
const here: Locale = "tr";
Expand Down Expand Up @@ -38,5 +39,6 @@ for (const loc of locales) {
</head>
<body>
<p><a href="/antalya">Antalya Nöbetçi Eczaneler</a></p>
<Analytics />
</body>
</html>
2 changes: 2 additions & 0 deletions src/pages/ru/index.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { locales, type Locale, cityPath } from "../../lib/i18n";
import Analytics from "../../components/Analytics.astro";

// This page's locale.
const here: Locale = "ru";
Expand Down Expand Up @@ -36,5 +37,6 @@ for (const loc of locales) {
</head>
<body>
<p><a href="/ru/antalya">Дежурные аптеки Антальи</a></p>
<Analytics />
</body>
</html>
Loading