Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #21

Merged
merged 4 commits into from
Oct 3, 2024
Merged
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
7 changes: 7 additions & 0 deletions app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ This template leverages [Remix SPA Mode](https://remix.run/docs/en/main/future/s

📖 See the [Remix Vite docs][remix-vite-docs] for details on supported features.

## Docker

```shell
docker buildx build . -t bastienlaw:latest
docker run -d -p 8080 bastienlaw:latest
```

## Setup

```shellscript
Expand Down
4 changes: 4 additions & 0 deletions app/app/config/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const appConfig = {
youtube_video_id: "fdi_z9NLT_w?si=iSeO4L8snTfPbG5H",
youtube_video_title: "Villard Bastien"
};
160 changes: 150 additions & 10 deletions app/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,159 @@
import type { EntryContext } from "@remix-run/node";
import { PassThrough } from "node:stream";

import type { AppLoadContext, EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";
import { config } from "dotenv";
import * as isbotModule from "isbot";
import { renderToPipeableStream } from "react-dom/server";

// Load .env variables
config();

const ABORT_DELAY = 5_000;

export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
remixContext: EntryContext,
loadContext: AppLoadContext,
) {
const prohibitOutOfOrderStreaming =
isBotRequest(request.headers.get("user-agent")) || remixContext.isSpaMode;

return prohibitOutOfOrderStreaming
? handleBotRequest(
request,
responseStatusCode,
responseHeaders,
remixContext,
)
: handleBrowserRequest(
request,
responseStatusCode,
responseHeaders,
remixContext,
);
}

// We have some Remix apps in the wild already running with isbot@3 so we need
// to maintain backwards compatibility even though we want new apps to use
// isbot@4. That way, we can ship this as a minor Semver update to @remix-run/dev.
function isBotRequest(userAgent: string | null) {
if (!userAgent) {
return false;
}

// isbot >= 3.8.0, >4
if ("isbot" in isbotModule && typeof isbotModule.isbot === "function") {
return isbotModule.isbot(userAgent);
}

// isbot < 3.8.0
if ("default" in isbotModule && typeof isbotModule.default === "function") {
return isbotModule.default(userAgent);
}

return false;
}

function handleBotRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
let html = renderToString(
<RemixServer context={remixContext} url={request.url} />
);
html = "<!DOCTYPE html>\n" + html;
return new Response(html, {
headers: { "Content-Type": "text/html" },
status: responseStatusCode,
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;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
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;
// Log streaming rendering errors from inside the shell. Don't log
// errors encountered during initial shell rendering since they'll
// reject and get logged in handleDocumentRequest.
if (shellRendered) {
console.error(error);
}
},
},
);

setTimeout(abort, ABORT_DELAY);
});
}
52 changes: 48 additions & 4 deletions app/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,49 @@
import { json } from "@remix-run/node";

import {
Link,
Links,
Meta,
MetaFunction,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
useLocation
} from "@remix-run/react";

import { useEffect } from "react";

import * as gtag from "./utils/gtags.client";

import "./styles/typography.css";
import './styles/global.css';
import ScrollToTopButton from "./components/ScrollToTopButton";
// import "./styles/dsethtml.css"
// import "./styles/dsethtml2.css"

import ScrollToTopButton from "./components/ScrollToTopButton";
// import ChatbotScript from "./components/ChatBot";

// Load the GA tracking id from the .env
export const loader = async () => {
return json({ gaTrackingId: "G-BQBBSBFGZG" });
};

export const meta: MetaFunction = () => {
return [
{title: "Bastien Law"},
{name: "description", content: "Law Offices of Villard Bastien"},
];
};

export function Layout({children}: {children: React.ReactNode}) {
export function Layout({ children }: { children: React.ReactNode }) {
const location = useLocation();
const gaTrackingId = "G-BQBBSBFGZG";

useEffect(() => {
if (gaTrackingId?.length) {
gtag.pageview(location.pathname, gaTrackingId);
}
}, [location, gaTrackingId]);

return (
<html lang="en">
<head>
Expand All @@ -32,6 +53,29 @@ export function Layout({children}: {children: React.ReactNode}) {
<Links />
</head>
<body>
{process.env.NODE_ENV === "development" || !gaTrackingId ? null : (
<>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${gaTrackingId}`}
/>
<script
async
id="gtag-init"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', '${gaTrackingId}', {
page_path: window.location.pathname,
});
`,
}}
/>
</>
)}
{[children, <ScrollToTopButton key={0} />]}
<ScrollRestoration />
<Scripts />
Expand Down
10 changes: 10 additions & 0 deletions app/app/routes/$.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This is a 'catch-all' route that will match any URL that hasn't already been matched by another route.
// Navigate to the Home (index) route to see this route in action.

import { redirect } from "@remix-run/react";

// Provide a `loader` function to handle the data fetching for this route.
export const loader = async () => {
// Redirect to the home page
return redirect('/');
};
5 changes: 3 additions & 2 deletions app/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Header} from "../components/Header";
import {Footer} from "../components/Footer";

import YouTubeEmbed from "../components/YouTubeEmbed";
import { appConfig } from "../config/constants";

// ASSETS //
// Fonts
Expand All @@ -17,8 +18,8 @@ import VillardImg from "../wp-content/uploads/sites/1302270/2022/06/Villard-S-Ba
// Styles
import "../styles/home.css";

const YoutubeVideoId = "fdi_z9NLT_w?si=iSeO4L8snTfPbG5H";
const YouTubeVideoTitle = "Villard Bastien"
const YoutubeVideoId = appConfig.youtube_video_id;
const YouTubeVideoTitle = appConfig.youtube_video_title;

export default function Index() {
return (
Expand Down
48 changes: 48 additions & 0 deletions app/app/utils/gtags.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
declare global {
interface Window {
gtag: (
option: string,
gaTrackingId: string,
options: Record<string, unknown>,
) => void;
}
}

/**
* @example
* https://developers.google.com/analytics/devguides/collection/gtagjs/pages
*/
export const pageview = (url: string, trackingId: string) => {
if (!window.gtag) {
console.warn(
"window.gtag is not defined. This could mean your google analytics script has not loaded on the page yet.",
);
return;
}
window.gtag("config", trackingId, {
page_path: url,
});
};

/**
* @example
* https://developers.google.com/analytics/devguides/collection/gtagjs/events
*/
export const event = ({
action,
category,
label,
value,
}: Record<string, string>) => {
if (!window.gtag) {
console.warn(
"window.gtag is not defined. This could mean your google analytics script has not loaded on the page yet.",
);
return;
}
window.gtag("event", action, {
event_category: category,
event_label: label,
value: value,
});
};
Loading