Skip to content

Commit 2f26d3f

Browse files
committed
Add status page links
1 parent 4e1c0c5 commit 2f26d3f

11 files changed

Lines changed: 198 additions & 70 deletions

File tree

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
# STATUS_API_URL: base URL of the status site's banner API, fetched SERVER-SIDE
55
# only (via the /api/status-banner route the browser polls) and cached for ~10s.
66
# Leave unset to disable the site-wide status banner.
7-
# NEXT_PUBLIC_STATUS_URL: public, visitor-facing status page the banner links to.
7+
# NEXT_PUBLIC_STATUS_URL: public, visitor-facing status page. Used both by the
8+
# banner and by the static "Service Status" links (footer, contact page, FAQ).
9+
# When unset it falls back to https://status.aliasvault.com, so set it only to
10+
# override (e.g. point at a local status instance during development).
811
#STATUS_API_URL=http://localhost:5180
912
#NEXT_PUBLIC_STATUS_URL=http://localhost:5180

src/app/[locale]/contact/page.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ const ContactPage = () => {
2828
pageName={t('contact.title')}
2929
description={t('contact.description')}
3030
/>
31-
<section className="pb-0 pt-8">
32-
<div className="container">
33-
<AlertBanner />
34-
</div>
35-
</section>
3631
<ContactContent />
3732
</Page>
3833
);

src/components/AlertBanner/index.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
"use client";
22
import { useTranslations } from "next-intl";
3+
import { getStatusPageUrl } from "@/lib/status-banner";
34

