Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Remix migration
  • Loading branch information
regalijan committed Oct 19, 2023
1 parent 5d2774f commit 04dcbb4
Show file tree
Hide file tree
Showing 33 changed files with 19,901 additions and 5,169 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Expand Up @@ -24,3 +24,8 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Remix files
.cache
functions/\[\[path\]\].js
public/build
2 changes: 1 addition & 1 deletion .node-version
@@ -1 +1 @@
v16.19.0
v18.14.2
19 changes: 19 additions & 0 deletions app/context.tsx
@@ -0,0 +1,19 @@
import { createContext } from "react";

export interface ServerStyleContextData {
key: string;
ids: Array<string>;
css: string;
}

export const ServerStyleContext = createContext<
ServerStyleContextData[] | null
>(null);

export interface ClientStyleContextData {
reset: () => void;
}

export const ClientStyleContext = createContext<ClientStyleContextData | null>(
null
);
7 changes: 7 additions & 0 deletions app/createEmotionCache.ts
@@ -0,0 +1,7 @@
import createCache from "@emotion/cache";

export const defaultCache = createEmotionCache();

export default function createEmotionCache() {
return createCache.default({ key: "cha" });
}
39 changes: 39 additions & 0 deletions app/entry.client.tsx
@@ -0,0 +1,39 @@
import { CacheProvider } from "@emotion/react";
import { ClientStyleContext } from "./context.js";
import createEmotionCache, { defaultCache } from "./createEmotionCache.js";
import { hydrateRoot } from "react-dom/client";
import { Integrations } from "@sentry/tracing";
import { RemixBrowser } from "@remix-run/react";
import * as Sentry from "@sentry/react";
import { type ReactNode, StrictMode, useState } from "react";

Sentry.init({
dsn:
document.querySelector("meta[name='dsn']")?.getAttribute("content") ??
undefined,
integrations: [new Integrations.BrowserTracing()],
tracesSampleRate: 0.1,
});

function ClientCacheProvider({ children }: { children: ReactNode }) {
const [cache, setCache] = useState(defaultCache);

function reset() {
setCache(createEmotionCache());
}

return (
<ClientStyleContext.Provider value={{ reset }}>
<CacheProvider value={cache}>{children}</CacheProvider>
</ClientStyleContext.Provider>
);
}

hydrateRoot(
document,
<StrictMode>
<ClientCacheProvider>
<RemixBrowser />
</ClientCacheProvider>
</StrictMode>
);
41 changes: 41 additions & 0 deletions app/entry.server.tsx
@@ -0,0 +1,41 @@
import { CacheProvider } from "@emotion/react";
import createEmotionCache from "./createEmotionCache.js";
import { createEmotionServer } from "../emotion-server.js";
import { type EntryContext } from "@remix-run/cloudflare";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
import { ServerStyleContext } from "./context.js";

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
const html = renderToString(
<ServerStyleContext.Provider value={null}>
<CacheProvider value={cache}>
<RemixServer context={remixContext} url={request.url} />
</CacheProvider>
</ServerStyleContext.Provider>
);

const chunks = extractCriticalToChunks(html);

const markup = renderToString(
<ServerStyleContext.Provider value={chunks.styles}>
<CacheProvider value={cache}>
<RemixServer context={remixContext} url={request.url} />
</CacheProvider>
</ServerStyleContext.Provider>
);

responseHeaders.set("content-type", "text/html;charset=utf-8");

return new Response("<!DOCTYPE html>" + markup, {
headers: responseHeaders,
status: responseStatusCode,
});
}
177 changes: 177 additions & 0 deletions app/root.tsx
@@ -0,0 +1,177 @@
import {
ChakraProvider,
Container,
cookieStorageManagerSSR,
Heading,
Link,
Text,
} from "@chakra-ui/react";
import { ClientStyleContext, ServerStyleContext } from "./context.js";
import fontStyle from "@fontsource/plus-jakarta-sans/index.css";
import Forbidden from "../components/Forbidden.js";
import globalStyles from "../index.css";
import { HelmetProvider } from "react-helmet-async";
import {
Links,
LiveReload,
Outlet,
Scripts,
useCatch,
useLoaderData,
} from "@remix-run/react";
import { LinksFunction } from "@remix-run/cloudflare";
import Login from "../components/Login.js";
import Navigation from "../components/Navigation.js";

import { type ReactNode, StrictMode, useContext, useEffect } from "react";
import theme from "../theme.js";
import { withEmotionCache } from "@emotion/react";

export function CatchBoundary() {
const { status } = useCatch();

switch (status) {
case 303:
return "";

case 401:
return getMarkup({ hide: true }, <Login />);

case 403:
return getMarkup({ hide: true }, <Forbidden />);

case 404:
return getMarkup(
{ hide: true },
<Container maxW="container.lg" pt="8vh" textAlign="left">
<Heading size="4xl">404</Heading>
<br />
<Text fontSize="xl">There is nothing to find here.</Text>
<br />
<br />
<br />
<Link color="#646cff" onClick={() => history.go(-1)}>
Go back
</Link>
</Container>
);

default:
return getMarkup(
{ hide: true },
<Container maxW="container.lg" pt="8vh" textAlign="left">
<Heading size="4xl">500</Heading>
<br />
<Text fontSize="xl">S̶̡͈̠̗̠͖͙̭o̶̶͕͚̥͍̪̤m̸̨͏͈͔̖͚̖̰̱͞e҉̵͖͚͇̀t̕͟͠͏͎̺̯̲̱̣̤̠̟͙̠̙̫̬ḩ̸̭͓̬͎̙̀į̞̮͉͖̰̥̹͚̫̙̪̗̜̳̕ͅn҉͔̯̪̗̝̝͖̲͇͍͎̲̲̤̖̫͈̪͡g̴̰̻̙̝͉̭͇̖̰̝̙͕̼͙͘͜ ̵̶̫̥̳̲̘̻̗͈͕̭̲͇̘̜̺̟̥̖̥b̴̙̭̹͕̞͠r̞͎̠̩͈̖̰̞̯̯͢͢͠ͅo̝̯̗̹̳͍̰͉͕̘̰̠̺̥̰͔̕ͅk̵̸̻̠͕̺̦̦͖̲̺̦̞̝̞͞͡e̶͏̤̼̼͔̘̰̰̭͈̀͞͡</Text>
<br />
<br />
<br />
<Link color="#646cff" onClick={() => location.reload()}>
Reload
</Link>
</Container>
);
}
}

