diff --git a/apps/webapp/.env.example b/apps/webapp/.env.example index bc51f9231..523c1448a 100644 --- a/apps/webapp/.env.example +++ b/apps/webapp/.env.example @@ -5,4 +5,5 @@ GITHUB_CLIENT_ID= GITHUB_SECRET= MAILGUN_KEY= FROM_EMAIL= -MERGENT_KEY= \ No newline at end of file +MERGENT_KEY= +APP_ORIGIN=http://localhost:3000 \ No newline at end of file diff --git a/apps/webapp/app/components/UserProfileMenu.tsx b/apps/webapp/app/components/UserProfileMenu.tsx index 512a2c9ec..9ba418e52 100644 --- a/apps/webapp/app/components/UserProfileMenu.tsx +++ b/apps/webapp/app/components/UserProfileMenu.tsx @@ -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 }) {
Open user menu - {/* */} +
- {/* {user.name ? ( + {user.name ? (

{user.name}

@@ -31,7 +31,7 @@ export function UserProfileMenu({ user }: { user: User }) {

{user.email}

- )} */} + )} {userNavigation.map((item) => ( diff --git a/apps/webapp/app/components/UserProfilePhoto.tsx b/apps/webapp/app/components/UserProfilePhoto.tsx new file mode 100644 index 000000000..943a4b188 --- /dev/null +++ b/apps/webapp/app/components/UserProfilePhoto.tsx @@ -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 ? ( + {user.name + ) : ( + + ); +} diff --git a/apps/webapp/app/db.server.ts b/apps/webapp/app/db.server.ts index d7c71be22..44f021206 100644 --- a/apps/webapp/app/db.server.ts +++ b/apps/webapp/app/db.server.ts @@ -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; diff --git a/apps/webapp/app/entry.server.tsx b/apps/webapp/app/entry.server.tsx index 529cd6abc..3f1933c8d 100644 --- a/apps/webapp/app/entry.server.tsx +++ b/apps/webapp/app/entry.server.tsx @@ -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, diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index c46dbd3da..e88fe8f13 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -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; diff --git a/apps/webapp/app/models/message.server.ts b/apps/webapp/app/models/message.server.ts index ecafe992f..4948070d3 100644 --- a/apps/webapp/app/models/message.server.ts +++ b/apps/webapp/app/models/message.server.ts @@ -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", }, }); diff --git a/apps/webapp/app/root.tsx b/apps/webapp/app/root.tsx index c06813d87..5eee96cc1 100644 --- a/apps/webapp/app/root.tsx +++ b/apps/webapp/app/root.tsx @@ -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>; - 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( + return typedjson( { user: await getUser(request), toastMessage, @@ -58,7 +54,8 @@ export const loader: LoaderFunction = async ({ request }) => { }; function App() { - const { toastMessage, posthogProjectKey, user } = useLoaderData(); + const { toastMessage, posthogProjectKey, user } = + useTypedLoaderData(); const postHogInitialised = useRef(false); useEffect(() => { diff --git a/apps/webapp/app/routes/__app.tsx b/apps/webapp/app/routes/__app.tsx index dd7c10981..c5740af7f 100644 --- a/apps/webapp/app/routes/__app.tsx +++ b/apps/webapp/app/routes/__app.tsx @@ -21,7 +21,6 @@ export async function loader({ request }: LoaderArgs) { export default function AppLayout() { return (
-
Home
); diff --git a/apps/webapp/app/routes/__app/index.tsx b/apps/webapp/app/routes/__app/index.tsx index 14c3ae4b8..49c5cb657 100644 --- a/apps/webapp/app/routes/__app/index.tsx +++ b/apps/webapp/app/routes/__app/index.tsx @@ -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 ( -
- adsadsdasasd asads asa dsa ds ads -
+ <> +
Home
+
+ adsadsdasasd asads asa dsa ds ads +
+ ); } diff --git a/apps/webapp/app/services/gitHubAuth.server.ts b/apps/webapp/app/services/gitHubAuth.server.ts index 6e0f2289b..a936f9d4f 100644 --- a/apps/webapp/app/services/gitHubAuth.server.ts +++ b/apps/webapp/app/services/gitHubAuth.server.ts @@ -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; diff --git a/apps/webapp/app/services/redirectTo.server.ts b/apps/webapp/app/services/redirectTo.server.ts index da6505daa..b1e98c53d 100644 --- a/apps/webapp/app/services/redirectTo.server.ts +++ b/apps/webapp/app/services/redirectTo.server.ts @@ -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, }, }); diff --git a/apps/webapp/app/services/session.server.ts b/apps/webapp/app/services/session.server.ts index 8d0215aab..ff666fef6 100644 --- a/apps/webapp/app/services/session.server.ts +++ b/apps/webapp/app/services/session.server.ts @@ -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); } diff --git a/apps/webapp/app/services/sessionStorage.server.ts b/apps/webapp/app/services/sessionStorage.server.ts index 4428250b7..3bb9a51fa 100644 --- a/apps/webapp/app/services/sessionStorage.server.ts +++ b/apps/webapp/app/services/sessionStorage.server.ts @@ -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 }, }); diff --git a/apps/webapp/app/utils.ts b/apps/webapp/app/utils.ts index a7eb66eb3..5595528e1 100644 --- a/apps/webapp/app/utils.ts +++ b/apps/webapp/app/utils.ts @@ -37,6 +37,7 @@ export function useMatchesData( id: string ): Record | undefined { const matchingRoutes = useMatches(); + console.log("matchingRoutes", matchingRoutes); const route = useMemo( () => matchingRoutes.find((route) => route.id === id), [matchingRoutes, id] diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 5e7884470..47eb2aba4 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -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", diff --git a/apps/webapp/server.ts b/apps/webapp/server.ts index 2a07a5678..1d3421d79 100644 --- a/apps/webapp/server.ts +++ b/apps/webapp/server.ts @@ -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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b59c9c01..68c632493 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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