diff --git a/gdpr-cookie-consent/.eslintrc.cjs b/gdpr-cookie-consent/.eslintrc.cjs
new file mode 100644
index 00000000..4f6f59ee
--- /dev/null
+++ b/gdpr-cookie-consent/.eslintrc.cjs
@@ -0,0 +1,84 @@
+/**
+ * This is intended to be a basic starting point for linting in your app.
+ * It relies on recommended configs out of the box for simplicity, but you can
+ * and should modify this configuration to best suit your team's needs.
+ */
+
+/** @type {import('eslint').Linter.Config} */
+module.exports = {
+ root: true,
+ parserOptions: {
+ ecmaVersion: "latest",
+ sourceType: "module",
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ env: {
+ browser: true,
+ commonjs: true,
+ es6: true,
+ },
+ ignorePatterns: ["!**/.server", "!**/.client"],
+
+ // Base config
+ extends: ["eslint:recommended"],
+
+ overrides: [
+ // React
+ {
+ files: ["**/*.{js,jsx,ts,tsx}"],
+ plugins: ["react", "jsx-a11y"],
+ extends: [
+ "plugin:react/recommended",
+ "plugin:react/jsx-runtime",
+ "plugin:react-hooks/recommended",
+ "plugin:jsx-a11y/recommended",
+ ],
+ settings: {
+ react: {
+ version: "detect",
+ },
+ formComponents: ["Form"],
+ linkComponents: [
+ { name: "Link", linkAttribute: "to" },
+ { name: "NavLink", linkAttribute: "to" },
+ ],
+ "import/resolver": {
+ typescript: {},
+ },
+ },
+ },
+
+ // Typescript
+ {
+ files: ["**/*.{ts,tsx}"],
+ plugins: ["@typescript-eslint", "import"],
+ parser: "@typescript-eslint/parser",
+ settings: {
+ "import/internal-regex": "^~/",
+ "import/resolver": {
+ node: {
+ extensions: [".ts", ".tsx"],
+ },
+ typescript: {
+ alwaysTryTypes: true,
+ },
+ },
+ },
+ extends: [
+ "plugin:@typescript-eslint/recommended",
+ "plugin:import/recommended",
+ "plugin:import/typescript",
+ ],
+ },
+
+ // Node
+ {
+ files: [".eslintrc.cjs"],
+ env: {
+ node: true,
+ },
+ },
+ ],
+};
diff --git a/gdpr-cookie-consent/.eslintrc.js b/gdpr-cookie-consent/.eslintrc.js
deleted file mode 100644
index 2061cd22..00000000
--- a/gdpr-cookie-consent/.eslintrc.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('eslint').Linter.Config} */
-module.exports = {
- extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
-};
diff --git a/gdpr-cookie-consent/.gitignore b/gdpr-cookie-consent/.gitignore
index 3f7bf98d..80ec311f 100644
--- a/gdpr-cookie-consent/.gitignore
+++ b/gdpr-cookie-consent/.gitignore
@@ -2,5 +2,4 @@ node_modules
/.cache
/build
-/public/build
.env
diff --git a/gdpr-cookie-consent/README.md b/gdpr-cookie-consent/README.md
index 6fad5e99..dda7ac1d 100644
--- a/gdpr-cookie-consent/README.md
+++ b/gdpr-cookie-consent/README.md
@@ -1,7 +1,6 @@
# GDPR Cookie Consent
-Create a simple GDPR consent form.
-Till the user doesn't click on the `Accept` button, she will see a banner prompting to accept cookies.
+Create a simple GDPR consent form, displayed until the button click the `Accept` button.
## Preview
@@ -14,7 +13,7 @@ Open this example on [CodeSandbox](https://codesandbox.com):
Users will be presented with a GDPR consent form on every page [app/root.tsx](app/root.tsx) till they submit the accept button.
Once they submit the consent form, a dummy tracking [script](public/dummy-analytics-script.js) at the [app/root.tsx](app/root.tsx) will start tracking the user's data (open the browser console too see the dummy tracking message).
-> If you want to reset the example delete the `gdpr-consent` cookie in the `Application`/`cookies` in the browser's developer tools.
+> If you want to reset the example, delete the `gdpr-consent` cookie in the `Application`/`cookies` in the browser's developer tools.
The example is using [Remix Cookie API](https://remix.run/utils/cookies).
diff --git a/gdpr-cookie-consent/app/cookies.ts b/gdpr-cookie-consent/app/cookies.server.ts
similarity index 100%
rename from gdpr-cookie-consent/app/cookies.ts
rename to gdpr-cookie-consent/app/cookies.server.ts
diff --git a/gdpr-cookie-consent/app/root.tsx b/gdpr-cookie-consent/app/root.tsx
index 9aa5e3fc..a8f6dc11 100644
--- a/gdpr-cookie-consent/app/root.tsx
+++ b/gdpr-cookie-consent/app/root.tsx
@@ -1,35 +1,34 @@
-import type { LoaderArgs, MetaFunction } from "@remix-run/node";
-import { json } from "@remix-run/node";
+import type { LoaderFunctionArgs } from "@remix-run/node";
import {
Links,
- LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
+ json,
useFetcher,
- useLoaderData,
+ useRouteLoaderData,
} from "@remix-run/react";
-import * as React from "react";
+import { useEffect } from "react";
-import { gdprConsent } from "~/cookies";
+import { gdprConsent } from "~/cookies.server";
-export const loader = async ({ request }: LoaderArgs) => {
+export const loader = async ({ request }: LoaderFunctionArgs) => {
const cookieHeader = request.headers.get("Cookie");
const cookie = (await gdprConsent.parse(cookieHeader)) || {};
+
return json({ track: cookie.gdprConsent });
};
-export const meta: MetaFunction = () => ({
- charset: "utf-8",
- title: "New Remix App",
- viewport: "width=device-width,initial-scale=1",
-});
+export function Layout({ children }: { children: React.ReactNode }) {
+ // We use `useRouteLoaderData` here instead of `useLoaderData` because
+ // the component will also be used by the
+ // if an error is thrown somewhere in the app, and we can't call
+ // `useLoaderData()` while rendering an .
+ const rootLoaderData = useRouteLoaderData<{ track?: true }>("root");
+ const track = rootLoaderData?.track ?? false;
-export default function App() {
- const { track } = useLoaderData();
- const analyticsFetcher = useFetcher();
- React.useEffect(() => {
+ useEffect(() => {
if (track) {
const script = document.createElement("script");
script.src = "/dummy-analytics-script.js";
@@ -37,14 +36,18 @@ export default function App() {
}
}, [track]);
+ const analyticsFetcher = useFetcher();
+
return (
+
+
-
+ {children}
{track ? null : (
-
);
}
+
+export default function App() {
+ return ;
+}
diff --git a/gdpr-cookie-consent/app/routes/enable-analytics.tsx b/gdpr-cookie-consent/app/routes/enable-analytics.tsx
index 18c20d62..a8d6c4c9 100644
--- a/gdpr-cookie-consent/app/routes/enable-analytics.tsx
+++ b/gdpr-cookie-consent/app/routes/enable-analytics.tsx
@@ -1,9 +1,9 @@
-import type { ActionArgs } from "@remix-run/node";
+import type { ActionFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
-import { gdprConsent } from "~/cookies";
+import { gdprConsent } from "~/cookies.server";
-export const action = async ({ request }: ActionArgs) => {
+export const action = async ({ request }: ActionFunctionArgs) => {
const formData = await request.formData();
const cookieHeader = request.headers.get("Cookie");
const cookie = (await gdprConsent.parse(cookieHeader)) || {};
diff --git a/gdpr-cookie-consent/package.json b/gdpr-cookie-consent/package.json
index a437590d..1314c27c 100644
--- a/gdpr-cookie-consent/package.json
+++ b/gdpr-cookie-consent/package.json
@@ -1,29 +1,40 @@
{
+ "name": "gdpr-cookie-consent",
"private": true,
"sideEffects": false,
+ "type": "module",
"scripts": {
- "build": "remix build",
- "dev": "remix dev",
- "start": "remix-serve build",
+ "build": "remix vite:build",
+ "dev": "remix vite:dev",
+ "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
+ "start": "remix-serve ./build/server/index.js",
"typecheck": "tsc"
},
"dependencies": {
- "@remix-run/node": "^1.19.3",
- "@remix-run/react": "^1.19.3",
- "@remix-run/serve": "^1.19.3",
- "isbot": "^3.6.5",
+ "@remix-run/node": "^2.9.2",
+ "@remix-run/react": "^2.9.2",
+ "@remix-run/serve": "^2.9.2",
+ "isbot": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
- "@remix-run/dev": "^1.19.3",
- "@remix-run/eslint-config": "^1.19.3",
- "@types/react": "^18.0.25",
- "@types/react-dom": "^18.0.8",
- "eslint": "^8.27.0",
- "typescript": "^4.8.4"
+ "@remix-run/dev": "^2.9.2",
+ "@types/react": "^18.2.20",
+ "@types/react-dom": "^18.2.7",
+ "@typescript-eslint/eslint-plugin": "^6.7.4",
+ "@typescript-eslint/parser": "^6.7.4",
+ "eslint": "^8.38.0",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "typescript": "^5.1.6",
+ "vite": "^5.1.0",
+ "vite-tsconfig-paths": "^4.2.1"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">=20.0.0"
}
}
diff --git a/gdpr-cookie-consent/remix.config.js b/gdpr-cookie-consent/remix.config.js
deleted file mode 100644
index ca00ba94..00000000
--- a/gdpr-cookie-consent/remix.config.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {
- future: {
- v2_routeConvention: true,
- },
- ignoredRouteFiles: ["**/.*"],
- // appDirectory: "app",
- // assetsBuildDirectory: "public/build",
- // publicPath: "/build/",
- // serverBuildPath: "build/index.js",
-};
diff --git a/gdpr-cookie-consent/remix.env.d.ts b/gdpr-cookie-consent/remix.env.d.ts
deleted file mode 100644
index dcf8c45e..00000000
--- a/gdpr-cookie-consent/remix.env.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-///
-///
diff --git a/gdpr-cookie-consent/tsconfig.json b/gdpr-cookie-consent/tsconfig.json
index 20f8a386..9d87dd37 100644
--- a/gdpr-cookie-consent/tsconfig.json
+++ b/gdpr-cookie-consent/tsconfig.json
@@ -1,22 +1,32 @@
{
- "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "**/.server/**/*.ts",
+ "**/.server/**/*.tsx",
+ "**/.client/**/*.ts",
+ "**/.client/**/*.tsx"
+ ],
"compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "types": ["@remix-run/node", "vite/client"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
- "moduleResolution": "node",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
"resolveJsonModule": true,
- "target": "ES2019",
+ "target": "ES2022",
"strict": true,
"allowJs": true,
+ "skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
- // Remix takes care of building everything in `remix build`.
+ // Vite takes care of building everything, not tsc.
"noEmit": true
}
}
diff --git a/gdpr-cookie-consent/vite.config.ts b/gdpr-cookie-consent/vite.config.ts
new file mode 100644
index 00000000..54066fb7
--- /dev/null
+++ b/gdpr-cookie-consent/vite.config.ts
@@ -0,0 +1,16 @@
+import { vitePlugin as remix } from "@remix-run/dev";
+import { defineConfig } from "vite";
+import tsconfigPaths from "vite-tsconfig-paths";
+
+export default defineConfig({
+ plugins: [
+ remix({
+ future: {
+ v3_fetcherPersist: true,
+ v3_relativeSplatPath: true,
+ v3_throwAbortReason: true,
+ },
+ }),
+ tsconfigPaths(),
+ ],
+});