Skip to content
Open
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
13 changes: 13 additions & 0 deletions apps/web/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { I18nProviderClient } from '@/yuzu/client';

export default async function StatusPageI18nLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<I18nProviderClient>
{children}
</I18nProviderClient>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client'

import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem
} from '@openstatus/ui'
import { useChangeLocale, useCurrentLocale } from '@/yuzu/client'
import yuzuConfig from '@/../yuzu.config'
const locales = yuzuConfig.locales

export function Switcher() {
const changeLocale = useChangeLocale()
const locale = useCurrentLocale()

function onSwitch(value: typeof locales[number]['code']) {
changeLocale(value)
}

return (
<div className="fixed bottom-4 right-4 z-50">
<Select onValueChange={onSwitch} defaultValue={locale}>
<SelectTrigger className="bg-background shadow-xl gap-2">
<svg className="w-4 h-4 flex-none" xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<line x1="2" x2="22" y1="12" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4
10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
<SelectValue placeholder="Select a language" />
</SelectTrigger>
<SelectContent>
{locales.map((locale) => (
<SelectItem key={locale.code} value={locale.code}>{locale.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { Header } from "@/components/dashboard/header";
import { IncidentList } from "@/components/status-page/incident-list";
import { api } from "@/trpc/server";
import { getI18n } from '@/yuzu/server';

type Props = {
params: { domain: string };
Expand Down Expand Up @@ -36,6 +37,7 @@ export default async function Page({ params }: Props) {
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const page = await api.page.getPageBySlug.query({ slug: params.domain });
const firstMonitor = page?.monitors?.[0]; // temporary solution
const t = await getI18n();

return {
...defaultMetadata,
Expand All @@ -46,7 +48,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
...twitterMetadata,
images: [
`/api/og?monitorId=${firstMonitor?.id}&title=${page?.title}&description=${
page?.description || `The ${page?.title} status page`
page?.description || `${t('The')} ${page?.title} ${t('status page')}`
}`,
],
title: page?.title,
Expand All @@ -56,7 +58,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
...ogMetadata,
images: [
`/api/og?monitorId=${firstMonitor?.id}&title=${page?.title}&description=${
page?.description || `The ${page?.title} status page`
page?.description || `${t('The')} ${page?.title} ${t('status page')}`
}`,
],
title: page?.title,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { Shell } from "@/components/dashboard/shell";
import NavigationLink from "./_components/navigation-link";
import { UserButton } from "./_components/user-button";
import { Switcher } from './_components/switcher';
import { getI18n } from '@/yuzu/server';

export default function StatusPageLayout({
export default async function StatusPageLayout({
children,
}: {
children: React.ReactNode;
}) {

const t = await getI18n();

return (
<div className="flex min-h-screen w-full flex-col space-y-6 p-4 md:p-8">
<header className="mx-auto w-full max-w-xl">
<Shell className="mx-auto flex items-center justify-center gap-2 p-2 px-2 md:p-3">
<NavigationLink slug={null}>Status</NavigationLink>
<NavigationLink slug="incidents">Incidents</NavigationLink>
<NavigationLink slug={null}>{t('Status')}</NavigationLink>
<NavigationLink slug="incidents">{t('Incidents')}</NavigationLink>
</Shell>
</header>
<main className="flex h-full w-full flex-1 flex-col">
Expand All @@ -22,7 +27,7 @@ export default function StatusPageLayout({
</main>
<footer className="z-10">
<p className="text-muted-foreground text-center text-sm">
powered by{" "}
{t('powered by')}{" "}
<a
href="https://www.openstatus.dev"
target="_blank"
Expand All @@ -32,6 +37,7 @@ export default function StatusPageLayout({
openstatus.dev
</a>
</p>
<Switcher />
</footer>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IncidentList } from "@/components/status-page/incident-list";
import { MonitorList } from "@/components/status-page/monitor-list";
import { StatusCheck } from "@/components/status-page/status-check";
import { api } from "@/trpc/server";
import { getI18n } from '@/yuzu/server';

const url =
process.env.NODE_ENV === "development"
Expand All @@ -39,6 +40,8 @@ export default async function Page({ params }: Props) {
Boolean(page.monitors.length) || Boolean(page.incidents.length)
);

const t = await getI18n();

return (
<div className="mx-auto flex w-full flex-col gap-6">
<Header
Expand All @@ -49,11 +52,11 @@ export default async function Page({ params }: Props) {
{isEmptyState ? (
<EmptyState
icon="activity"
title="Missing Monitors"
description="Fill your status page with monitors."
title={t("Missing Monitors")}
description={t("Fill your status page with monitors.")}
action={
<Button asChild>
<Link href={`${url}/app`}>Go to Dashboard</Link>
<Link href={`${url}/app`}>{t('Go to Dashboard')}</Link>
</Button>
}
/>
Expand All @@ -75,6 +78,7 @@ export default async function Page({ params }: Props) {
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const page = await api.page.getPageBySlug.query({ slug: params.domain });
const firstMonitor = page?.monitors?.[0]; // temporary solution
const t = await getI18n();

return {
...defaultMetadata,
Expand All @@ -85,7 +89,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
...twitterMetadata,
images: [
`/api/og?monitorId=${firstMonitor?.id}&title=${page?.title}&description=${
page?.description || `The ${page?.title} status page`
page?.description || `${t('The')} ${page?.title} ${t('status page')}`
}`,
],
title: page?.title,
Expand All @@ -95,7 +99,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
...ogMetadata,
images: [
`/api/og?monitorId=${firstMonitor?.id}&title=${page?.title}&description=${
page?.description || `The ${page?.title} status page`
page?.description || `${t('The')} ${page?.title} ${t('status page')}`
}`,
],
title: page?.title,
Expand Down
14 changes: 8 additions & 6 deletions apps/web/src/components/status-page/incident-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { notEmpty } from "@/lib/utils";
import { AffectedMonitors } from "../incidents/affected-monitors";
import { Events } from "../incidents/events";
import { StatusBadge } from "../incidents/status-badge";
import { getI18n } from '@/yuzu/server';

// TODO: change layout - it is too packed with data rn

export const IncidentList = ({
export const IncidentList = async ({
incidents,
monitors,
context = "all",
Expand All @@ -21,6 +22,7 @@ export const IncidentList = ({
monitors: z.infer<typeof selectPublicMonitorSchema>[];
context?: "all" | "latest"; // latest 7 days
}) => {
const t = await getI18n();
const lastWeek = Date.now() - 1000 * 60 * 60 * 24 * 7;

function getLastWeeksIncidents() {
Expand All @@ -42,7 +44,7 @@ export const IncidentList = ({
})?.length > 0 ? (
<div className="grid gap-4">
<h2 className="text-muted-foreground text-lg font-light">
{context === "all" ? "All incidents" : "Latest incidents"}
{context === "all" ? t("All incidents") : t("Latest incidents")}
</h2>
{_incidents.map((incident) => {
const affectedMonitors = incident.monitorsToIncidents
Expand All @@ -60,7 +62,7 @@ export const IncidentList = ({
{Boolean(affectedMonitors.length) ? (
<div className="overflow-hidden text-ellipsis">
<p className="text-muted-foreground mb-2 text-xs">
Affected Monitors
{t('Affected Monitors')}
</p>
<AffectedMonitors
monitors={incident.monitorsToIncidents
Expand All @@ -76,7 +78,7 @@ export const IncidentList = ({
) : null}
<div>
<p className="text-muted-foreground mb-2 text-xs">
Latest Updates
{t('Latest Updates')}
</p>
<Events incidentUpdates={incident.incidentUpdates} />
</div>
Expand All @@ -87,8 +89,8 @@ export const IncidentList = ({
) : (
<p className="text-muted-foreground text-center text-sm font-light">
{context === "all"
? "No incidents."
: "No incidents in the last week."}
? t("No incidents.")
: t("No incidents in the last week.")}
</p>
)}
</>
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/components/status-page/monitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import type { selectPublicMonitorSchema } from "@openstatus/db/src/schema";

import { getMonitorListData } from "@/lib/tb";
import { Tracker } from "../tracker";
import { getI18n } from '@/yuzu/server';

export const Monitor = async ({
monitor,
}: {
monitor: z.infer<typeof selectPublicMonitorSchema>;
}) => {
const data = await getMonitorListData({ monitorId: String(monitor.id) });
if (!data) return <div>Something went wrong</div>;
const t = await getI18n();
if (!data) return <div>{t('Something went wrong')}</div>;
return (
<Tracker
data={data}
Expand Down
8 changes: 5 additions & 3 deletions apps/web/src/components/tracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import useWindowSize from "@/hooks/use-window-size";
import type { CleanMonitor } from "@/lib/tracker";
import { blacklistDates, cleanData, getStatus } from "@/lib/tracker";
import { useI18n } from '@/yuzu/client';

// What would be cool is tracker that turn from green to red depending on the number of errors
const tracker = cva("h-10 rounded-full flex-1", {
Expand Down Expand Up @@ -129,6 +130,7 @@ const Bar = ({
context,
}: CleanMonitor & Pick<TrackerProps, "context">) => {
const [open, setOpen] = React.useState(false);
const t = useI18n()
const ratio = ok / count;
const date = new Date(cronTimestamp);
const toDate = date.setDate(date.getDate() + 1);
Expand Down Expand Up @@ -169,21 +171,21 @@ const Bar = ({
{format(new Date(cronTimestamp), dateFormat)}
</p>
<p className="text-muted-foreground text-xs">
avg. <span className="font-mono">{avgLatency}ms</span>
{t('avg.')} <span className="font-mono">{avgLatency}{t('ms')}</span>
</p>
</div>
<Separator className="my-1.5" />
<div className="grid grid-cols-2">
<p className="text-left text-xs">
<span className="font-mono text-green-600">{count}</span>{" "}
<span className="text-muted-foreground font-light">
total requests
{t('total requests')}
</span>
</p>
<p className="text-right text-xs">
<span className="font-mono text-red-600">{count - ok}</span>{" "}
<span className="text-muted-foreground font-light">
failed requests
{t('failed requests')}
</span>
</p>
</div>
Expand Down
13 changes: 13 additions & 0 deletions apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ import {
usersToWorkspaces,
workspace,
} from "@openstatus/db/src/schema";
import { createI18nMiddleware } from 'next-international/middleware'

import { env } from "./env";

const I18nMiddleware = createI18nMiddleware({
locales: ['en', 'es', 'fr', 'de'],
defaultLocale: 'en',
urlMappingStrategy: 'rewriteDefault'
})

const before = (req: NextRequest) => {
const url = req.nextUrl.clone();

Expand Down Expand Up @@ -85,6 +92,12 @@ export default authMiddleware({
beforeAuth: before,
debug: false,
async afterAuth(auth, req) {
const host = req.headers.get("host");
const subdomain = getValidSubdomain(host);
if (subdomain || req.nextUrl.pathname.includes('/status-page/')) {
return I18nMiddleware(req)
}

// handle users who aren't authenticated
if (!auth.userId && !auth.isPublicRoute) {
return redirectToSignIn({ returnBackUrl: req.url });
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/yuzu/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client'

import { createI18nClient } from 'next-international/client'

export const { useCurrentLocale, useChangeLocale, useI18n, useScopedI18n, I18nProviderClient } = createI18nClient({
'en': () => import('./en.json'),
'es': () => import('./es.json'),
'fr': () => import('./fr.json'),
'de': () => import('./de.json')
})
1 change: 1 addition & 0 deletions apps/web/src/yuzu/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"avg.":"Durchschnittswert","ms":"ms","total requests":"Anfragen insgesamt","failed requests":"fehlgeschlagene Anfragen","Something went wrong":"Etwas ist schief gelaufen","All incidents":"Alle Vorfälle","Latest incidents":"Letzte Vorfälle","Affected Monitors":"Betroffene Monitore","Latest Updates":"Letzte Updates","No incidents.":"Keine Vorfälle.","No incidents in the last week.":"Keine Vorfälle in der letzten Woche.","Missing Monitors":"Fehlende Monitore","Fill your status page with monitors.":"Füllen Sie Ihre Statusseite mit Monitoren.","Go to Dashboard":"Zum Dashboard gehen","The":"Die","status page":"Statusseite","Status":"Status","Incidents":"Vorfälle","powered by":"angetrieben von"}
1 change: 1 addition & 0 deletions apps/web/src/yuzu/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"avg.":"avg.","ms":"ms","total requests":"total requests","failed requests":"failed requests","Something went wrong":"Something went wrong","All incidents":"All incidents","Latest incidents":"Latest incidents","Affected Monitors":"Affected Monitors","Latest Updates":"Latest Updates","No incidents.":"No incidents.","No incidents in the last week.":"No incidents in the last week.","Missing Monitors":"Missing Monitors","Fill your status page with monitors.":"Fill your status page with monitors.","Go to Dashboard":"Go to Dashboard","The":"The","status page":"status page","Status":"Status","Incidents":"Incidents","powered by":"powered by"}
1 change: 1 addition & 0 deletions apps/web/src/yuzu/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"avg.":"promedio","ms":"ms","total requests":"total solicitudes","failed requests":"solicitudes fallidas","Something went wrong":"Algo salió mal","All incidents":"Todos los incidentes","Latest incidents":"Últimos incidentes","Affected Monitors":"Monitores afectados","Latest Updates":"Últimas actualizaciones","No incidents.":"Ningún incidente.","No incidents in the last week.":"Ningún incidente en la última semana.","Missing Monitors":"Monitores desaparecidos","Fill your status page with monitors.":"Llena tu página de estado con monitores.","Go to Dashboard":"Ir al panel de control","The":"En","status page":"página de estado","Status":"Estado","Incidents":"Incidentes","powered by":"accionado por"}
1 change: 1 addition & 0 deletions apps/web/src/yuzu/fr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"avg.":"moyenne.","ms":"ms","total requests":"total des demandes","failed requests":"Demandes rejetées","Something went wrong":"Quelque chose n'a pas fonctionné","All incidents":"Tous les incidents","Latest incidents":"Derniers incidents","Affected Monitors":"Moniteurs concernés","Latest Updates":"Dernières mises à jour","No incidents.":"Aucun incident.","No incidents in the last week.":"Aucun incident au cours de la semaine écoulée.","Missing Monitors":"Moniteurs manquants","Fill your status page with monitors.":"Remplissez votre page de statut avec des moniteurs.","Go to Dashboard":"Accéder au tableau de bord","The":"Les","status page":"page d'état","Status":"Statut","Incidents":"Incidents","powered by":"alimenté par"}
8 changes: 8 additions & 0 deletions apps/web/src/yuzu/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createI18nServer } from 'next-international/server'

export const { getCurrentLocale, getI18n, getScopedI18n, getStaticParams } = createI18nServer({
'en': () => import('./en.json'),
'es': () => import('./es.json'),
'fr': () => import('./fr.json'),
'de': () => import('./de.json')
})
Loading