Skip to content

PR | Hanko Remix Starter | Updated Starter -> Main #1

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

Merged
merged 6 commits into from
May 27, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
82 changes: 81 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +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 = {
extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
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,
},
},
],
};
20 changes: 0 additions & 20 deletions LICENSE

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The license should not be deleted,

This file was deleted.

51 changes: 51 additions & 0 deletions app/components/HankoAuth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useNavigate, useLoaderData } from "@remix-run/react";
import { Suspense, useCallback, useEffect, useState } from "react";
import { register, type Hanko } from "../utils/hanko.client";

export const loader = () => {
return { hankoUrl: process.env.HANKO_API_URL };
};

import "./hanko-style.css"

const HankoAuth = () => {
const [hanko, setHanko] = useState<Hanko>();
const navigate = useNavigate();

const data = useLoaderData<typeof loader>();
const hankoUrl = data.hankoUrl || '';

const redirectAfterLogin = useCallback(() => {
navigate("/dashboard");//Path user gets navigated to once they log in
}, [navigate]);

useEffect(() => {
if (hanko) {
hanko.onSessionCreated(() => {
redirectAfterLogin();
});
}
}, [hanko, redirectAfterLogin]);

useEffect(() => {
register(hankoUrl)
.catch((error: Error) => {
console.error(error.message);
})
.then((result) => {
if (result) {
setHanko(result.hanko);
}
});
}, [hankoUrl]);

return (
<div className="">
<Suspense fallback={"Loading..."}>
<hanko-auth />
</Suspense>
</div>
);
};

export default HankoAuth;
30 changes: 30 additions & 0 deletions app/components/HankoProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useLoaderData } from "@remix-run/react";
import { Suspense, useEffect, } from "react";
import { register } from "../utils/hanko.client";

import "./hanko-style.css"

export const loader = () => {
return { hankoUrl: process.env.HANKO_API_URL };
};

const HankoProfile = () => {
const data = useLoaderData<typeof loader>();
const hankoUrl = data.hankoUrl || '';

useEffect(() => {
register(hankoUrl).catch((error) => {
// handle error
console.log(error);
});
});

return(
<Suspense fallback={"Loading..."}>
<hanko-profile />
</Suspense>
)
}

export default HankoProfile;

41 changes: 41 additions & 0 deletions app/components/LogoutButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useLoaderData, useNavigate } from "@remix-run/react";

import { useEffect, useState, } from "react";
import { type Hanko, } from "../utils/hanko.client";


export const loader = () => {
return { hankoUrl: process.env.HANKO_API_URL };
};

const LogoutButton = () => {

const data = useLoaderData<typeof loader>();
const hankoUrl = data.hankoUrl || '';

const navigate = useNavigate();
const [hanko, setHanko] = useState<Hanko>();

useEffect(() => {
import("@teamhanko/hanko-elements").then(({ Hanko }) =>
setHanko(new Hanko(hankoUrl ?? ""))
);
}, []);

const logout = async () => {
try {
await hanko?.user.logout();

navigate("/");//Path the user will be redirected to once logged out

return;
} catch (error) {
console.error("Error during logout:", error);
}
};

return <button onClick={logout}>Logout</button>;
}

export default LogoutButton;

24 changes: 24 additions & 0 deletions app/components/hanko-style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

/* For more information on customization of hanko elements go to https://docs.hanko.io/guides/hanko-elements/customize-appearance */

@media (prefers-color-scheme: dark) {
hanko-auth,
hanko-profile {
/* Color Scheme */
--color: #ffffff;
--color-shade-1: rgb(174, 135, 142);
--color-shade-2: rgba(226, 188, 193, 0.306);

--brand-color: rgb(255, 46, 76);
--brand-color-shade-1: rgb(255, 84, 109);
--brand-contrast-color: white;

--background-color: transparent;
--error-color: rgb(190, 48, 70);
--link-color: rgb(255, 46, 76);

}
hanko-auth::part(container){
--background-color: rgba(0, 0, 0, 0.32);
}
}
7 changes: 5 additions & 2 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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 isbot from "isbot";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";

const ABORT_DELAY = 5_000;
Expand All @@ -19,9 +19,12 @@ export default function handleRequest(
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
// This is ignored so we can keep it in the template for visibility. Feel
// free to delete this parameter in your app if you're not using it!
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadContext: AppLoadContext
) {
return isbot(request.headers.get("user-agent"))
return isbot(request.headers.get("user-agent") || "")
? handleBotRequest(
request,
responseStatusCode,
Expand Down
74 changes: 74 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* Light mode color scheme */
:root {
--background: rgba(47, 91, 212, 0.015);
--backgroundGradient: white;
--foreground: #171717;
}

/* Dark mode color scheme */
@media (prefers-color-scheme: dark) {
:root {
--background: rgb(11, 16, 25);
--backgroundGradient: rgb(94, 14, 26);;
--foreground: #ededed;
}
}

html,
body {
max-width: 100vw;
overflow-x: hidden;
}

@media (max-aspect-ratio: 3/3) {
body{
padding-left: 0 !important;
}
}

body {
color: var(--foreground);

background: var(--background);
background: linear-gradient(120deg, var(--background) 30%, var(--backgroundGradient) 110%);

font-family: Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

padding-left: 37.5vw;

/* Center the items in the center */
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}

button{
width: 205px;
height: 6vh;
background-color: rgb(255, 255, 255);
color: rgb(36, 36, 36);
font-size: 150%;
font-weight: bold;
border-color: rgba(128, 128, 128, 0.404);
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
}

a {
color: inherit;
text-decoration: none;
}

@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
20 changes: 20 additions & 0 deletions app/hanko starter components/HankoStarterDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import './hanko-starter-style.css'

import { useUserData } from '../hooks/useUserData';

const HankoStarterDashboard = () => {

const { id, email} = useUserData();

return (
<div className='hankoStarterDashboard'>
<h1>Dashboard</h1><br />
<h2>Here is an example of using a custom Hook we made to get user data from Hanko using the Hanko Client.</h2>
<br />
<h3>Email: {email}</h3>
<h3>Id: {id}</h3>
</div>
)
}

export default HankoStarterDashboard
Loading