Skip to content
Closed
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
15 changes: 15 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,18 @@ regexes = [
'''^\s*"packages/connect-web/src/assert-fetch-api\.ts": "b577e990dcf088f942a1dcf66405aab52c7d6f72bed2757c64e213e32ce91dac",?$''',
'''^\s*"packages/shared/ReactFlightPropertyAccess\.js": "b80258eb57aa5de9d4386788424c10b259b87b76f84498605bf10f0b0dd7c793",?$''',
]

[[allowlists]]
description = "Reviewed public source hashes in the immutable browser resource r8 profile"
condition = "AND"
targetRules = ["generic-api-key"]
regexTarget = "line"
paths = ['''^eng/provenance/profiles/browser-resources-r8\.json$''']
regexes = [
'''^\s*"node_modules/@bufbuild/protobuf/dist/esm/wkt/gen/google/protobuf/api_pb\.js": "73e489001027c703bc0224ae73f78ecefe028e88284bd06952e8603c3d81472c",?$''',
'''^\s*"node_modules/@connectrpc/connect-web/dist/esm/assert-fetch-api\.js": "bd56033776818aaa82959c12561dd084d3a730180e6e8f1e681f5d3d439b6474",?$''',
'''^\s*"packages/protobuf/src/wkt/gen/google/protobuf/api_pb\.ts": "538052ccd9a02c464702000dfc92a5e7cc01b7b5f55970b14af39662c7b46ae5",?$''',
'''^\s*"packages/protobuf/src/wkt/gen/google/protobuf/api_pb\.ts": "440d9371d9b04a8025e6f89d0041c2a54c7c926abedfbd24cef1432ec3fc38a4",?$''',
'''^\s*"packages/connect-web/src/assert-fetch-api\.ts": "b577e990dcf088f942a1dcf66405aab52c7d6f72bed2757c64e213e32ce91dac",?$''',
'''^\s*"packages/shared/ReactFlightPropertyAccess\.js": "b80258eb57aa5de9d4386788424c10b259b87b76f84498605bf10f0b0dd7c793",?$''',
]
5 changes: 4 additions & 1 deletion apps/site/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import {
Outlet,
Scripts,
ScrollRestoration,
useLocation,
useRouteError,
} from "react-router";
import { localeForPath } from "./site-manifest";