45
const AlertBanner = () => {
56
const t = useTranslations();
7+
const statusUrl = getStatusPageUrl();
68

79
return (
810
<div className="bg-primary/10 border border-primary/30 rounded-lg py-4 px-6 mb-6">
911
<div className="flex flex-col sm:flex-row items-center justify-center gap-2 text-center sm:text-left">
1012
<span className="text-dark dark:text-white">
1113
{t('alertBanner.message')}
1214
<a
13-
href="mailto:support@aliasvault.com"
14-
className="text-primary font-semibold hover:underline ml-1"
15-
>
16-
support@aliasvault.com
17-
</a>
15+
href={statusUrl}
16+
target="_blank"
17+
rel="noopener noreferrer"
18+
className="text-primary font-semibold hover:underline ml-1"
19+
>
20+
{t('alertBanner.statusLink')}
21+
</a>{" "}
22+
{t('alertBanner.contactPrompt')}
23+
<a
24+
href="mailto:support@aliasvault.com"
25+
className="text-primary font-semibold hover:underline ml-1"
26+
>
27+
support@aliasvault.com
28+
</a>
1829
</span>
1930

2031
</div>

src/components/Contact/index.tsx

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import Link from "next/link";
22
import Image from "next/image";
33
import { useTranslations } from "next-intl";
44
import AnchorHeading from "@/components/Common/AnchorHeading";
5+
import { getStatusPageUrl } from "@/lib/status-banner";
56

67
const ContactContent = () => {
78
const t = useTranslations();
9+
const statusUrl = getStatusPageUrl();
810

911
return (
1012
<section className="pb-16 pt-8">
@@ -28,8 +30,8 @@ const ContactContent = () => {
2830
className="text-primary hover:underline"
2931
>
3032
{t('contact.content.founder')}
31-
</a>{" "}
32-
{t('contact.content.location')}
33+
</a>
34+
{t('contact.content.team')}
3335
</p>
3436
<p className="text-base text-body-color dark:text-body-color-dark">
3537
{t('contact.content.main.contactBlurb')}{" "}
@@ -45,62 +47,53 @@ const ContactContent = () => {
4547
<div className="mb-12">
4648
<AnchorHeading
4749
as="h3"
48-
id="faq"
50+
id="status"
4951
className="mb-4 text-xl font-bold text-black dark:text-white sm:text-2xl lg:text-xl xl:text-2xl"
5052
copyLabel="Copy link"
5153
>
52-
{t('contact.content.faq.title')}
54+
{t('contact.content.status.title')}
5355
</AnchorHeading>
5456
<p className="text-base text-body-color dark:text-body-color-dark">
55-
{t('contact.content.faq.description')}
57+
{t('contact.content.status.description')}
5658
</p>
5759
<div className="mt-4">
58-
<Link
59-
href="/#faq"
60-
className="text-primary hover:underline"
61-
>
62-
{t('contact.content.faq.links.faq')}
63-
</Link>
64-
{" • "}
65-
<Link
66-
href="/docs"
60+
<a
61+
href={statusUrl}
62+
target="_blank"
63+
rel="noopener noreferrer"
6764
className="text-primary hover:underline"
6865
>
69-
{t('contact.content.faq.links.docs')}
70-
</Link>
66+
{t('contact.content.status.link')}
67+
</a>
7168
</div>
7269
</div>
7370

7471
<div className="mb-12">
7572
<AnchorHeading
7673
as="h3"
77-
id="feature-requests"
74+
id="faq"
7875
className="mb-4 text-xl font-bold text-black dark:text-white sm:text-2xl lg:text-xl xl:text-2xl"
7976
copyLabel="Copy link"
8077
>
81-
{t('contact.content.features.title')}
78+
{t('contact.content.faq.title')}
8279
</AnchorHeading>
8380
<p className="text-base text-body-color dark:text-body-color-dark">
84-
{t('contact.content.features.description')}
81+
{t('contact.content.faq.description')}
8582
</p>
8683
<div className="mt-4">
87-
<a
88-
href="https://github.com/aliasvault/aliasvault/discussions/categories/ideas"
89-
target="_blank"
90-
rel="noopener noreferrer"
84+
<Link
85+
href="/#faq"
9186
className="text-primary hover:underline"
9287
>
93-
{t('contact.content.features.links.submit')}
94-
</a>
88+
{t('contact.content.faq.links.faq')}
89+
</Link>
9590
{" • "}
96-
<a
97-
href="https://discord.gg/DsaXMTEtpF"
98-
target="_blank"
99-
rel="noopener noreferrer"
91+
<Link
92+
href="/docs"
10093
className="text-primary hover:underline"
10194
>
102-
{t('contact.content.features.links.discuss')}
103-
</a>
95+
{t('contact.content.faq.links.docs')}
96+
</Link>
10497
</div>
10598
</div>
10699

@@ -140,6 +133,39 @@ const ContactContent = () => {
140133
</ul>
141134
</div>
142135

136+
<div className="mb-12">
137+
<AnchorHeading
138+
as="h3"
139+
id="feature-requests"
140+
className="mb-4 text-xl font-bold text-black dark:text-white sm:text-2xl lg:text-xl xl:text-2xl"
141+
copyLabel="Copy link"
142+
>
143+
{t('contact.content.features.title')}
144+
</AnchorHeading>
145+
<p className="text-base text-body-color dark:text-body-color-dark">
146+
{t('contact.content.features.description')}
147+
</p>
148+
<div className="mt-4">
149+
<a
150+
href="https://github.com/aliasvault/aliasvault/discussions/categories/ideas"
151+
target="_blank"
152+
rel="noopener noreferrer"
153+
className="text-primary hover:underline"
154+
>
155+
{t('contact.content.features.links.submit')}
156+
</a>
157+
{" • "}
158+
<a
159+
href="https://discord.gg/DsaXMTEtpF"
160+
target="_blank"
161+
rel="noopener noreferrer"
162+
className="text-primary hover:underline"
163+
>
164+
{t('contact.content.features.links.discuss')}
165+
</a>
166+
</div>
167+
</div>
168+
143169
<div className="mb-12">
144170
<AnchorHeading
145171
as="h3"
@@ -208,6 +234,18 @@ const ContactContent = () => {
208234
</a>
209235
</div>
210236
</div>
237+
238+
<div className="mt-12 border-t border-body-color/20 pt-8 dark:border-body-color-dark/20">
239+
<p className="text-base text-body-color dark:text-body-color-dark">
240+
{t('contact.content.closing.text')}{" "}
241+
<a
242+
href="mailto:support@aliasvault.com"
243+
className="text-primary hover:underline"
244+
>
245+
support@aliasvault.com
246+
</a>
247+
</p>
248+
</div>
211249
</div>
212250
</div>
213251
</section>

src/components/FAQ/faqData.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { getStatusPageUrl } from "@/lib/status-banner";
2+
13
export type FAQItem = {
24
id: number;
35
question: string;
@@ -75,6 +77,16 @@ const getFAQData = (t: (key: string) => string): FAQItem[] => [
7577
.replace(/\[missionLink\]/g, "<a href='/mission' rel='noopener noreferrer'>")
7678
.replace(/\[\/missionLink\]/g, "</a>"),
7779
},
80+
{
81+
id: 11,
82+
slug: "service-status",
83+
question: t('faq.items.serviceStatus.question'),
84+
answer: t('faq.items.serviceStatus.answer')
85+
.replace(/\[statusLink\]/g, `<a href='${getStatusPageUrl()}' target='_blank' rel='noopener noreferrer'>`)
86+
.replace(/\[\/statusLink\]/g, "</a>")
87+
.replace(/\[contactLink\]/g, "<a href='/contact' rel='noopener noreferrer'>")
88+
.replace(/\[\/contactLink\]/g, "</a>"),
89+
},
7890
];
7991

8092
export default getFAQData;

src/components/Footer/index.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import Image from "next/image";
33
import Link from "next/link";
44
import { useTranslations, useLocale } from "next-intl";
55
import { SiGithub, SiCodeberg, SiGitlab, SiDiscord, SiMastodon, SiX, SiYoutube, SiFacebook } from "react-icons/si";
6+
import { FaEnvelope } from "react-icons/fa";
67
import LanguageSwitcher from "@/components/LanguageSwitcher";
8+
import { getStatusPageUrl } from "@/lib/status-banner";
79

810
const Footer = () => {
911
const t = useTranslations();
1012
const locale = useLocale();
13+
const statusUrl = getStatusPageUrl();
1114

1215
return (
1316
<>
@@ -30,9 +33,16 @@ const Footer = () => {
3033
<h1 className="text-2xl font-bold my-auto">AliasVault</h1>
3134
</div>
3235
</Link>
33-
<p className="mb-9 text-base leading-relaxed text-body-color dark:text-body-color-dark">
36+
<p className="mb-4 text-base leading-relaxed text-body-color dark:text-body-color-dark">
3437
{t('footer.description')}
3538
</p>
39+
<a
40+
href="mailto:support@aliasvault.com"
41+
className="mb-9 inline-flex items-center gap-2 text-base text-body-color duration-300 hover:text-primary dark:text-body-color-dark dark:hover:text-primary"
42+
>
43+
<FaEnvelope className="h-4 w-4" aria-hidden="true" />
44+
support@aliasvault.com
45+
</a>
3646
<div className="flex flex-wrap items-center gap-x-5 gap-y-3">
3747
<a
3848
href="https://github.com/aliasvault/aliasvault"
@@ -287,6 +297,30 @@ const Footer = () => {
287297
{t('footer.links.contact')}
288298
</Link>
289299
</li>
300+
<li>
301+
<a
302+
href={statusUrl}
303+
target="_blank"
304+
rel="noopener noreferrer"
305+
className="mb-4 inline-block text-base text-body-color duration-300 hover:text-primary dark:text-body-color-dark dark:hover:text-primary"
306+
>
307+
{t('footer.links.status')}
308+
<svg
309+
className="ml-1 inline-block h-4 w-4"
310+
fill="none"
311+
stroke="currentColor"
312+
viewBox="0 0 24 24"
313+
xmlns="http://www.w3.org/2000/svg"
314+
>
315+
<path
316+
strokeLinecap="round"
317+
strokeLinejoin="round"
318+
strokeWidth={2}
319+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
320+
/>
321+
</svg>
322+
</a>
323+
</li>
290324
<li>
291325
<Link
292326
href={`/${locale}/report-abuse`}

src/components/Header/index.tsx

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -167,19 +167,6 @@ const Header = ({ banner }: { banner?: ReactNode }) => {
167167
)}
168168
</li>
169169
))}
170-
<li className="py-3">
171-
<Link
172-
href="/contact"
173-
className={`block text-base transition-colors ${
174-
pathname === "/contact"
175-
? "text-primary dark:text-white"
176-
: "text-dark hover:text-primary dark:text-white/70 dark:hover:text-white"
177-
}`}
178-
onClick={() => setNavbarOpen(false)}
179-
>
180-
{t('navigation.contact')}
181-
</Link>
182-
</li>
183170
<li className="py-3">
184171
<Link
185172
href="https://github.com/aliasvault/aliasvault"
@@ -302,13 +289,6 @@ const Header = ({ banner }: { banner?: ReactNode }) => {
302289
{/* Icons & Buttons */}
303290
<div className="items-center gap-3 lg:ml-4 hidden lg:flex">
304291
<ThemeToggler />
305-
{/* Contact Button */}
306-
<Link
307-
href="/contact"
308-
className="hidden lg:flex h-10 items-center justify-center rounded-md border border-gray-300 px-4 py-2 text-dark hover:bg-gray-100 active:bg-gray-200 dark:border-gray-600 dark:text-white dark:hover:bg-gray-800 dark:active:bg-gray-700 transition-colors"
309-
>
310-
{t('navigation.contact')}
311-
</Link>
312292
{/* Desktop Login Button - Shows on medium screens and up */}
313293
<Link
314294
href="https://app.aliasvault.com/user/login"

src/components/Header/menuData.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ const getMenuData = (t: (key: string) => string): Menu[] => [
2525
path: "/blog",
2626
newTab: false,
2727
},
28+
{
29+
id: 5,
30+
title: t('navigation.contact'),
31+
path: "/contact",
32+
newTab: false,
33+
},
2834
];
2935

3036
export default getMenuData;

src/lib/status-banner.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ function normalizeBaseUrl(raw: string): string {
3434
return raw.replace(/\/+$/, "");
3535
}
3636

37+
// The canonical public status page, used when no NEXT_PUBLIC_STATUS_URL override
38+
// is configured. Static links (footer, contact page, FAQ) should always resolve
39+
// to a real page, so they fall back to this rather than disappearing.
40+
export const DEFAULT_STATUS_PAGE_URL = "https://status.aliasvault.com";
41+
42+
/**
43+
* The visitor-facing status page URL for static links. Prefers the configured
44+
* NEXT_PUBLIC_STATUS_URL (so local/staging can point elsewhere) and otherwise
45+
* falls back to the production status site. Safe to call from client components
46+
* (NEXT_PUBLIC_* is inlined at build time).
47+
*/
48+
export function getStatusPageUrl(): string {
49+
const configured = process.env.NEXT_PUBLIC_STATUS_URL;
50+
return configured ? normalizeBaseUrl(configured) : DEFAULT_STATUS_PAGE_URL;
51+
}
52+
3753
/**
3854
* Fetch the current status banner, server-side and cached.
3955
*/

0 commit comments

Comments
 (0)