Login with GitHub and Magic link is working
This commit is contained in:
@@ -5,4 +5,5 @@ GITHUB_CLIENT_ID=<random string>
|
||||
GITHUB_SECRET=<random string>
|
||||
MAILGUN_KEY=<random string>
|
||||
FROM_EMAIL=<your email address>
|
||||
MERGENT_KEY=<Mergent key>
|
||||
MERGENT_KEY=<Mergent key>
|
||||
APP_ORIGIN=http://localhost:3000
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import classnames from "classnames";
|
||||
import type { User } from "~/models/user.server";
|
||||
// import { UserProfilePhoto } from "./UserProfilePhoto";
|
||||
import { UserProfilePhoto } from "./UserProfilePhoto";
|
||||
|
||||
const userNavigation = [{ name: "Logout", href: "/logout" }];
|
||||
|
||||
@@ -11,7 +11,7 @@ export function UserProfileMenu({ user }: { user: User }) {
|
||||
<div>
|
||||
<Menu.Button className="transitions flex max-w-xs items-center rounded-full bg-white text-sm">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
{/* <UserProfilePhoto user={user} className="h-7 w-7" /> */}
|
||||
<UserProfilePhoto user={user} className="h-7 w-7" />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
@@ -23,7 +23,7 @@ export function UserProfileMenu({ user }: { user: User }) {
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-white pb-2 pt-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
{/* {user.name ? (
|
||||
{user.name ? (
|
||||
<h2 className="mb-2 block border-b border-slate-200 py-2 pl-5 pr-2 text-sm font-semibold text-slate-600">
|
||||
{user.name}
|
||||
</h2>
|
||||
@@ -31,7 +31,7 @@ export function UserProfileMenu({ user }: { user: User }) {
|
||||
<h2 className="mb-2 block border-b border-slate-200 py-2 pl-5 pr-2 text-sm font-semibold text-slate-600">
|
||||
{user.email}
|
||||
</h2>
|
||||
)} */}
|
||||
)}
|
||||
|
||||
{userNavigation.map((item) => (
|
||||
<Menu.Item key={item.name}>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { UserCircleIcon } from "@heroicons/react/24/solid";
|
||||
import classNames from "classnames";
|
||||
import type { User } from "~/models/user.server";
|
||||
|
||||
export function UserProfilePhoto({
|
||||
user,
|
||||
className,
|
||||
}: {
|
||||
user: User;
|
||||
className?: string;
|
||||
}) {
|
||||
return user.avatarUrl ? (
|
||||
<img
|
||||
className={classNames("rounded-full", className)}
|
||||
src={user.avatarUrl}
|
||||
alt={user.name ?? user.displayName ?? "User"}
|
||||
/>
|
||||
) : (
|
||||
<UserCircleIcon className={classNames("text-gray-400", className)} />
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { PrismaClient, Prisma } from ".prisma/client";
|
||||
import { PrismaClient } from ".prisma/client";
|
||||
import invariant from "tiny-invariant";
|
||||
import { env } from "./env.server";
|
||||
|
||||
let prisma: PrismaClient;
|
||||
|
||||
@@ -11,7 +12,7 @@ declare global {
|
||||
// the server with every change, but we want to make sure we don't
|
||||
// create a new connection to the DB with every change either.
|
||||
// in production we'll have a single connection to the DB.
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
if (env.NODE_ENV === "production") {
|
||||
prisma = getClient();
|
||||
} else {
|
||||
if (!global.__db__) {
|
||||
@@ -21,15 +22,15 @@ if (process.env.NODE_ENV === "production") {
|
||||
}
|
||||
|
||||
function getClient() {
|
||||
const { DATABASE_URL } = process.env;
|
||||
const { DATABASE_URL } = env;
|
||||
invariant(typeof DATABASE_URL === "string", "DATABASE_URL env var not set");
|
||||
|
||||
const databaseUrl = new URL(DATABASE_URL);
|
||||
|
||||
const isLocalHost = databaseUrl.hostname === "localhost";
|
||||
|
||||
const PRIMARY_REGION = isLocalHost ? null : process.env.PRIMARY_REGION;
|
||||
const FLY_REGION = isLocalHost ? null : process.env.FLY_REGION;
|
||||
const PRIMARY_REGION = isLocalHost ? null : env.PRIMARY_REGION;
|
||||
const FLY_REGION = isLocalHost ? null : env.FLY_REGION;
|
||||
|
||||
const isReadReplicaRegion = !PRIMARY_REGION || PRIMARY_REGION === FLY_REGION;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function handleRequest(
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
if (env.NODE_ENV === "production") {
|
||||
Sentry.init({
|
||||
dsn: env.SENTRY_DSN,
|
||||
tracesSampleRate: 1,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const EnvironmentSchema = z.object({
|
||||
NODE_ENV: z.union([
|
||||
z.literal("development"),
|
||||
z.literal("production"),
|
||||
z.literal("test"),
|
||||
]),
|
||||
REMIX_APP_PORT: z.string().optional(),
|
||||
DATABASE_URL: z.string(),
|
||||
APP_ORIGIN: z.string().default("https://app.trigger.dev"),
|
||||
SENTRY_DSN: z
|
||||
.string()
|
||||
@@ -14,6 +21,9 @@ const EnvironmentSchema = z.object({
|
||||
MAILGUN_KEY: z.string(),
|
||||
FROM_EMAIL: z.string(),
|
||||
MERGENT_KEY: z.string(),
|
||||
PRIMARY_REGION: z.string().optional(),
|
||||
FLY_REGION: z.string().optional(),
|
||||
SESSION_SECRET: z.string(),
|
||||
});
|
||||
|
||||
export type Environment = z.infer<typeof EnvironmentSchema>;
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import type { Session } from "@remix-run/node";
|
||||
import { redirect } from "remix-typedjson";
|
||||
import { createCookieSessionStorage } from "@remix-run/node";
|
||||
import invariant from "tiny-invariant";
|
||||
import { env } from "~/env.server";
|
||||
|
||||
export type ToastMessage = { message: string; type: "success" | "error" };
|
||||
|
||||
const ONE_YEAR = 1000 * 60 * 60 * 24 * 365;
|
||||
|
||||
invariant(process.env.SESSION_SECRET, "SESSION_SECRET must be set");
|
||||
|
||||
export const { commitSession, getSession } = createCookieSessionStorage({
|
||||
cookie: {
|
||||
name: "__message",
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secrets: [process.env.SESSION_SECRET],
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
secrets: [env.SESSION_SECRET],
|
||||
secure: env.NODE_ENV === "production",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
LinksFunction,
|
||||
LoaderArgs,
|
||||
LoaderFunction,
|
||||
MetaFunction,
|
||||
} from "@remix-run/node";
|
||||
@@ -25,6 +26,7 @@ import { useEffect, useRef } from "react";
|
||||
import posthog from "posthog-js";
|
||||
import { withSentry } from "@sentry/remix";
|
||||
import { env } from "./env.server";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: tailwindStylesheetUrl }];
|
||||
@@ -36,18 +38,12 @@ export const meta: MetaFunction = () => ({
|
||||
viewport: "width=device-width,initial-scale=1",
|
||||
});
|
||||
|
||||
type LoaderData = {
|
||||
user: Awaited<ReturnType<typeof getUser>>;
|
||||
toastMessage: ToastMessage | null;
|
||||
posthogProjectKey?: string;
|
||||
};
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
export const loader = async ({ request }: LoaderArgs) => {
|
||||
const session = await getSession(request.headers.get("cookie"));
|
||||
const toastMessage = session.get("toastMessage") as ToastMessage;
|
||||
const posthogProjectKey = env.POSTHOG_PROJECT_KEY;
|
||||
|
||||
return json<LoaderData>(
|
||||
return typedjson(
|
||||
{
|
||||
user: await getUser(request),
|
||||
toastMessage,
|
||||
@@ -58,7 +54,8 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||
};
|
||||
|
||||
function App() {
|
||||
const { toastMessage, posthogProjectKey, user } = useLoaderData<LoaderData>();
|
||||
const { toastMessage, posthogProjectKey, user } =
|
||||
useTypedLoaderData<typeof loader>();
|
||||
const postHogInitialised = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -21,7 +21,6 @@ export async function loader({ request }: LoaderArgs) {
|
||||
export default function AppLayout() {
|
||||
return (
|
||||
<div className="flex h-screen flex-col overflow-auto">
|
||||
<Header>Home</Header>
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson } from "remix-typedjson";
|
||||
import { Header } from "~/components/Header";
|
||||
|
||||
export const loader = async ({ request }: LoaderArgs) => {
|
||||
return typedjson({});
|
||||
@@ -7,8 +8,11 @@ export const loader = async ({ request }: LoaderArgs) => {
|
||||
|
||||
export default function AppLayout() {
|
||||
return (
|
||||
<div className="flex h-screen flex-col overflow-auto">
|
||||
adsadsdasasd asads asa dsa ds ads
|
||||
</div>
|
||||
<>
|
||||
<Header>Home</Header>
|
||||
<div className="flex h-screen flex-col overflow-auto">
|
||||
adsadsdasasd asads asa dsa ds ads
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ const gitHubStrategy = new GitHubStrategy(
|
||||
{
|
||||
clientID: env.GITHUB_CLIENT_ID ?? "",
|
||||
clientSecret: env.GITHUB_SECRET ?? "",
|
||||
callbackURL: `${process.env.APP_ORIGIN}/auth/github/callback`,
|
||||
callbackURL: `${env.APP_ORIGIN}/auth/github/callback`,
|
||||
},
|
||||
async ({ accessToken, extraParams, profile }) => {
|
||||
const emails = profile.emails;
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { createCookieSessionStorage } from "@remix-run/node";
|
||||
import invariant from "tiny-invariant";
|
||||
import { z } from "zod";
|
||||
import { env } from "~/env.server";
|
||||
|
||||
const ONE_DAY = 60 * 60 * 24;
|
||||
|
||||
invariant(process.env.SESSION_SECRET, "SESSION_SECRET must be set");
|
||||
|
||||
export const { commitSession, getSession } = createCookieSessionStorage({
|
||||
cookie: {
|
||||
name: "__redirectTo",
|
||||
path: "/",
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secrets: [process.env.SESSION_SECRET],
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
secrets: [env.SESSION_SECRET],
|
||||
secure: env.NODE_ENV === "production",
|
||||
maxAge: ONE_DAY,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,6 +14,8 @@ export async function getUser(request: Request) {
|
||||
const user = await getUserById(userId);
|
||||
if (user) return user;
|
||||
|
||||
console.log("user", user);
|
||||
|
||||
throw await logout(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { createCookieSessionStorage } from "@remix-run/node";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
invariant(process.env.SESSION_SECRET, "SESSION_SECRET must be set");
|
||||
import { env } from "~/env.server";
|
||||
|
||||
export const sessionStorage = createCookieSessionStorage({
|
||||
cookie: {
|
||||
@@ -9,8 +7,8 @@ export const sessionStorage = createCookieSessionStorage({
|
||||
sameSite: "lax", // this helps with CSRF
|
||||
path: "/", // remember to add this so the cookie will work in all routes
|
||||
httpOnly: true, // for security reasons, make this cookie http only
|
||||
secrets: [process.env.SESSION_SECRET],
|
||||
secure: process.env.NODE_ENV === "production", // enable this in prod only
|
||||
secrets: [env.SESSION_SECRET],
|
||||
secure: env.NODE_ENV === "production", // enable this in prod only
|
||||
maxAge: 60 * 60 * 24 * 365, // 7 days
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ export function useMatchesData(
|
||||
id: string
|
||||
): Record<string, unknown> | undefined {
|
||||
const matchingRoutes = useMatches();
|
||||
console.log("matchingRoutes", matchingRoutes);
|
||||
const route = useMemo(
|
||||
() => matchingRoutes.find((route) => route.id === id),
|
||||
[matchingRoutes, id]
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "^29.2.0",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/node": "^18.8.0",
|
||||
"@types/node": "^18.11.11",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/slug": "^5.0.3",
|
||||
|
||||
@@ -4,9 +4,10 @@ import compression from "compression";
|
||||
import morgan from "morgan";
|
||||
import { createRequestHandler as expressCreateRequestHandler } from "@remix-run/express";
|
||||
import { wrapExpressCreateRequestHandler } from "@sentry/remix";
|
||||
import { env } from "~/env.server";
|
||||
|
||||
const createRequestHandler =
|
||||
process.env.NODE_ENV === "production"
|
||||
env.NODE_ENV === "production"
|
||||
? wrapExpressCreateRequestHandler(expressCreateRequestHandler)
|
||||
: expressCreateRequestHandler;
|
||||
|
||||
@@ -14,7 +15,7 @@ const app = express();
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// helpful headers:
|
||||
res.set("x-fly-region", process.env.FLY_REGION ?? "unknown");
|
||||
res.set("x-fly-region", env.FLY_REGION ?? "unknown");
|
||||
res.set("Strict-Transport-Security", `max-age=${60 * 60 * 24 * 365 * 100}`);
|
||||
|
||||
// /clean-urls/ -> /clean-urls
|
||||
@@ -33,7 +34,7 @@ app.use((req, res, next) => {
|
||||
// learn more: https://fly.io/docs/getting-started/multi-region-databases/#replay-the-request
|
||||
app.all("*", function getReplayResponse(req, res, next) {
|
||||
const { method, path: pathname } = req;
|
||||
const { PRIMARY_REGION, FLY_REGION } = process.env;
|
||||
const { PRIMARY_REGION, FLY_REGION } = env;
|
||||
|
||||
const isMethodReplayable = !["GET", "OPTIONS", "HEAD"].includes(method);
|
||||
const isReadOnlyRegion =
|
||||
@@ -71,7 +72,7 @@ app.use(express.static("public", { maxAge: "1h" }));
|
||||
|
||||
app.use(morgan("tiny"));
|
||||
|
||||
const MODE = process.env.NODE_ENV;
|
||||
const MODE = env.NODE_ENV;
|
||||
const BUILD_DIR = path.join(process.cwd(), "build");
|
||||
|
||||
app.all(
|
||||
@@ -88,7 +89,7 @@ app.all(
|
||||
}
|
||||
);
|
||||
|
||||
const port = process.env.REMIX_APP_PORT || 3000;
|
||||
const port = env.REMIX_APP_PORT || 3000;
|
||||
|
||||
app.listen(port, () => {
|
||||
// require the built app so we're ready when the first request comes in
|
||||
|
||||
Generated
+1
-1
@@ -62,7 +62,7 @@ importers:
|
||||
'@types/jest': ^29.2.0
|
||||
'@types/mailgun-js': ^0.22.12
|
||||
'@types/morgan': ^1.9.3
|
||||
'@types/node': ^18.8.0
|
||||
'@types/node': ^18.11.11
|
||||
'@types/react': ^18.0.21
|
||||
'@types/react-dom': ^18.0.6
|
||||
'@types/slug': ^5.0.3
|
||||
|
||||
Reference in New Issue
Block a user