export function Layout({ children }: { children: ReactNode }) {
const location = useLocation();
return (
<html lang="en">
<html lang={localeForPath(location.pathname)}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
1 change: 1 addition & 0 deletions apps/site/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default [
index("routes/home.tsx"),
route("hello", "routes/hello.tsx"),
route("cloud-hello", "routes/cloud-hello.tsx"),
route("*", "routes/localized-site.tsx"),
] satisfies RouteConfig;
85 changes: 16 additions & 69 deletions apps/site/app/routes/cloud-hello.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,22 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { Button } from "@arcforges/web-ui";
import { useEffect, useRef, useState } from "react";
import { checkServerConnection } from "../cloud-hello";
import { useLoaderData } from "react-router";
import { loadCloudHelloPage } from "../site-content.server";
import { metadataForPage } from "../site-metadata";
import { CloudHelloPageView } from "../site-pages";

export function meta() {
return [{ title: "Server connection — ArcForges" }];
export function loader() {
return loadCloudHelloPage();
}

export default function CloudHello() {
const [ready, setReady] = useState(false);
const [state, setState] = useState<"idle" | "pending" | "success" | "error">("idle");
const [reply, setReply] = useState("");
const active = useRef<AbortController | null>(null);
useEffect(() => {
setReady(true);
return () => active.current?.abort();
}, []);

async function connect() {
if (active.current) return;
const controller = new AbortController();
active.current = controller;
setState("pending");
try {
const message = await checkServerConnection(window.location.origin, controller.signal);
if (!controller.signal.aborted) {
setReply(message);
setState("success");
}
} catch {
if (!controller.signal.aborted) setState("error");
} finally {
if (active.current === controller) active.current = null;
}
}

const message = {
idle: "No request sent yet.",
pending: "Contacting the server…",
success: reply,
error: "The server is unavailable or returned an unexpected response. Try again later.",
}[state];
export function meta({
loaderData,
}: {
loaderData: ReturnType<typeof loadCloudHelloPage> | undefined;
}) {
return loaderData ? metadataForPage(loaderData) : [];
}

return (
<section className="example">
<a className="back-link" href="/hello/">
← Back to your hello
</a>
<p className="eyebrow">Connection example</p>
<h1>
A hello, <em>from the server.</em>
</h1>
<p className="hero-copy">
Send a fixed “ArcForges” greeting to check the server connection. Your name from the local
example stays in your browser. Nothing is sent until you choose to connect.
</p>
<Button type="button" disabled={!ready || state === "pending"} onClick={connect}>
{state === "pending" ? "Connecting…" : "Check connection"}
</Button>
<noscript>
<p className="no-script">Enable JavaScript to check the server connection.</p>
</noscript>
<div
className="greeting"
role={state === "error" ? "alert" : "status"}
aria-live="polite"
aria-atomic="true"
>
<span className="greeting-label">Server response</span>
<p>{message}</p>
</div>
</section>
);
export default function CloudHello() {
const page = useLoaderData<typeof loader>();
return <CloudHelloPageView page={page} />;
}
88 changes: 12 additions & 76 deletions apps/site/app/routes/hello.tsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,18 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { Button } from "@arcforges/web-ui";
import { type FormEvent, useEffect, useState } from "react";
import { greet } from "../hello";
import { useLoaderData } from "react-router";
import { loadHelloPage } from "../site-content.server";
import { metadataForPage } from "../site-metadata";
import { HelloPageView } from "../site-pages";

export function meta() {
return [{ title: "Your hello — ArcForges" }];
export function loader() {
return loadHelloPage();
}

export function meta({ loaderData }: { loaderData: ReturnType<typeof loadHelloPage> | undefined }) {
return loaderData ? metadataForPage(loaderData) : [];
}

export default function Hello() {
const [name, setName] = useState("World");
const [message, setMessage] = useState("Hello, World!");
const [error, setError] = useState("");
const [ready, setReady] = useState(false);
useEffect(() => {
setReady(true);
}, []);
function submit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
try {
setMessage(greet(name));
setError("");
} catch {
setError("Enter a name between 1 and 80 characters, without control characters.");
}
}
return (
<section className="example">
<a className="back-link" href="/">
← Back home
</a>
<p className="eyebrow">
<span className="status-dot" aria-hidden="true" />
Hello example
</p>
<h1>
A hello, <em>just for you.</em>
</h1>
<p className="hero-copy">
Start with your name. This example runs in your browser, and your name stays on this page.
</p>
<form className="hello-form" onSubmit={submit}>
<label htmlFor="hello-name">Your name</label>
<div className="input-row">
<input
id="hello-name"
disabled={!ready}
value={name}
onChange={(event) => setName(event.target.value)}
autoComplete="off"
spellCheck={false}
aria-invalid={Boolean(error)}
aria-describedby={error ? "name-error name-hint" : "name-hint"}
/>
<Button type="submit" disabled={!ready}>
Say hello
</Button>
</div>
<p id="name-hint" className="field-hint">
Up to 80 characters. No account needed.
</p>
{error && (
<p id="name-error" className="field-error" role="alert">
{error}
</p>
)}
</form>
<noscript>
<p className="no-script">
The example greeting is shown below. Enable JavaScript to personalize it.
</p>
</noscript>
<div className="greeting" role="status" aria-live="polite" aria-atomic="true">
<span className="greeting-label">Your greeting</span>
<p>{message}</p>
</div>
<p className="field-hint">
<a href="/cloud-hello/">Check the server connection →</a>
</p>
</section>
);
const page = useLoaderData<typeof loader>();
return <HelloPageView page={page} />;
}
54 changes: 12 additions & 42 deletions apps/site/app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,18 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { Arrow } from "@arcforges/web-ui";
import { useLoaderData } from "react-router";
import { loadHomePage } from "../site-content.server";
import { metadataForPage } from "../site-metadata";
import { HomePageView } from "../site-pages";

export function meta() {
return [
{ title: "Hello, world. — ArcForges" },
{ name: "description", content: "A small, open-source beginning for ArcForges Web." },
];
export function loader() {
return loadHomePage();
}

export function meta({ loaderData }: { loaderData: ReturnType<typeof loadHomePage> | undefined }) {
return loaderData ? metadataForPage(loaderData) : [];
}

export default function Home() {
return (
<>
<section className="hero">
<div>
<p className="eyebrow">
<span className="status-dot" aria-hidden="true" />
ArcForges Web · Early preview
</p>
<h1>
Hello,
<br />
<em>world.</em>
</h1>
<p className="hero-copy">
Every good thing starts somewhere.
<br />
Welcome to the first page of ArcForges Web.
</p>
<a className="button" href="/hello">
Make it your hello
<Arrow />
</a>
</div>
<div className="hello-art" aria-hidden="true">
<span>hello.</span>
</div>
</section>
<section className="intro-line" aria-labelledby="intro-title">
<h2 id="intro-title">A beginning you can explore.</h2>
<p>
This small preview has one job: say hello. Try the example, look around the source, and
follow along as ArcForges takes shape.
</p>
</section>
</>
);
const page = useLoaderData<typeof loader>();
return <HomePageView page={page} />;
}
41 changes: 41 additions & 0 deletions apps/site/app/routes/localized-site.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { useLoaderData } from "react-router";
import { metadataForPage } from "../site-metadata";
import { loadLocalizedPage } from "../site-content.server";
import { CloudHelloPageView, HelloPageView, HomePageView } from "../site-pages";

export function loader({ params }: { params: Record<string, string | undefined> }) {
return loadLocalizedPage(params);
}

export function meta({
loaderData,
}: {
loaderData: Awaited<ReturnType<typeof loader>> | undefined;
}) {
return loaderData ? metadataForPage(loaderData) : [];
}

export default function LocalizedSitePage() {
const page = useLoaderData<typeof loader>();
switch (page.kind) {
case "home":
return <HomePageView page={page} />;
case "hello":
return <HelloPageView page={page} />;
case "cloud-hello":
return <CloudHelloPageView page={page} />;
case "document":
return (
<article className="example">
<a className="back-link" href="/">
← Back home
</a>
<p className="eyebrow">Documentation · {page.version}</p>
<h1>{page.title}</h1>
<p className="hero-copy">{page.description}</p>
<p>{page.body}</p>
</article>
);
}
}
Loading
Loading