export const links: LinksFunction = () => {
return [
{ href: "/favicon.ico", rel: "icon" },
{ href: "/files/logo192.png", rel: "apple-touch-icon", type: "image/png" },
{ href: fontStyle, rel: "stylesheet " },
{ href: globalStyles, rel: "stylesheet" },
];
};

export async function loader({
context,
}: {
context: RequestContext;
}): Promise<{ [k: string]: any }> {
let data: { [k: string]: string } = {};

if (context.data.current_user) data = { ...context.data.current_user };
if (context.env.DSN) data.dsn = context.env.DSN;
if (context.data.theme) data.theme = context.data.theme;

return data;
}

function getMarkup(
loaderData: { [k: string]: any },
child: ReactNode
): JSX.Element {
const Document = withEmotionCache(
({ children }: { children: ReactNode }, emotionCache) => {
const serverStyleData = useContext(ServerStyleContext);
const clientStyleData = useContext(ClientStyleContext);

useEffect(() => {
emotionCache.sheet.container = document.head;
const tags = emotionCache.sheet.tags;

emotionCache.sheet.flush();
tags.forEach((tag) => {
(emotionCache.sheet as any)._insertTag(tag);
});

clientStyleData?.reset();
}, []);

const helmetContext: { [k: string]: any } = {};

const body = (
<StrictMode>
<ChakraProvider
colorModeManager={cookieStorageManagerSSR(
typeof document === "undefined" ? "" : document.cookie
)}
theme={theme}
>
<HelmetProvider>
<div className="App">
<Navigation {...loaderData} />
{children}
<Scripts />
<LiveReload />
</div>
</HelmetProvider>
</ChakraProvider>
</StrictMode>
);

const { helmet } = helmetContext;

return (
<html lang="en-US">
<head>
<Links />
{serverStyleData?.map(({ key, ids, css }) => (
<style
key={key}
data-emotion={`${key} ${ids.join(" ")}`}
dangerouslySetInnerHTML={{ __html: css }}
/>
))}
<meta charSet="UTF-8" />
{loaderData.dsn ? (
<meta name="dsn" content={loaderData.dsn} />
) : null}
<meta name="theme-color" content="#00a8f8" />
{helmet.meta?.toString()}
{helmet.title?.toString() ?? <title>Car Crushers</title>}
</head>
<body>{body}</body>
</html>
);
}
);

return <Document>{child}</Document>;
}

export default function () {
const loaderData = useLoaderData<typeof loader>();

return getMarkup(loaderData, <Outlet />);
}
4 changes: 1 addition & 3 deletions pages/index.page.tsx → app/routes/_index.tsx
@@ -1,6 +1,6 @@
import { Box, Container, Text } from "@chakra-ui/react";

export function Page() {
export default function () {
return (
<>
<Box alignContent="left">
Expand All @@ -14,5 +14,3 @@ export function Page() {
</>
);
}

export const title = "Home - Car Crushers";
36 changes: 31 additions & 5 deletions pages/appeals.page.tsx → app/routes/appeals.tsx
Expand Up @@ -21,13 +21,39 @@ import {
useDisclosure,
useToast,
} from "@chakra-ui/react";
import { useLoaderData } from "@remix-run/react";
import { useState } from "react";
import Login from "../components/Login";
import Success from "../components/Success";
import Success from "../../components/Success.js";

export function Page(pageProps: { [p: string]: any }) {
if (!pageProps.logged_in) return <Login />;
export async function loader({ context }: { context: RequestContext }) {
if (!context.data.current_user)
throw new Response(null, {
status: 401,
});

const { current_user: currentUser } = context.data;
const dataKV = context.env.DATA;
const disabled = await dataKV.get("appeal_disabled");

return {
can_appeal:
!Boolean(disabled) &&
!Boolean(await dataKV.get(`blockedappeal_${currentUser.id}`)) &&
!Boolean(
(
await dataKV.list({
prefix: `appeal_${currentUser.id}`,
})
).keys.find((appeal) => (appeal.metadata as { [k: string]: any }).open)
),
can_toggle:
currentUser.permissions & (1 << 0) || currentUser.permissions & (1 << 11),
disabled: Boolean(disabled),
};
}

export default function () {
const pageProps = useLoaderData<typeof loader>();
const { isOpen, onClose, onOpen } = useDisclosure();
const [showSuccess, setShowSuccess] = useState(false);
const toast = useToast();
Expand Down Expand Up @@ -195,7 +221,7 @@ export function Page(pageProps: { [p: string]: any }) {
<br />
<br />
<Button
disabled={pageProps.disabled || pageProps.already_submitted}
disabled={pageProps.can_appeal}
onClick={async () => await submit()}
>
Submit
Expand Down

0 comments on commit 04dcbb4

Please sign in to comment.