From 568dd891479b90513829e645c1c9b435f4e98572 Mon Sep 17 00:00:00 2001
From: Erik Brinkman
Date: Tue, 13 Oct 2026 18:06:09 -0400
Subject: [PATCH] feat: add a contact page and link
---
.../kotlin/cc/hafa/reader/sync/SyncConfig.kt | 3 +++
.../kotlin/cc/hafa/reader/ui/AccountScreen.kt | 6 ++++--
android/app/src/main/res/values/strings.xml | 1 +
web/src/lib/components/shortcuts-pane.svelte | 5 ++++-
web/src/lib/config.ts | 3 +++
web/src/lib/copy.ts | 2 ++
web/src/routes/(policy)/contact/+page.svelte | 17 +++++++++++++++++
web/src/routes/(policy)/privacy/+page.svelte | 3 ++-
web/src/routes/(policy)/terms/+page.svelte | 3 ++-
9 files changed, 38 insertions(+), 5 deletions(-)
create mode 100644 web/src/routes/(policy)/contact/+page.svelte
diff --git a/android/app/src/main/kotlin/cc/hafa/reader/sync/SyncConfig.kt b/android/app/src/main/kotlin/cc/hafa/reader/sync/SyncConfig.kt
index 67f9926..08f7dfd 100644
--- a/android/app/src/main/kotlin/cc/hafa/reader/sync/SyncConfig.kt
+++ b/android/app/src/main/kotlin/cc/hafa/reader/sync/SyncConfig.kt
@@ -26,6 +26,9 @@ object SyncConfig {
/** The terms of service. */
const val TERMS_URL = "https://reader.hafa.cc/terms"
+ /** The contact page. */
+ const val CONTACT_URL = "https://reader.hafa.cc/contact"
+
/** Firebase options from the values above. */
fun firebaseOptions(): FirebaseOptions =
FirebaseOptions.Builder()
diff --git a/android/app/src/main/kotlin/cc/hafa/reader/ui/AccountScreen.kt b/android/app/src/main/kotlin/cc/hafa/reader/ui/AccountScreen.kt
index c1ed755..8ffd42d 100644
--- a/android/app/src/main/kotlin/cc/hafa/reader/ui/AccountScreen.kt
+++ b/android/app/src/main/kotlin/cc/hafa/reader/ui/AccountScreen.kt
@@ -293,12 +293,14 @@ private fun Note(text: AnnotatedString) {
Text(text, fontSize = 14.sp, lineHeight = 20.sp, color = ReaderTheme.colors.muted)
}
+@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun Policies() {
val links = LocalUriHandler.current
val colors = ReaderTheme.colors
- Row(Modifier.fillMaxWidth().padding(top = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- for ((label, address) in listOf(R.string.privacy to SyncConfig.PRIVACY_URL, R.string.terms to SyncConfig.TERMS_URL)) {
+ FlowRow(Modifier.fillMaxWidth().padding(top = 12.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ val pages = listOf(R.string.privacy to SyncConfig.PRIVACY_URL, R.string.terms to SyncConfig.TERMS_URL, R.string.contact to SyncConfig.CONTACT_URL)
+ for ((label, address) in pages) {
TextButton(onClick = { links.openUri(address) }, colors = ButtonDefaults.textButtonColors(contentColor = colors.muted)) {
Text(stringResource(label), textDecoration = TextDecoration.Underline)
}
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 13a53c9..ab333b2 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -100,4 +100,5 @@
No app on this device can share that link.
Privacy
Terms
+ Contact
diff --git a/web/src/lib/components/shortcuts-pane.svelte b/web/src/lib/components/shortcuts-pane.svelte
index 07e0918..2ce0866 100644
--- a/web/src/lib/components/shortcuts-pane.svelte
+++ b/web/src/lib/components/shortcuts-pane.svelte
@@ -3,7 +3,7 @@
import LogIn from "@lucide/svelte/icons/log-in";
import LogOut from "@lucide/svelte/icons/log-out";
import X from "@lucide/svelte/icons/x";
- import { privacyUrl, termsUrl } from "#lib/config.ts";
+ import { contactUrl, privacyUrl, termsUrl } from "#lib/config.ts";
import { copy } from "#lib/copy.ts";
import type { Reader } from "#lib/reader.svelte.ts";
import { type Retention, retentionFromDays } from "#lib/sync/documents.ts";
@@ -237,6 +237,9 @@
>{copy.privacy}
{copy.terms}
+ {copy.contact}
diff --git a/web/src/lib/config.ts b/web/src/lib/config.ts
index 3665553..ee45b03 100644
--- a/web/src/lib/config.ts
+++ b/web/src/lib/config.ts
@@ -14,6 +14,9 @@ export const privacyUrl = "/privacy";
/** The terms of service, a page of its own beside the app (`src/routes/(policy)/terms`), like the privacy policy. */
export const termsUrl = "/terms";
+/** How to reach support, a page of its own beside the app (`src/routes/(policy)/contact`), like the privacy policy. */
+export const contactUrl = "/contact";
+
/** The extension's Chrome Web Store listing page. */
export const chromeWebStoreUrl =
"https://chromewebstore.google.com/detail/ehmpednijaggcbpmjldkdookepbjindc";
diff --git a/web/src/lib/copy.ts b/web/src/lib/copy.ts
index a356529..c7d8726 100644
--- a/web/src/lib/copy.ts
+++ b/web/src/lib/copy.ts
@@ -173,4 +173,6 @@ export const copy = {
privacy: "Privacy",
/** Link to the terms of service. */
terms: "Terms",
+ /** Link to the contact page. */
+ contact: "Contact",
};
diff --git a/web/src/routes/(policy)/contact/+page.svelte b/web/src/routes/(policy)/contact/+page.svelte
new file mode 100644
index 0000000..bc1d84e
--- /dev/null
+++ b/web/src/routes/(policy)/contact/+page.svelte
@@ -0,0 +1,17 @@
+
+ Contact Reader
+
+
+
+Contact
+
+
+ Questions, problems or requests about Reader: write to
+ support@reader.hafa.cc.
+
+
+
+ To delete your account, use the delete account page.
+ What Reader stores, and where, is in the
+ privacy policy.
+
diff --git a/web/src/routes/(policy)/privacy/+page.svelte b/web/src/routes/(policy)/privacy/+page.svelte
index 0282d51..d6f1109 100644
--- a/web/src/routes/(policy)/privacy/+page.svelte
+++ b/web/src/routes/(policy)/privacy/+page.svelte
@@ -136,6 +136,7 @@
Contact
diff --git a/web/src/routes/(policy)/terms/+page.svelte b/web/src/routes/(policy)/terms/+page.svelte
index 0d42a3e..18ecc68 100644
--- a/web/src/routes/(policy)/terms/+page.svelte
+++ b/web/src/routes/(policy)/terms/+page.svelte
@@ -10,5 +10,6 @@
it shows belong to the sites that publish them. Reader is provided as is, with
no warranty. You can stop at any time; what is stored and how to delete it is
in the privacy policy. Questions:
- support@reader.hafa.cc.
+ support@reader.hafa.cc, or see the
+ contact page.