aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/app')
-rw-r--r--frontend/app/entry.client.tsx12
-rw-r--r--frontend/app/entry.server.tsx124
-rw-r--r--frontend/app/root.tsx35
-rw-r--r--frontend/app/routes/_index.tsx9
-rw-r--r--frontend/app/tailwind.css3
5 files changed, 183 insertions, 0 deletions
diff --git a/frontend/app/entry.client.tsx b/frontend/app/entry.client.tsx
new file mode 100644
index 0000000..442da4f
--- /dev/null
+++ b/frontend/app/entry.client.tsx
@@ -0,0 +1,12 @@
+import { RemixBrowser } from "@remix-run/react";
+import { startTransition, StrictMode } from "react";
+import { hydrateRoot } from "react-dom/client";
+
+startTransition(() => {
+ hydrateRoot(
+ document,
+ <StrictMode>
+ <RemixBrowser />
+ </StrictMode>,
+ );
+});
diff --git a/frontend/app/entry.server.tsx b/frontend/app/entry.server.tsx
new file mode 100644
index 0000000..77b6f15
--- /dev/null
+++ b/frontend/app/entry.server.tsx
@@ -0,0 +1,124 @@
+import { PassThrough } from "node:stream";
+
+import type { EntryContext } from "@remix-run/node";
+import { createReadableStreamFromReadable } from "@remix-run/node";
+import { RemixServer } from "@remix-run/react";
+import { isbot } from "isbot";
+import { renderToPipeableStream } from "react-dom/server";
+
+const ABORT_DELAY = 5_000;
+
+export default function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext,
+) {
+ return isbot(request.headers.get("user-agent") || "")
+ ? handleBotRequest(
+ request,
+ responseStatusCode,
+ responseHeaders,
+ remixContext,
+ )
+ : handleBrowserRequest(
+ request,
+ responseStatusCode,
+ responseHeaders,
+ remixContext,
+ );
+}
+
+function handleBotRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext,
+) {
+ return new Promise((resolve, reject) => {
+ let shellRendered = false;
+ const { pipe, abort } = renderToPipeableStream(
+ <RemixServer
+ context={remixContext}
+ url={request.url}
+ abortDelay={ABORT_DELAY}
+ />,
+ {
+ onAllReady() {
+ shellRendered = true;
+ const body = new PassThrough();
+ const stream = createReadableStreamFromReadable(body);
+
+ responseHeaders.set("Content-Type", "text/html");
+
+ resolve(
+ new Response(stream, {
+ headers: responseHeaders,
+ status: responseStatusCode,
+ }),
+ );
+
+ pipe(body);
+ },
+ onShellError(error: unknown) {
+ reject(error);
+ },
+ onError(error: unknown) {
+ responseStatusCode = 500;
+ if (shellRendered) {
+ console.error(error);
+ }
+ },
+ },
+ );
+
+ setTimeout(abort, ABORT_DELAY);
+ });
+}
+
+function handleBrowserRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext,
+) {
+ return new Promise((resolve, reject) => {
+ let shellRendered = false;
+ const { pipe, abort } = renderToPipeableStream(
+ <RemixServer
+ context={remixContext}
+ url={request.url}
+ abortDelay={ABORT_DELAY}
+ />,
+ {
+ onShellReady() {
+ shellRendered = true;
+ const body = new PassThrough();
+ const stream = createReadableStreamFromReadable(body);
+
+ responseHeaders.set("Content-Type", "text/html");
+
+ resolve(
+ new Response(stream, {
+ headers: responseHeaders,
+ status: responseStatusCode,
+ }),
+ );
+
+ pipe(body);
+ },
+ onShellError(error: unknown) {
+ reject(error);
+ },
+ onError(error: unknown) {
+ responseStatusCode = 500;
+ if (shellRendered) {
+ console.error(error);
+ }
+ },
+ },
+ );
+
+ setTimeout(abort, ABORT_DELAY);
+ });
+}
diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx
new file mode 100644
index 0000000..9768f45
--- /dev/null
+++ b/frontend/app/root.tsx
@@ -0,0 +1,35 @@
+import type { LinksFunction } from "@remix-run/node";
+import {
+ Links,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "@remix-run/react";
+import "./tailwind.css";
+
+export const links: LinksFunction = () => {
+ return [{ rel: "icon", href: "/favicon.svg" }];
+};
+
+export function Layout({ children }: { children: React.ReactNode }) {
+ return (
+ <html lang="en">
+ <head>
+ <meta charSet="utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <Meta />
+ <Links />
+ </head>
+ <body>
+ {children}
+ <ScrollRestoration />
+ <Scripts />
+ </body>
+ </html>
+ );
+}
+
+export default function App() {
+ return <Outlet />;
+}
diff --git a/frontend/app/routes/_index.tsx b/frontend/app/routes/_index.tsx
new file mode 100644
index 0000000..f39c7eb
--- /dev/null
+++ b/frontend/app/routes/_index.tsx
@@ -0,0 +1,9 @@
+import type { MetaFunction } from "@remix-run/node";
+
+export const meta: MetaFunction = () => {
+ return [{ title: "Albatross.swift" }];
+};
+
+export default function Index() {
+ return <p>iOSDC 2024 Albatross.swift</p>;
+}
diff --git a/frontend/app/tailwind.css b/frontend/app/tailwind.css
new file mode 100644
index 0000000..b5c61c9
--- /dev/null
+++ b/frontend/app/tailwind.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;