From b63fc801010d9b8fd2299c8bf87fb48a7e47d4d4 Mon Sep 17 00:00:00 2001 From: hunar Date: Tue, 23 Jan 2024 18:13:41 +0530 Subject: [PATCH 01/30] added emails HTML for emails in bookings --- app/emails/bookings-updates-template.tsx | 116 +++++++++++++++++++++++ app/emails/invite-template.tsx | 12 +-- app/emails/styles.ts | 7 ++ app/modules/booking/email-helpers.ts | 8 ++ app/modules/booking/service.server.ts | 17 ++-- app/modules/booking/worker.server.ts | 8 ++ 6 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 app/emails/bookings-updates-template.tsx diff --git a/app/emails/bookings-updates-template.tsx b/app/emails/bookings-updates-template.tsx new file mode 100644 index 000000000..e4dc01b2c --- /dev/null +++ b/app/emails/bookings-updates-template.tsx @@ -0,0 +1,116 @@ +import type { Booking } from "@prisma/client"; +import { + Button, + Html, + Text, + Img, + Link, + Head, + render, + Container, + Heading, +} from "@react-email/components"; +import { SERVER_URL } from "~/utils/env"; +import { styles } from "./styles"; + +interface Props { + heading: string; + booking: any; + assetCount: number; +} + +export function BookingUpdatesEmailTemplate({ + booking, + heading, + assetCount, +}: Props) { + return ( + + + Bookings update from Shelf.nu + + + + Shelf's logo +
+ + {heading} + + + {booking.id} | {booking.name} | {assetCount}{" "} + {assetCount === 1 ? "asset" : "assets"} + +

+ + Custodian: + {" "} + {`${booking.custodianUser?.firstName} ${booking.custodianUser?.lastName}` || + booking.custodianTeamMember?.name} +

+

+ From:{" "} + {booking.from.toLocaleString()} +

+

+ To:{" "} + {booking.to.toLocaleString()} +

+
+ + + + This email was sent to{" "} + + {booking.custodianUser!.email} + {" "} + because it is part of the Shelf workspace. If you think you weren’t + supposed to have received this email please{" "} + + contact the owner + {" "} + of the workspace. + + + {" "} + © 2023 Shelf.nu, Meander 901, 6825 MH Arnhem + +
+ + ); +} + +export const bookingUpdatesTemplateString = ({ + booking, + heading, + assetCount, +}: Props) => + render( + + ); diff --git a/app/emails/invite-template.tsx b/app/emails/invite-template.tsx index 7d968acba..90e4d6cbb 100644 --- a/app/emails/invite-template.tsx +++ b/app/emails/invite-template.tsx @@ -33,9 +33,7 @@ export function InvitationEmailTemplate({ invite, token }: Props) { style={{ marginBottom: "24px" }} />
- + Howdy,
{invite.inviter.firstName} {invite.inviter.lastName} invites you to @@ -48,9 +46,7 @@ export function InvitationEmailTemplate({ invite, token }: Props) { > Accept the invite - + Once you’re done setting up your account, you'll be able to access the workspace and start exploring features like Asset Explorer, Location Tracking, Collaboration, Custom fields and more. If you @@ -61,9 +57,7 @@ export function InvitationEmailTemplate({ invite, token }: Props) { . - + Thanks,
The Shelf team
diff --git a/app/emails/styles.ts b/app/emails/styles.ts index ed261f0be..73a443e44 100644 --- a/app/emails/styles.ts +++ b/app/emails/styles.ts @@ -15,4 +15,11 @@ export const styles = { padding: "10px 18px", borderRadius: "4px", }, + h1: { + fontSize: "20px", color: "#101828", fontWeight: "600" + }, + h2: { + fontSize: "16px", color: "#101828", fontWeight: "600" + }, + p: { fontSize: "16px", color: "#344054" } }; diff --git a/app/modules/booking/email-helpers.ts b/app/modules/booking/email-helpers.ts index 7485e44f7..93be9ddb9 100644 --- a/app/modules/booking/email-helpers.ts +++ b/app/modules/booking/email-helpers.ts @@ -4,6 +4,7 @@ import { getDateTimeFormatFromHints } from "~/utils/client-hints"; import { getTimeRemainingMessage } from "~/utils/date-fns"; import { sendEmail } from "~/utils/mail.server"; import type { ClientHint } from "./types"; +import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template"; /** * THis is the base content of the bookings related emails. @@ -176,6 +177,13 @@ export const sendCheckinReminder = async ( to: booking.to!, bookingId: booking.id, }), + html: bookingUpdatesTemplateString({ + booking, heading: `Your booking is due for checkin in ${getTimeRemainingMessage( + new Date(booking.to!), + new Date() + )} minutes.`, + assetCount + }) }); }; diff --git a/app/modules/booking/service.server.ts b/app/modules/booking/service.server.ts index 8f3fc2d57..27889eb45 100644 --- a/app/modules/booking/service.server.ts +++ b/app/modules/booking/service.server.ts @@ -7,6 +7,7 @@ import { AssetStatus, } from "@prisma/client"; import { db } from "~/database"; +import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template"; import { calcTimeDifference } from "~/utils/date-fns"; import { ShelfStackError } from "~/utils/error"; import { sendEmail } from "~/utils/mail.server"; @@ -217,32 +218,32 @@ export const upsertBooking = async ( data.status === BookingStatus.COMPLETE || data.status === BookingStatus.CANCELLED ) { + const custodian = `${res.custodianUser?.firstName} ${res.custodianUser?.lastName}` || + (res.custodianTeamMember?.name as string); let subject = `Booking reserved (${res.name}) - shelf.nu`; let text = assetReservedEmailContent({ bookingName: res.name, assetsCount: res.assets.length, - custodian: - `${res.custodianUser?.firstName} ${res.custodianUser?.lastName}` || - (res.custodianTeamMember?.name as string), + custodian: custodian, from: res.from!, to: res.to!, hints, bookingId: res.id, }); + let html = bookingUpdatesTemplateString({ booking: res, heading: `Booking confirmation for ${custodian}`, assetCount: res.assets.length }) if (data.status === BookingStatus.COMPLETE) { subject = `Booking completed (${res.name}) - shelf.nu`; text = completedBookingEmailContent({ bookingName: res.name, assetsCount: res._count.assets, - custodian: - `${res.custodianUser?.firstName} ${res.custodianUser?.lastName}` || - (res.custodianTeamMember?.name as string), + custodian: custodian, from: booking.from as Date, // We can safely cast here as we know the booking is overdue so it myust have a from and to date to: booking.to as Date, bookingId: res.id, hints: hints, }); + html = bookingUpdatesTemplateString({ booking: res, heading: `Your booking has been completed: "${res.name}".`, assetCount: res._count.assets }) } if (data.status === BookingStatus.CANCELLED) { @@ -258,6 +259,7 @@ export const upsertBooking = async ( bookingId: res.id, hints: hints, }); + html = bookingUpdatesTemplateString({ booking: res, heading: `Your booking has been cancelled: "${res.name}".`, assetCount: res._count.assets }) } promises.push( @@ -265,6 +267,7 @@ export const upsertBooking = async ( to: email, subject, text, + html }) ); } else if (data.status === BookingStatus.ONGOING && res.to) { @@ -506,11 +509,13 @@ export const deleteBooking = async ( bookingId: b.id, hints: hints, }); + const html = bookingUpdatesTemplateString({ booking: b, heading: `Your booking has been deleted: "${b.name}".`, assetCount: b._count.assets }) await sendEmail({ to: email, subject, text, + html }); } diff --git a/app/modules/booking/worker.server.ts b/app/modules/booking/worker.server.ts index b8df21381..5c0784d7b 100644 --- a/app/modules/booking/worker.server.ts +++ b/app/modules/booking/worker.server.ts @@ -11,6 +11,8 @@ import { } from "./email-helpers"; import { scheduleNextBookingJob } from "./service.server"; import type { SchedulerData } from "./types"; +import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template"; +import { getTimeRemainingMessage } from "~/utils/date-fns"; /** ===== start: listens and creates chain of jobs for a given booking ===== */ @@ -52,6 +54,12 @@ export const registerBookingWorkers = () => { bookingId: booking.id, hints: data.hints, }), + html: bookingUpdatesTemplateString({ + booking, heading: `Your booking is due for checkout in ${getTimeRemainingMessage( + new Date(booking.from), + new Date() + )}.`, assetCount: booking._count.assets + }) }).catch((err) => { console.error(`failed to send checkoutReminder email`, err); }); From b7a736fa6fc20c067124f86557434eb1c3e80496 Mon Sep 17 00:00:00 2001 From: hunar Date: Tue, 23 Jan 2024 18:15:27 +0530 Subject: [PATCH 02/30] added emails HTML for emails in bookings --- app/emails/styles.ts | 10 ++++++--- app/modules/booking/email-helpers.ts | 9 ++++---- app/modules/booking/service.server.ts | 31 +++++++++++++++++++++------ app/modules/booking/worker.server.ts | 12 ++++++----- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/app/emails/styles.ts b/app/emails/styles.ts index 73a443e44..4a7fe5f4b 100644 --- a/app/emails/styles.ts +++ b/app/emails/styles.ts @@ -16,10 +16,14 @@ export const styles = { borderRadius: "4px", }, h1: { - fontSize: "20px", color: "#101828", fontWeight: "600" + fontSize: "20px", + color: "#101828", + fontWeight: "600", }, h2: { - fontSize: "16px", color: "#101828", fontWeight: "600" + fontSize: "16px", + color: "#101828", + fontWeight: "600", }, - p: { fontSize: "16px", color: "#344054" } + p: { fontSize: "16px", color: "#344054" }, }; diff --git a/app/modules/booking/email-helpers.ts b/app/modules/booking/email-helpers.ts index 93be9ddb9..a71be986d 100644 --- a/app/modules/booking/email-helpers.ts +++ b/app/modules/booking/email-helpers.ts @@ -1,10 +1,10 @@ import type { Booking, TeamMember, User } from "@prisma/client"; +import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template"; import { SERVER_URL } from "~/utils"; import { getDateTimeFormatFromHints } from "~/utils/client-hints"; import { getTimeRemainingMessage } from "~/utils/date-fns"; import { sendEmail } from "~/utils/mail.server"; import type { ClientHint } from "./types"; -import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template"; /** * THis is the base content of the bookings related emails. @@ -178,12 +178,13 @@ export const sendCheckinReminder = async ( bookingId: booking.id, }), html: bookingUpdatesTemplateString({ - booking, heading: `Your booking is due for checkin in ${getTimeRemainingMessage( + booking, + heading: `Your booking is due for checkin in ${getTimeRemainingMessage( new Date(booking.to!), new Date() )} minutes.`, - assetCount - }) + assetCount, + }), }); }; diff --git a/app/modules/booking/service.server.ts b/app/modules/booking/service.server.ts index 27889eb45..8a6557dc4 100644 --- a/app/modules/booking/service.server.ts +++ b/app/modules/booking/service.server.ts @@ -218,7 +218,8 @@ export const upsertBooking = async ( data.status === BookingStatus.COMPLETE || data.status === BookingStatus.CANCELLED ) { - const custodian = `${res.custodianUser?.firstName} ${res.custodianUser?.lastName}` || + const custodian = + `${res.custodianUser?.firstName} ${res.custodianUser?.lastName}` || (res.custodianTeamMember?.name as string); let subject = `Booking reserved (${res.name}) - shelf.nu`; let text = assetReservedEmailContent({ @@ -230,7 +231,11 @@ export const upsertBooking = async ( hints, bookingId: res.id, }); - let html = bookingUpdatesTemplateString({ booking: res, heading: `Booking confirmation for ${custodian}`, assetCount: res.assets.length }) + let html = bookingUpdatesTemplateString({ + booking: res, + heading: `Booking confirmation for ${custodian}`, + assetCount: res.assets.length, + }); if (data.status === BookingStatus.COMPLETE) { subject = `Booking completed (${res.name}) - shelf.nu`; @@ -243,7 +248,11 @@ export const upsertBooking = async ( bookingId: res.id, hints: hints, }); - html = bookingUpdatesTemplateString({ booking: res, heading: `Your booking has been completed: "${res.name}".`, assetCount: res._count.assets }) + html = bookingUpdatesTemplateString({ + booking: res, + heading: `Your booking has been completed: "${res.name}".`, + assetCount: res._count.assets, + }); } if (data.status === BookingStatus.CANCELLED) { @@ -259,7 +268,11 @@ export const upsertBooking = async ( bookingId: res.id, hints: hints, }); - html = bookingUpdatesTemplateString({ booking: res, heading: `Your booking has been cancelled: "${res.name}".`, assetCount: res._count.assets }) + html = bookingUpdatesTemplateString({ + booking: res, + heading: `Your booking has been cancelled: "${res.name}".`, + assetCount: res._count.assets, + }); } promises.push( @@ -267,7 +280,7 @@ export const upsertBooking = async ( to: email, subject, text, - html + html, }) ); } else if (data.status === BookingStatus.ONGOING && res.to) { @@ -509,13 +522,17 @@ export const deleteBooking = async ( bookingId: b.id, hints: hints, }); - const html = bookingUpdatesTemplateString({ booking: b, heading: `Your booking has been deleted: "${b.name}".`, assetCount: b._count.assets }) + const html = bookingUpdatesTemplateString({ + booking: b, + heading: `Your booking has been deleted: "${b.name}".`, + assetCount: b._count.assets, + }); await sendEmail({ to: email, subject, text, - html + html, }); } diff --git a/app/modules/booking/worker.server.ts b/app/modules/booking/worker.server.ts index 5c0784d7b..8dac23f46 100644 --- a/app/modules/booking/worker.server.ts +++ b/app/modules/booking/worker.server.ts @@ -1,6 +1,8 @@ /* eslint-disable no-console */ import { BookingStatus } from "@prisma/client"; import { db } from "~/database"; +import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template"; +import { getTimeRemainingMessage } from "~/utils/date-fns"; import { sendEmail } from "~/utils/mail.server"; import { scheduler } from "~/utils/scheduler.server"; import { schedulerKeys } from "./constants"; @@ -11,8 +13,6 @@ import { } from "./email-helpers"; import { scheduleNextBookingJob } from "./service.server"; import type { SchedulerData } from "./types"; -import { bookingUpdatesTemplateString } from "~/emails/bookings-updates-template"; -import { getTimeRemainingMessage } from "~/utils/date-fns"; /** ===== start: listens and creates chain of jobs for a given booking ===== */ @@ -55,11 +55,13 @@ export const registerBookingWorkers = () => { hints: data.hints, }), html: bookingUpdatesTemplateString({ - booking, heading: `Your booking is due for checkout in ${getTimeRemainingMessage( + booking, + heading: `Your booking is due for checkout in ${getTimeRemainingMessage( new Date(booking.from), new Date() - )}.`, assetCount: booking._count.assets - }) + )}.`, + assetCount: booking._count.assets, + }), }).catch((err) => { console.error(`failed to send checkoutReminder email`, err); }); From 0e30503d2b6ae58943d6a73912d81f9c0afd7d48 Mon Sep 17 00:00:00 2001 From: Madhurjya Kalita <88551650+melsonic@users.noreply.github.com> Date: Tue, 30 Jan 2024 19:27:11 +0000 Subject: [PATCH 03/30] feat: disable ui in workspace switching --- app/atoms/switching-workspace.ts | 3 + .../layout/sidebar/organization-select.tsx | 5 ++ app/root.tsx | 8 +- app/routes/_layout+/_layout.tsx | 85 +++++++++++-------- 4 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 app/atoms/switching-workspace.ts diff --git a/app/atoms/switching-workspace.ts b/app/atoms/switching-workspace.ts new file mode 100644 index 000000000..0fbb133b5 --- /dev/null +++ b/app/atoms/switching-workspace.ts @@ -0,0 +1,3 @@ +import { atom } from "jotai"; + +export const switchingWorkspaceAtom = atom(false); diff --git a/app/components/layout/sidebar/organization-select.tsx b/app/components/layout/sidebar/organization-select.tsx index cf406d20c..7c36c9c41 100644 --- a/app/components/layout/sidebar/organization-select.tsx +++ b/app/components/layout/sidebar/organization-select.tsx @@ -1,4 +1,6 @@ import { useFetcher, useLoaderData } from "@remix-run/react"; +import { useAtom } from "jotai"; +import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; import { Select, SelectContent, @@ -18,12 +20,15 @@ export const OrganizationSelect = () => { useLoaderData(); const fetcher = useFetcher(); + const [workspaceSwitching, setWorkspaceSwitching] = useAtom(switchingWorkspaceAtom); + return ( { const form = e.currentTarget; + setWorkspaceSwitching(true); fetcher.submit(form); }} > diff --git a/app/root.tsx b/app/root.tsx index b5c12c2a2..c0ab48ed9 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,4 +1,4 @@ -import type { PropsWithChildren } from "react"; +import { useEffect, type PropsWithChildren } from "react"; import type { User } from "@prisma/client"; import type { LinksFunction, @@ -17,6 +17,8 @@ import { useLoaderData, } from "@remix-run/react"; +import { useAtom } from "jotai"; +import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; import { ErrorBoundryComponent } from "./components/errors"; import { HomeIcon } from "./components/icons"; @@ -99,6 +101,10 @@ function Document({ children, title }: PropsWithChildren<{ title?: string }>) { export default function App() { const { maintenanceMode } = useLoaderData(); + const [workspaceSwitching, setWorkspaceSwitching] = useAtom(switchingWorkspaceAtom); + useEffect(() => { + setWorkspaceSwitching(false); + }) return ( {maintenanceMode ? : } ); diff --git a/app/routes/_layout+/_layout.tsx b/app/routes/_layout+/_layout.tsx index 04b44b3bf..2ad2a9838 100644 --- a/app/routes/_layout+/_layout.tsx +++ b/app/routes/_layout+/_layout.tsx @@ -1,11 +1,14 @@ import { Roles } from "@prisma/client"; import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/node"; - import { json, redirect } from "@remix-run/node"; import { Outlet } from "@remix-run/react"; +import { useAtom } from "jotai"; +import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; + import { ErrorBoundryComponent } from "~/components/errors"; import Sidebar from "~/components/layout/sidebar/sidebar"; import { useCrisp } from "~/components/marketing/crisp"; +import { Spinner } from "~/components/shared/spinner"; import { Toaster } from "~/components/shared/toast"; import { db } from "~/database"; import { commitAuthSession, requireAuthSession } from "~/modules/auth"; @@ -33,33 +36,33 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { // @TODO - we need to look into doing a select as we dont want to expose all data always const user = authSession ? await db.user.findUnique({ - where: { email: authSession.email.toLowerCase() }, - include: { - roles: true, - organizations: { - select: { - id: true, - name: true, - type: true, - imageId: true, - }, + where: { email: authSession.email.toLowerCase() }, + include: { + roles: true, + organizations: { + select: { + id: true, + name: true, + type: true, + imageId: true, }, - userOrganizations: { - where: { - userId: authSession.userId, - }, - select: { - organization: true, - roles: true, - }, + }, + userOrganizations: { + where: { + userId: authSession.userId, }, - tier: { - select: { - tierLimit: true, - }, + select: { + organization: true, + roles: true, + }, + }, + tier: { + select: { + tierLimit: true, }, }, - }) + }, + }) : undefined; let subscription = null; if (user?.customerId && stripe) { @@ -83,6 +86,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const { organizationId, organizations, currentOrganization } = await requireOrganisationId(authSession, request); + // setWorkspaceSwitching(); + return json( { user, @@ -113,20 +118,28 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { export default function App() { useCrisp(); + const [workspaceSwitching, setWorkspaceSwitching] = useAtom(switchingWorkspaceAtom); - return ( -
-
- -
-
- + return (<> + { + workspaceSwitching ? +
+ +
+ : +
+
+ +
+
+ +
+ +
- -
-
-
- ); +
+ } + ); } export const ErrorBoundary = () => ( From c78b1fd4dc30b28575be452e53cda998c2b4543c Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 11:44:17 +0200 Subject: [PATCH 04/30] fixing issue with broken string --- app/modules/booking/email-helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/booking/email-helpers.ts b/app/modules/booking/email-helpers.ts index 7485e44f7..b3594a680 100644 --- a/app/modules/booking/email-helpers.ts +++ b/app/modules/booking/email-helpers.ts @@ -151,7 +151,7 @@ export const checkinReminderEmailContent = ({ emailContent: `Your booking is due for checkin in ${getTimeRemainingMessage( new Date(to), new Date() - )} minutes.`, + )}.`, }); export const sendCheckinReminder = async ( From dde84c2b8ae14b07173b493f7bcfbb452d96f6a7 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 12:40:07 +0200 Subject: [PATCH 05/30] increasing limit of connections --- fly.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fly.toml b/fly.toml index c38dc2f31..302cb5abd 100644 --- a/fly.toml +++ b/fly.toml @@ -21,8 +21,8 @@ processes = [] script_checks = [] [services.concurrency] - hard_limit = 25 - soft_limit = 20 + hard_limit = 125 + soft_limit = 100 type = "connections" [[services.ports]] From d0efe9f511a84efdaa35504f0cc20ad453832b09 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 15:00:57 +0200 Subject: [PATCH 06/30] integrating sentry --- .env.example | 5 +- .gitignore | 3 + app/entry.client.tsx | 26 ++- app/entry.server.tsx | 24 ++- app/root.tsx | 5 +- app/utils/env.ts | 4 + package-lock.json | 410 +++++++++++++++++++++++++++++++++++++++++-- package.json | 5 +- 8 files changed, 462 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 1c0237a19..03cb2787d 100644 --- a/.env.example +++ b/.env.example @@ -32,4 +32,7 @@ MAPTILER_TOKEN="maptiler-token" MICROSOFT_CLARITY_ID="microsoft-clarity-id" INVITE_TOKEN_SECRET="secret-test-invite" -GEOCODE_API_KEY="geocode-api-key" \ No newline at end of file +GEOCODE_API_KEY="geocode-api-key" + +# Used for Sentry error logging +SENTRY_DSN="sentry-dsn" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 24b41c89f..334c3510f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ desktop-app/portable .npmrc .vscode + +# Sentry Config File +.sentryclirc diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 8518bd495..adc1ad04d 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -1,8 +1,30 @@ -import React from "react"; +import React, { useEffect } from "react"; -import { RemixBrowser } from "@remix-run/react"; +import { RemixBrowser, useLocation, useMatches } from "@remix-run/react"; +import * as Sentry from "@sentry/remix"; import { Provider as JotaiProvider } from "jotai"; import { hydrateRoot } from "react-dom/client"; +import { SENTRY_DSN } from "./utils"; + +if (SENTRY_DSN) { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1, + + integrations: [ + new Sentry.BrowserTracing({ + routingInstrumentation: Sentry.remixRouterInstrumentation( + useEffect, + useLocation, + useMatches + ), + }), + new Sentry.Replay(), + ], + }); +} function hydrate() { React.startTransition(() => { diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 3c07d0d8d..fbd55f699 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -1,13 +1,35 @@ import { PassThrough } from "stream"; import { createReadableStreamFromReadable } from "@remix-run/node"; -import type { EntryContext } from "@remix-run/node"; +import type { + ActionFunctionArgs, + EntryContext, + LoaderFunctionArgs, +} from "@remix-run/node"; import { RemixServer } from "@remix-run/react"; +import * as Sentry from "@sentry/remix"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; import { registerBookingWorkers } from "./modules/booking"; +import { SENTRY_DSN } from "./utils"; import * as schedulerService from "./utils/scheduler.server"; +export function handleError( + error: unknown, + { request }: LoaderFunctionArgs | ActionFunctionArgs +) { + if (Sentry) { + Sentry.captureRemixServerException(error, "remix.server", request); + } +} + +if (SENTRY_DSN) { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1, + }); +} + const ABORT_DELAY = 5000; export default async function handleRequest( diff --git a/app/root.tsx b/app/root.tsx index c0ab48ed9..27115a4dc 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -16,6 +16,7 @@ import { ScrollRestoration, useLoaderData, } from "@remix-run/react"; +import { withSentry } from "@sentry/remix"; import { useAtom } from "jotai"; import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; @@ -99,7 +100,7 @@ function Document({ children, title }: PropsWithChildren<{ title?: string }>) { ); } -export default function App() { +function App() { const { maintenanceMode } = useLoaderData(); const [workspaceSwitching, setWorkspaceSwitching] = useAtom(switchingWorkspaceAtom); useEffect(() => { @@ -110,4 +111,6 @@ export default function App() { ); } +export default withSentry(App); + export const ErrorBoundary = () => ; diff --git a/app/utils/env.ts b/app/utils/env.ts index e26969830..691f3badf 100644 --- a/app/utils/env.ts +++ b/app/utils/env.ts @@ -39,7 +39,9 @@ declare global { SMTP_USER: string; MAINTENANCE_MODE: string; DATABASE_URL: string; + DIRECT_URL: string; GEOCODE_API_KEY: string; + SENTRY_DSN: string; } } } @@ -82,6 +84,8 @@ export const SMTP_PWD = getEnv("SMTP_PWD"); export const SMTP_HOST = getEnv("SMTP_HOST"); export const SMTP_USER = getEnv("SMTP_USER"); export const DATABASE_URL = getEnv("DATABASE_URL"); +export const DIRECT_URL = getEnv("DIRECT_URL"); +export const SENTRY_DSN = getEnv("SENTRY_DSN") || false; /** * Shared envs diff --git a/package-lock.json b/package-lock.json index 240d0b52a..d6f374973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@remix-run/node": "2.1.0", "@remix-run/react": "2.1.0", "@remix-run/serve": "2.1.0", + "@sentry/remix": "^7.98.0", "@stripe/react-stripe-js": "^2.1.1", "@stripe/stripe-js": "^1.54.2", "@supabase/supabase-js": "^2.26.0", @@ -4440,6 +4441,388 @@ "url": "https://ko-fi.com/killymxi" } }, + "node_modules/@sentry-internal/feedback": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.98.0.tgz", + "integrity": "sha512-t/mATvwkLcQLKRlx8SO5vlUjaadF6sT3lfR0PdWYyBy8qglbMTHDW4KP6JKh1gdzTVQGnwMByy+/4h9gy4AVzw==", + "dependencies": { + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.98.0.tgz", + "integrity": "sha512-vAR6KIycyazaY9HwxG5UONrPTe8jeKtZr6k04svPC8OvcoI0xF+l1jMEYcarffuzKpZlPfssYb5ChHtKuXCB+Q==", + "dependencies": { + "@sentry/core": "7.98.0", + "@sentry/replay": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.98.0.tgz", + "integrity": "sha512-FnhD2uMLIAJvv4XsYPv3qsTTtxrImyLxiZacudJyaWFhxoeVQ8bKKbWJ/Ar68FAwqTtjXMeY5evnEBbRMcQlaA==", + "dependencies": { + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.98.0.tgz", + "integrity": "sha512-/MzTS31N2iM6Qwyh4PSpHihgmkVD5xdfE5qi1mTlwQZz5Yz8t7MdMriX8bEDPlLB8sNxl7+D6/+KUJO8akX0nQ==", + "dependencies": { + "@sentry-internal/feedback": "7.98.0", + "@sentry-internal/replay-canvas": "7.98.0", + "@sentry-internal/tracing": "7.98.0", + "@sentry/core": "7.98.0", + "@sentry/replay": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/cli": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.26.0.tgz", + "integrity": "sha512-WRrY9nkjLLUvyo+l8KE0x0Q+0NtCd2U8HYJzh3kyJHyyfKWiSH7ZhExcsb2MoSIjlzbKjjrIJzxhklZABkidDw==", + "hasInstallScript": true, + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@sentry/cli-darwin": "2.26.0", + "@sentry/cli-linux-arm": "2.26.0", + "@sentry/cli-linux-arm64": "2.26.0", + "@sentry/cli-linux-i686": "2.26.0", + "@sentry/cli-linux-x64": "2.26.0", + "@sentry/cli-win32-i686": "2.26.0", + "@sentry/cli-win32-x64": "2.26.0" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.26.0.tgz", + "integrity": "sha512-SJ4ts9VELoLdOx1g034Tv2nGqhjutBYNAI3WMsjBaQG3tqNPJkQJKGrOqfpL6kTdO2tqQIAYeVw60yqWuHU3FA==", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.26.0.tgz", + "integrity": "sha512-qNqKLf3eGowhm+4gg47jGLfova5SLgC0wvWX181U+w94oVGp4onuSjbqpy7wbSA9nsfTXllMhEFI5jA4CMmZVw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.26.0.tgz", + "integrity": "sha512-tAsK5pWrLyU+zqoW0uwylfLB7udOV8FtU8xqcfMsYGxt44zviiuxzKeDnaUdHsZcvk03aTAyf1Dxqn0u32A0MA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.26.0.tgz", + "integrity": "sha512-+dSFR9rK6o6F0gBxoU0mrHw18qVgF1t27Y0jvdItMtDuCuduBuXTffmsbBwbPFWBgWuLPG+ojB1LuoBt5qVMng==", + "cpu": [ + "x86", + "ia32" + ], + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.26.0.tgz", + "integrity": "sha512-oY86ECWVQuk434K+enUVZnn28T8qxjJTpxN079xvz7SIWOxQ609tMva91Ywo0gExcu07AZ0pg71XFsEQ9WhZgA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux", + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.26.0.tgz", + "integrity": "sha512-vLju9NFl4venKEVpuFJpxaCwa2NdG6C9mhYNqxRvZAPrXWMdMd697qBDOMepAPT7CI8EWiyXUwMli0WjGXGMeQ==", + "cpu": [ + "x86", + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.26.0.tgz", + "integrity": "sha512-r3ZaxdHGC6OyJhOxO5ADzAitpGcgT/PkqQzOzKXBOebHj5jzwY27JWjdNhpT6sJZDII13HxqwISRedVWftZgRw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sentry/core": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.98.0.tgz", + "integrity": "sha512-baRUcpCNGyk7cApQHMfqEZJkXdvAKK+z/dVWiMqWc5T5uhzMnPE8/gjP1JZsMtJSQ8g5nHimBdI5TwOyZtxPaA==", + "dependencies": { + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.98.0.tgz", + "integrity": "sha512-9cHW217DnU9wC4iR+QxmY3q59N1Touh23hPMDtpMRmbRHSgrmLMoHTVPhK9zHsXRs0mUeidmMqY1ubAWauQByw==", + "dependencies": { + "@sentry-internal/tracing": "7.98.0", + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/react": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.98.0.tgz", + "integrity": "sha512-rTvsAaGPuOGm2FvJAD8aB7iE+rUIrwYWKT4gANvg8zxRzPCK7ukKkpmL3SeJi7bvLNHYLATl1hUVDgm8VpHDng==", + "dependencies": { + "@sentry/browser": "7.98.0", + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0", + "hoist-non-react-statics": "^3.3.2" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "15.x || 16.x || 17.x || 18.x" + } + }, + "node_modules/@sentry/remix": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/remix/-/remix-7.98.0.tgz", + "integrity": "sha512-V4PytD5KEQMQ4fKX6LozMv39Q9MgS3yVoJQlHpP6n8/2rJh8Wb89rZHWXXfe0qzu5BkWrkj8mNeMyxtpxD7SSQ==", + "dependencies": { + "@sentry/cli": "^2.23.0", + "@sentry/core": "7.98.0", + "@sentry/node": "7.98.0", + "@sentry/react": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0", + "glob": "^10.3.4", + "yargs": "^17.6.0" + }, + "bin": { + "sentry-upload-sourcemaps": "scripts/sentry-upload-sourcemaps.js" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@remix-run/node": "1.x || 2.x", + "@remix-run/react": "1.x || 2.x", + "react": "16.x || 17.x || 18.x" + } + }, + "node_modules/@sentry/remix/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/remix/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/@sentry/remix/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/remix/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@sentry/remix/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/remix/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/replay": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.98.0.tgz", + "integrity": "sha512-CQabv/3KnpMkpc2TzIquPu5krpjeMRAaDIO0OpTj5SQeH2RqSq3fVWNZkHa8tLsADxcfLFINxqOg2jd1NxvwxA==", + "dependencies": { + "@sentry-internal/tracing": "7.98.0", + "@sentry/core": "7.98.0", + "@sentry/types": "7.98.0", + "@sentry/utils": "7.98.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/types": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.98.0.tgz", + "integrity": "sha512-pc034ziM0VTETue4bfBcBqTWGy4w0okidtoZJjGVrYAfE95ObZnUGVj/XYIQ3FeCYWIa7NFN2MvdsCS0buwivQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.98.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.98.0.tgz", + "integrity": "sha512-0/LY+kpHxItVRY0xPDXPXVsKRb95cXsGSQf8sVMtfSjz++0bLL1U4k7PFz1c5s2/Vk0B8hS6duRrgMv6dMIZDw==", + "dependencies": { + "@sentry/types": "7.98.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -5779,7 +6162,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -7508,7 +7890,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -8015,7 +8396,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -8025,7 +8405,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -8299,7 +8678,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -9918,7 +10296,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -10438,6 +10815,19 @@ "integrity": "sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==", "dev": true }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hosted-git-info": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", @@ -10550,7 +10940,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -13942,7 +14331,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -16059,7 +16447,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -16146,8 +16533,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/ps-tree": { "version": "1.2.0", @@ -17081,7 +17467,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -20307,7 +20692,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } diff --git a/package.json b/package.json index d7b221542..8c628c431 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "sideEffects": false, "scripts": { - "build": "remix build", + "build": "remix build --sourcemap && sentry-upload-sourcemaps --org nikolay-bonev --project shelf-webapp", "db:diff": "prisma migrate diff --from-url=${DATABASE_URL} --to-migrations=./app/database/migrations --shadow-database-url=${SHADOW_DATABASE_URL} --script > rollback.sql", "db:prepare-migration": "prisma generate && prisma migrate dev --create-only --skip-seed", "db:generate-type": "prisma generate", @@ -54,6 +54,7 @@ "@remix-run/node": "2.1.0", "@remix-run/react": "2.1.0", "@remix-run/serve": "2.1.0", + "@sentry/remix": "^7.98.0", "@stripe/react-stripe-js": "^2.1.1", "@stripe/stripe-js": "^1.54.2", "@supabase/supabase-js": "^2.26.0", @@ -145,4 +146,4 @@ "schema": "app/database/schema.prisma", "seed": "tsx app/database/seed.server.ts" } -} +} \ No newline at end of file From 0c1e910edc550b4b53765705fcaaad60b4c4fef7 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 15:01:25 +0200 Subject: [PATCH 07/30] mend --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c628c431..8b36d3006 100644 --- a/package.json +++ b/package.json @@ -146,4 +146,4 @@ "schema": "app/database/schema.prisma", "seed": "tsx app/database/seed.server.ts" } -} \ No newline at end of file +} From 0e491cd5ec9c6ed7dacaedcd1a10de7f5cf562b6 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 15:12:58 +0200 Subject: [PATCH 08/30] adjusting SENTRY_DSN, making it not required --- app/utils/env.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/utils/env.ts b/app/utils/env.ts index 691f3badf..b50647834 100644 --- a/app/utils/env.ts +++ b/app/utils/env.ts @@ -11,7 +11,6 @@ declare global { MICROSOFT_CLARITY_ID: string; CRISP_WEBSITE_ID: string; ENABLE_PREMIUM_FEATURES: string; - FORMBRICKS_ENV_ID: string; MAINTENANCE_MODE: string; }; } @@ -33,7 +32,6 @@ declare global { STRIPE_WEBHOOK_ENDPOINT_SECRET: string; ENABLE_PREMIUM_FEATURES: string; INVITE_TOKEN_SECRET: string; - FORMBRICKS_ENV_ID: string; SMTP_PWD: string; SMTP_HOST: string; SMTP_USER: string; @@ -85,7 +83,9 @@ export const SMTP_HOST = getEnv("SMTP_HOST"); export const SMTP_USER = getEnv("SMTP_USER"); export const DATABASE_URL = getEnv("DATABASE_URL"); export const DIRECT_URL = getEnv("DIRECT_URL"); -export const SENTRY_DSN = getEnv("SENTRY_DSN") || false; +export const SENTRY_DSN = getEnv("SENTRY_DSN", { + isRequired: false, +}); /** * Shared envs @@ -140,7 +140,6 @@ export function getBrowserEnv() { CRISP_WEBSITE_ID, MICROSOFT_CLARITY_ID, ENABLE_PREMIUM_FEATURES, - FORMBRICKS_ENV_ID, MAINTENANCE_MODE, }; } From 04d75f9f039a69a29855c8a41746e99134235126 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 15:26:48 +0200 Subject: [PATCH 09/30] remove sentry client side logging --- .env.example | 4 +++- app/entry.client.tsx | 26 ++------------------------ package.json | 2 +- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/.env.example b/.env.example index 03cb2787d..d69b7c0cb 100644 --- a/.env.example +++ b/.env.example @@ -35,4 +35,6 @@ INVITE_TOKEN_SECRET="secret-test-invite" GEOCODE_API_KEY="geocode-api-key" # Used for Sentry error logging -SENTRY_DSN="sentry-dsn" \ No newline at end of file +SENTRY_DSN="sentry-dsn" +SENTRY_ORG="sentry-org" +SENTRY_PROJECT="sentry-project" \ No newline at end of file diff --git a/app/entry.client.tsx b/app/entry.client.tsx index adc1ad04d..8518bd495 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -1,30 +1,8 @@ -import React, { useEffect } from "react"; +import React from "react"; -import { RemixBrowser, useLocation, useMatches } from "@remix-run/react"; -import * as Sentry from "@sentry/remix"; +import { RemixBrowser } from "@remix-run/react"; import { Provider as JotaiProvider } from "jotai"; import { hydrateRoot } from "react-dom/client"; -import { SENTRY_DSN } from "./utils"; - -if (SENTRY_DSN) { - Sentry.init({ - dsn: SENTRY_DSN, - tracesSampleRate: 1, - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1, - - integrations: [ - new Sentry.BrowserTracing({ - routingInstrumentation: Sentry.remixRouterInstrumentation( - useEffect, - useLocation, - useMatches - ), - }), - new Sentry.Replay(), - ], - }); -} function hydrate() { React.startTransition(() => { diff --git a/package.json b/package.json index 8b36d3006..ad4fc3de1 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "sideEffects": false, "scripts": { - "build": "remix build --sourcemap && sentry-upload-sourcemaps --org nikolay-bonev --project shelf-webapp", + "build": "remix build --sourcemap && sentry-upload-sourcemaps --org ${SENTRY_ORG} --project ${SENTRY_APP}", "db:diff": "prisma migrate diff --from-url=${DATABASE_URL} --to-migrations=./app/database/migrations --shadow-database-url=${SHADOW_DATABASE_URL} --script > rollback.sql", "db:prepare-migration": "prisma generate && prisma migrate dev --create-only --skip-seed", "db:generate-type": "prisma generate", From 295e2e4f2de835abe78e598df2753fce6e07c15d Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 15:50:29 +0200 Subject: [PATCH 10/30] Fixed wrong var name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad4fc3de1..47b939332 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "sideEffects": false, "scripts": { - "build": "remix build --sourcemap && sentry-upload-sourcemaps --org ${SENTRY_ORG} --project ${SENTRY_APP}", + "build": "remix build --sourcemap && sentry-upload-sourcemaps --org ${SENTRY_ORG} --project ${SENTRY_PROJECT}", "db:diff": "prisma migrate diff --from-url=${DATABASE_URL} --to-migrations=./app/database/migrations --shadow-database-url=${SHADOW_DATABASE_URL} --script > rollback.sql", "db:prepare-migration": "prisma generate && prisma migrate dev --create-only --skip-seed", "db:generate-type": "prisma generate", From 1a7f349d88d1c67b857748d251b3b2110624dda0 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 17:03:38 +0200 Subject: [PATCH 11/30] use different build function depending on weather sentry is enabled --- .github/workflows/deploy.yml | 5 +++++ Dockerfile | 18 +++++++++++++++++- package.json | 3 ++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d7656a3f6..08581b4dd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,6 +21,9 @@ jobs: build: name: 🐳 Build + env: + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} # only build/deploy main branch on pushes if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'}} runs-on: ubuntu-latest @@ -65,6 +68,8 @@ jobs: tags: registry.fly.io/${{ steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ github.sha }} build-args: | COMMIT_SHA=${{ github.sha }} + SENTRY_ORG=${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT=${{ secrets.SENTRY_PROJECT }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new diff --git a/Dockerfile b/Dockerfile index 83540055b..bb7551f73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,7 +35,23 @@ ADD /app/database/schema.prisma . RUN npx prisma generate ADD . . -RUN npm run build + +# Declare the arguments +ARG SENTRY_ORG +ARG SENTRY_PROJECT + +# Assign the arguments to environment variables +ENV SENTRY_ORG=$SENTRY_ORG +ENV SENTRY_PROJECT=$SENTRY_PROJECT + +# Use shell logic to determine which build command to run +RUN if [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_PROJECT" ]; then \ + npm run build-with-sentry; \ + else \ + npm run build; \ + fi + + # Finally, build the production image with minimal footprint FROM base diff --git a/package.json b/package.json index 47b939332..62c46abdb 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,8 @@ "private": true, "sideEffects": false, "scripts": { - "build": "remix build --sourcemap && sentry-upload-sourcemaps --org ${SENTRY_ORG} --project ${SENTRY_PROJECT}", + "build": "remix build", + "build-with-sentry": "remix build --sourcemap && sentry-upload-sourcemaps --org ${SENTRY_ORG} --project ${SENTRY_PROJECT}", "db:diff": "prisma migrate diff --from-url=${DATABASE_URL} --to-migrations=./app/database/migrations --shadow-database-url=${SHADOW_DATABASE_URL} --script > rollback.sql", "db:prepare-migration": "prisma generate && prisma migrate dev --create-only --skip-seed", "db:generate-type": "prisma generate", From 51268a3d96b9f0677d6c68ccccdade0332aa6fe1 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 17:14:40 +0200 Subject: [PATCH 12/30] mend --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bb7551f73..f27ae220a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM node:18-bullseye-slim as base ENV NODE_ENV production # Install openssl for Prisma -RUN apt-get update && apt-get install -y openssl +RUN apt-get update && apt-get install -y openssl && apt-get install ca-certificates # Install all node_modules, including dev dependencies FROM base as deps From f25043136a3b466bb682ede98a847ea702124c30 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 17:30:51 +0200 Subject: [PATCH 13/30] replacing custom integration with sentry gh action --- .github/workflows/deploy.yml | 15 ++++++++++----- Dockerfile | 20 ++------------------ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 08581b4dd..5c4ada655 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,9 +21,6 @@ jobs: build: name: 🐳 Build - env: - SENTRY_ORG: ${{ secrets.SENTRY_ORG }} - SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} # only build/deploy main branch on pushes if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'}} runs-on: ubuntu-latest @@ -68,11 +65,19 @@ jobs: tags: registry.fly.io/${{ steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ github.sha }} build-args: | COMMIT_SHA=${{ github.sha }} - SENTRY_ORG=${{ secrets.SENTRY_ORG }} - SENTRY_PROJECT=${{ secrets.SENTRY_PROJECT }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new + - name: Create Sentry release + uses: getsentry/action-release@v1 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + with: + environment: production + sourcemaps: "./build" + # This ugly bit is necessary if you don't want your cache to grow forever # till it hits GitHub's limit of 5GB. # Temp fix diff --git a/Dockerfile b/Dockerfile index f27ae220a..83540055b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM node:18-bullseye-slim as base ENV NODE_ENV production # Install openssl for Prisma -RUN apt-get update && apt-get install -y openssl && apt-get install ca-certificates +RUN apt-get update && apt-get install -y openssl # Install all node_modules, including dev dependencies FROM base as deps @@ -35,23 +35,7 @@ ADD /app/database/schema.prisma . RUN npx prisma generate ADD . . - -# Declare the arguments -ARG SENTRY_ORG -ARG SENTRY_PROJECT - -# Assign the arguments to environment variables -ENV SENTRY_ORG=$SENTRY_ORG -ENV SENTRY_PROJECT=$SENTRY_PROJECT - -# Use shell logic to determine which build command to run -RUN if [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_PROJECT" ]; then \ - npm run build-with-sentry; \ - else \ - npm run build; \ - fi - - +RUN npm run build # Finally, build the production image with minimal footprint FROM base From ae676fffb4633af96a8ec51626ae5fba530d11f1 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 17:43:26 +0200 Subject: [PATCH 14/30] commenting out sourcemap part of deploy action to test if sentry even works --- .github/workflows/deploy.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5c4ada655..c9647a73d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -68,15 +68,15 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new - - name: Create Sentry release - uses: getsentry/action-release@v1 - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_ORG: ${{ secrets.SENTRY_ORG }} - SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - with: - environment: production - sourcemaps: "./build" + # - name: Create Sentry release + # uses: getsentry/action-release@v1 + # env: + # SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + # SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + # SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + # with: + # environment: production + # sourcemaps: "./build" # This ugly bit is necessary if you don't want your cache to grow forever # till it hits GitHub's limit of 5GB. From 844f866f4a4f3fff85f4c6c8a9a1cd9587c46756 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 17:53:39 +0200 Subject: [PATCH 15/30] mend --- app/utils/env.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/utils/env.ts b/app/utils/env.ts index b50647834..1e6a2e911 100644 --- a/app/utils/env.ts +++ b/app/utils/env.ts @@ -82,7 +82,9 @@ export const SMTP_PWD = getEnv("SMTP_PWD"); export const SMTP_HOST = getEnv("SMTP_HOST"); export const SMTP_USER = getEnv("SMTP_USER"); export const DATABASE_URL = getEnv("DATABASE_URL"); -export const DIRECT_URL = getEnv("DIRECT_URL"); +export const DIRECT_URL = getEnv("DIRECT_URL", { + isRequired: false, +}); export const SENTRY_DSN = getEnv("SENTRY_DSN", { isRequired: false, }); From 5d34f79b9ab88a01f192fa3747c14bdf1e7863aa Mon Sep 17 00:00:00 2001 From: Donkoko Date: Mon, 29 Jan 2024 18:24:42 +0200 Subject: [PATCH 16/30] replacing database_url with direct_url for pg-boss --- app/utils/scheduler.server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/utils/scheduler.server.ts b/app/utils/scheduler.server.ts index 3a4166314..f5538ae24 100644 --- a/app/utils/scheduler.server.ts +++ b/app/utils/scheduler.server.ts @@ -1,5 +1,5 @@ import PgBoss from "pg-boss"; -import { DATABASE_URL, NODE_ENV } from "../utils/env"; +import { DIRECT_URL, NODE_ENV } from "../utils/env"; let scheduler!: PgBoss; @@ -11,10 +11,10 @@ declare global { export const init = async () => { if (!scheduler) { if (NODE_ENV === "production") { - scheduler = new PgBoss(DATABASE_URL); + scheduler = new PgBoss(DIRECT_URL); } else { if (!global.scheduler) { - global.scheduler = new PgBoss(DATABASE_URL); + global.scheduler = new PgBoss(DIRECT_URL); } scheduler = global.scheduler; } From 484b2c4894a4e0e41b0d5b90273cd098117145be Mon Sep 17 00:00:00 2001 From: Donkoko Date: Tue, 30 Jan 2024 17:49:06 +0200 Subject: [PATCH 17/30] run sentry only on production --- app/entry.server.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/entry.server.tsx b/app/entry.server.tsx index fbd55f699..14ebe3f60 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -11,9 +11,16 @@ import * as Sentry from "@sentry/remix"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; import { registerBookingWorkers } from "./modules/booking"; -import { SENTRY_DSN } from "./utils"; +import { NODE_ENV, SENTRY_DSN } from "./utils"; import * as schedulerService from "./utils/scheduler.server"; +if (SENTRY_DSN && NODE_ENV === "production") { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1, + }); +} + export function handleError( error: unknown, { request }: LoaderFunctionArgs | ActionFunctionArgs @@ -23,13 +30,6 @@ export function handleError( } } -if (SENTRY_DSN) { - Sentry.init({ - dsn: SENTRY_DSN, - tracesSampleRate: 1, - }); -} - const ABORT_DELAY = 5000; export default async function handleRequest( From e8c977b8d07c70d24bd79f605b08abf8c5debc83 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Tue, 30 Jan 2024 18:24:23 +0200 Subject: [PATCH 18/30] initializing sentry with initial data for user --- app/entry.server.tsx | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 14ebe3f60..8efec98ab 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -10,17 +10,11 @@ import { RemixServer } from "@remix-run/react"; import * as Sentry from "@sentry/remix"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; +import { getAuthSession } from "./modules/auth"; import { registerBookingWorkers } from "./modules/booking"; import { NODE_ENV, SENTRY_DSN } from "./utils"; import * as schedulerService from "./utils/scheduler.server"; -if (SENTRY_DSN && NODE_ENV === "production") { - Sentry.init({ - dsn: SENTRY_DSN, - tracesSampleRate: 1, - }); -} - export function handleError( error: unknown, { request }: LoaderFunctionArgs | ActionFunctionArgs @@ -47,6 +41,22 @@ export default async function handleRequest( registerBookingWorkers(); // === end: register scheduler and workers === + // === Init Sentry + const authSession = await getAuthSession(request); + if (SENTRY_DSN && NODE_ENV === "production") { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1, + environment: NODE_ENV, + initialScope: { + user: { + id: authSession ? authSession.userId : "unknown", + }, + }, + }); + } + // === END Init Sentry + return new Promise(async (res, reject) => { let didError = false; From 40e0bb466a5f2ce8e782a567f7d328f54895465d Mon Sep 17 00:00:00 2001 From: Donkoko Date: Tue, 30 Jan 2024 18:42:49 +0200 Subject: [PATCH 19/30] small adjustment --- app/entry.server.tsx | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 8efec98ab..fb85df045 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -10,11 +10,18 @@ import { RemixServer } from "@remix-run/react"; import * as Sentry from "@sentry/remix"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; -import { getAuthSession } from "./modules/auth"; import { registerBookingWorkers } from "./modules/booking"; import { NODE_ENV, SENTRY_DSN } from "./utils"; import * as schedulerService from "./utils/scheduler.server"; +if (SENTRY_DSN && NODE_ENV === "production") { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1, + environment: NODE_ENV, + }); +} + export function handleError( error: unknown, { request }: LoaderFunctionArgs | ActionFunctionArgs @@ -41,21 +48,21 @@ export default async function handleRequest( registerBookingWorkers(); // === end: register scheduler and workers === - // === Init Sentry - const authSession = await getAuthSession(request); - if (SENTRY_DSN && NODE_ENV === "production") { - Sentry.init({ - dsn: SENTRY_DSN, - tracesSampleRate: 1, - environment: NODE_ENV, - initialScope: { - user: { - id: authSession ? authSession.userId : "unknown", - }, - }, - }); - } - // === END Init Sentry + // // === Init Sentry + // const authSession = await getAuthSession(request); + // if (SENTRY_DSN && NODE_ENV === "production") { + // Sentry.init({ + // dsn: SENTRY_DSN, + // tracesSampleRate: 1, + // environment: NODE_ENV, + // initialScope: { + // user: { + // id: authSession ? authSession.userId : "unknown", + // }, + // }, + // }); + // } + // // === END Init Sentry return new Promise(async (res, reject) => { let didError = false; From c07fa9e3c611a8578deb01c80c82c112348f7415 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Tue, 30 Jan 2024 18:51:10 +0200 Subject: [PATCH 20/30] revert sentry changes --- app/entry.server.tsx | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/app/entry.server.tsx b/app/entry.server.tsx index fb85df045..33e6b5dc4 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -11,14 +11,13 @@ import * as Sentry from "@sentry/remix"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; import { registerBookingWorkers } from "./modules/booking"; -import { NODE_ENV, SENTRY_DSN } from "./utils"; +import { SENTRY_DSN } from "./utils"; import * as schedulerService from "./utils/scheduler.server"; -if (SENTRY_DSN && NODE_ENV === "production") { +if (SENTRY_DSN) { Sentry.init({ dsn: SENTRY_DSN, tracesSampleRate: 1, - environment: NODE_ENV, }); } @@ -48,22 +47,6 @@ export default async function handleRequest( registerBookingWorkers(); // === end: register scheduler and workers === - // // === Init Sentry - // const authSession = await getAuthSession(request); - // if (SENTRY_DSN && NODE_ENV === "production") { - // Sentry.init({ - // dsn: SENTRY_DSN, - // tracesSampleRate: 1, - // environment: NODE_ENV, - // initialScope: { - // user: { - // id: authSession ? authSession.userId : "unknown", - // }, - // }, - // }); - // } - // // === END Init Sentry - return new Promise(async (res, reject) => { let didError = false; From 219e384e4777cc25bb35c8fdb400dbc4d3f67db5 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Wed, 31 Jan 2024 10:56:12 +0200 Subject: [PATCH 21/30] fixing how state is being handled --- .../layout/sidebar/organization-select.tsx | 13 ++- app/components/layout/sidebar/sidebar.tsx | 7 +- app/root.tsx | 9 +- app/routes/_layout+/_layout.tsx | 85 ++++++++++--------- 4 files changed, 60 insertions(+), 54 deletions(-) diff --git a/app/components/layout/sidebar/organization-select.tsx b/app/components/layout/sidebar/organization-select.tsx index 7c36c9c41..aaf1a895e 100644 --- a/app/components/layout/sidebar/organization-select.tsx +++ b/app/components/layout/sidebar/organization-select.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { useFetcher, useLoaderData } from "@remix-run/react"; import { useAtom } from "jotai"; import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; @@ -13,14 +14,21 @@ import { Button } from "~/components/shared"; import { Image } from "~/components/shared/image"; import ProfilePicture from "~/components/user/profile-picture"; import type { loader } from "~/routes/_layout+/_layout"; -import { tw } from "~/utils"; +import { isFormProcessing, tw } from "~/utils"; export const OrganizationSelect = () => { const { organizations, currentOrganizationId } = useLoaderData(); const fetcher = useFetcher(); - const [workspaceSwitching, setWorkspaceSwitching] = useAtom(switchingWorkspaceAtom); + /** We track if the fetcher is submitting */ + const isProcessing = isFormProcessing(fetcher.state); + const [_workspaceSwitching, setWorkspaceSwitching] = useAtom( + switchingWorkspaceAtom + ); + useEffect(() => { + setWorkspaceSwitching(isProcessing); + }, [isProcessing, setWorkspaceSwitching]); return ( { method="POST" onChange={(e) => { const form = e.currentTarget; - setWorkspaceSwitching(true); fetcher.submit(form); }} > diff --git a/app/components/layout/sidebar/sidebar.tsx b/app/components/layout/sidebar/sidebar.tsx index 4695dafac..c59fbee25 100644 --- a/app/components/layout/sidebar/sidebar.tsx +++ b/app/components/layout/sidebar/sidebar.tsx @@ -18,7 +18,11 @@ import MenuItems from "./menu-items"; import { OrganizationSelect } from "./organization-select"; import Overlay from "./overlay"; -export default function Sidebar() { +export default function Sidebar({ + disabled, +}: { + disabled?: boolean; // used to disable the sidebar when the user is switching workspaces +}) { const { user, minimizedSidebar, currentOrganizationId } = useLoaderData(); const [isMobileNavOpen, toggleMobileNav] = useAtom(toggleMobileNavAtom); @@ -32,7 +36,6 @@ export default function Sidebar() { const handleScannerClose = () => { stopMediaStream(); setShowScanner(false); - // window.location.reload(); }; /** We use optimistic UI for folding of the sidebar diff --git a/app/root.tsx b/app/root.tsx index 27115a4dc..1a56a1da9 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,4 +1,4 @@ -import { useEffect, type PropsWithChildren } from "react"; +import { type PropsWithChildren } from "react"; import type { User } from "@prisma/client"; import type { LinksFunction, @@ -18,8 +18,6 @@ import { } from "@remix-run/react"; import { withSentry } from "@sentry/remix"; -import { useAtom } from "jotai"; -import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; import { ErrorBoundryComponent } from "./components/errors"; import { HomeIcon } from "./components/icons"; @@ -102,10 +100,7 @@ function Document({ children, title }: PropsWithChildren<{ title?: string }>) { function App() { const { maintenanceMode } = useLoaderData(); - const [workspaceSwitching, setWorkspaceSwitching] = useAtom(switchingWorkspaceAtom); - useEffect(() => { - setWorkspaceSwitching(false); - }) + return ( {maintenanceMode ? : } ); diff --git a/app/routes/_layout+/_layout.tsx b/app/routes/_layout+/_layout.tsx index 2ad2a9838..b58d6cd5b 100644 --- a/app/routes/_layout+/_layout.tsx +++ b/app/routes/_layout+/_layout.tsx @@ -36,33 +36,33 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { // @TODO - we need to look into doing a select as we dont want to expose all data always const user = authSession ? await db.user.findUnique({ - where: { email: authSession.email.toLowerCase() }, - include: { - roles: true, - organizations: { - select: { - id: true, - name: true, - type: true, - imageId: true, + where: { email: authSession.email.toLowerCase() }, + include: { + roles: true, + organizations: { + select: { + id: true, + name: true, + type: true, + imageId: true, + }, }, - }, - userOrganizations: { - where: { - userId: authSession.userId, - }, - select: { - organization: true, - roles: true, + userOrganizations: { + where: { + userId: authSession.userId, + }, + select: { + organization: true, + roles: true, + }, }, - }, - tier: { - select: { - tierLimit: true, + tier: { + select: { + tierLimit: true, + }, }, }, - }, - }) + }) : undefined; let subscription = null; if (user?.customerId && stripe) { @@ -118,28 +118,29 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { export default function App() { useCrisp(); - const [workspaceSwitching, setWorkspaceSwitching] = useAtom(switchingWorkspaceAtom); + const [workspaceSwitching] = useAtom(switchingWorkspaceAtom); - return (<> - { - workspaceSwitching ? -
- -
- : -
-
- -
-
+ return ( + <> +
+
+ +
+
+ {workspaceSwitching ? ( +
+ +
+ ) : ( -
- -
-
+ )} +
+ +
- } - ); +
+ + ); } export const ErrorBoundary = () => ( From aa39393b39a1c9c0534ab2fb254d95d13387b7e5 Mon Sep 17 00:00:00 2001 From: hunar Date: Wed, 31 Jan 2024 16:53:15 +0530 Subject: [PATCH 22/30] resolved feeedback points --- app/emails/bookings-updates-template.tsx | 95 +++++++++++++++++------- app/emails/invite-template.tsx | 4 + app/emails/styles.ts | 2 + app/modules/booking/email-helpers.ts | 1 + app/modules/booking/service.server.ts | 5 ++ app/modules/booking/worker.server.ts | 7 ++ 6 files changed, 87 insertions(+), 27 deletions(-) diff --git a/app/emails/bookings-updates-template.tsx b/app/emails/bookings-updates-template.tsx index e4dc01b2c..96f9edd88 100644 --- a/app/emails/bookings-updates-template.tsx +++ b/app/emails/bookings-updates-template.tsx @@ -1,4 +1,3 @@ -import type { Booking } from "@prisma/client"; import { Button, Html, @@ -12,38 +11,63 @@ import { } from "@react-email/components"; import { SERVER_URL } from "~/utils/env"; import { styles } from "./styles"; +import { ClientHint } from "~/modules/booking/types"; +import { getDateTimeFormatFromHints } from "~/utils/client-hints"; interface Props { heading: string; booking: any; assetCount: number; + hints: ClientHint; + hideViewButton?: boolean; } export function BookingUpdatesEmailTemplate({ booking, heading, + hints, assetCount, + hideViewButton = false, }: Props) { + const fromDate = getDateTimeFormatFromHints(hints, { + dateStyle: "short", + timeStyle: "short", + }).format(booking.from); + const toDate = getDateTimeFormatFromHints(hints, { + dateStyle: "short", + timeStyle: "short", + }).format(booking.to); return ( Bookings update from Shelf.nu - - Shelf's logo -
+ +
+ Shelf's logo +
+
{heading} - {booking.id} | {booking.name} | {assetCount}{" "} + {booking.name} | {assetCount}{" "} {assetCount === 1 ? "asset" : "assets"}

@@ -55,23 +79,26 @@ export function BookingUpdatesEmailTemplate({

From:{" "} - {booking.from.toLocaleString()} + {fromDate}

To:{" "} - {booking.to.toLocaleString()} + {toDate}

- + + {!hideViewButton && ( + + )} This email was sent to{" "} @@ -81,36 +108,50 @@ export function BookingUpdatesEmailTemplate({ > {booking.custodianUser!.email} {" "} - because it is part of the Shelf workspace. If you think you weren’t - supposed to have received this email please{" "} + because it is part of the Shelf workspace. + {/** + * @TODO need to figure out how to have workspace name and owner's email in this component + */} + {/* + *{organization?.name}* + + . If you think you weren’t supposed to have received this email please{" "} contact the owner {" "} - of the workspace. + of the workspace. */} {" "} - © 2023 Shelf.nu, Meander 901, 6825 MH Arnhem + © 2024 Shelf.nu
); } +/* + *The HTML content of an email will be accessed by a server file to send email, + we cannot import a TSX component in a server file so we are exporting TSX converted to HTML string using render function by react-email. + */ export const bookingUpdatesTemplateString = ({ booking, heading, assetCount, + hints, + hideViewButton = false, }: Props) => render( ); diff --git a/app/emails/invite-template.tsx b/app/emails/invite-template.tsx index 90e4d6cbb..98b1d560a 100644 --- a/app/emails/invite-template.tsx +++ b/app/emails/invite-template.tsx @@ -81,5 +81,9 @@ export function InvitationEmailTemplate({ invite, token }: Props) { ); } +/* + *The HTML content of an email will be accessed by a server file to send email, + we cannot import a TSX component in a server file so we are exporting TSX converted to HTML string using render function by react-email. + */ export const invitationTemplateString = ({ token, invite }: Props) => render(); diff --git a/app/emails/styles.ts b/app/emails/styles.ts index 4a7fe5f4b..98335b667 100644 --- a/app/emails/styles.ts +++ b/app/emails/styles.ts @@ -19,11 +19,13 @@ export const styles = { fontSize: "20px", color: "#101828", fontWeight: "600", + marginBottom: "16px" }, h2: { fontSize: "16px", color: "#101828", fontWeight: "600", + marginBottom: "16px" }, p: { fontSize: "16px", color: "#344054" }, }; diff --git a/app/modules/booking/email-helpers.ts b/app/modules/booking/email-helpers.ts index a71be986d..0dbe26a7b 100644 --- a/app/modules/booking/email-helpers.ts +++ b/app/modules/booking/email-helpers.ts @@ -184,6 +184,7 @@ export const sendCheckinReminder = async ( new Date() )} minutes.`, assetCount, + hints }), }); }; diff --git a/app/modules/booking/service.server.ts b/app/modules/booking/service.server.ts index ea53af00f..0f0eefcb2 100644 --- a/app/modules/booking/service.server.ts +++ b/app/modules/booking/service.server.ts @@ -235,6 +235,7 @@ export const upsertBooking = async ( booking: res, heading: `Booking confirmation for ${custodian}`, assetCount: res.assets.length, + hints }); if (data.status === BookingStatus.COMPLETE) { @@ -252,6 +253,7 @@ export const upsertBooking = async ( booking: res, heading: `Your booking has been completed: "${res.name}".`, assetCount: res._count.assets, + hints }); } @@ -272,6 +274,7 @@ export const upsertBooking = async ( booking: res, heading: `Your booking has been cancelled: "${res.name}".`, assetCount: res._count.assets, + hints }); } @@ -533,6 +536,8 @@ export const deleteBooking = async ( booking: b, heading: `Your booking has been deleted: "${b.name}".`, assetCount: b._count.assets, + hints, + hideViewButton: true }); await sendEmail({ diff --git a/app/modules/booking/worker.server.ts b/app/modules/booking/worker.server.ts index 8dac23f46..33b4879e5 100644 --- a/app/modules/booking/worker.server.ts +++ b/app/modules/booking/worker.server.ts @@ -61,6 +61,7 @@ export const registerBookingWorkers = () => { new Date() )}.`, assetCount: booking._count.assets, + hints: data.hints }), }).catch((err) => { console.error(`failed to send checkoutReminder email`, err); @@ -201,6 +202,12 @@ export const registerBookingWorkers = () => { bookingId: booking.id, hints: data.hints, }), + html: bookingUpdatesTemplateString({ + booking, + heading: `You have passed the deadline for checking in your booking "${booking.name}".`, + assetCount: booking._count.assets, + hints: data.hints + }), }).catch((err) => { console.error(`failed to send overdue reminder email`, err); }); From 5ead459278a7f04e81ed626e8fdf995d126c4223 Mon Sep 17 00:00:00 2001 From: hunar Date: Wed, 31 Jan 2024 16:54:28 +0530 Subject: [PATCH 23/30] resolved feedback points --- app/emails/bookings-updates-template.tsx | 4 ++-- app/emails/styles.ts | 4 ++-- app/modules/booking/email-helpers.ts | 2 +- app/modules/booking/service.server.ts | 8 ++++---- app/modules/booking/worker.server.ts | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/emails/bookings-updates-template.tsx b/app/emails/bookings-updates-template.tsx index 96f9edd88..418da20ef 100644 --- a/app/emails/bookings-updates-template.tsx +++ b/app/emails/bookings-updates-template.tsx @@ -9,10 +9,10 @@ import { Container, Heading, } from "@react-email/components"; +import type { ClientHint } from "~/modules/booking/types"; +import { getDateTimeFormatFromHints } from "~/utils/client-hints"; import { SERVER_URL } from "~/utils/env"; import { styles } from "./styles"; -import { ClientHint } from "~/modules/booking/types"; -import { getDateTimeFormatFromHints } from "~/utils/client-hints"; interface Props { heading: string; diff --git a/app/emails/styles.ts b/app/emails/styles.ts index 98335b667..f2c891f26 100644 --- a/app/emails/styles.ts +++ b/app/emails/styles.ts @@ -19,13 +19,13 @@ export const styles = { fontSize: "20px", color: "#101828", fontWeight: "600", - marginBottom: "16px" + marginBottom: "16px", }, h2: { fontSize: "16px", color: "#101828", fontWeight: "600", - marginBottom: "16px" + marginBottom: "16px", }, p: { fontSize: "16px", color: "#344054" }, }; diff --git a/app/modules/booking/email-helpers.ts b/app/modules/booking/email-helpers.ts index 0dbe26a7b..6f6d13900 100644 --- a/app/modules/booking/email-helpers.ts +++ b/app/modules/booking/email-helpers.ts @@ -184,7 +184,7 @@ export const sendCheckinReminder = async ( new Date() )} minutes.`, assetCount, - hints + hints, }), }); }; diff --git a/app/modules/booking/service.server.ts b/app/modules/booking/service.server.ts index 0f0eefcb2..a70f4a766 100644 --- a/app/modules/booking/service.server.ts +++ b/app/modules/booking/service.server.ts @@ -235,7 +235,7 @@ export const upsertBooking = async ( booking: res, heading: `Booking confirmation for ${custodian}`, assetCount: res.assets.length, - hints + hints, }); if (data.status === BookingStatus.COMPLETE) { @@ -253,7 +253,7 @@ export const upsertBooking = async ( booking: res, heading: `Your booking has been completed: "${res.name}".`, assetCount: res._count.assets, - hints + hints, }); } @@ -274,7 +274,7 @@ export const upsertBooking = async ( booking: res, heading: `Your booking has been cancelled: "${res.name}".`, assetCount: res._count.assets, - hints + hints, }); } @@ -537,7 +537,7 @@ export const deleteBooking = async ( heading: `Your booking has been deleted: "${b.name}".`, assetCount: b._count.assets, hints, - hideViewButton: true + hideViewButton: true, }); await sendEmail({ diff --git a/app/modules/booking/worker.server.ts b/app/modules/booking/worker.server.ts index 33b4879e5..692211b0c 100644 --- a/app/modules/booking/worker.server.ts +++ b/app/modules/booking/worker.server.ts @@ -61,7 +61,7 @@ export const registerBookingWorkers = () => { new Date() )}.`, assetCount: booking._count.assets, - hints: data.hints + hints: data.hints, }), }).catch((err) => { console.error(`failed to send checkoutReminder email`, err); @@ -206,7 +206,7 @@ export const registerBookingWorkers = () => { booking, heading: `You have passed the deadline for checking in your booking "${booking.name}".`, assetCount: booking._count.assets, - hints: data.hints + hints: data.hints, }), }).catch((err) => { console.error(`failed to send overdue reminder email`, err); From 6ac5ce902f1ad0a7506d525038869688dfed7ef7 Mon Sep 17 00:00:00 2001 From: Madhurjya Kalita <88551650+melsonic@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:06:50 +0000 Subject: [PATCH 24/30] centered the spinner --- app/routes/_layout+/_layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/routes/_layout+/_layout.tsx b/app/routes/_layout+/_layout.tsx index b58d6cd5b..2d9e86958 100644 --- a/app/routes/_layout+/_layout.tsx +++ b/app/routes/_layout+/_layout.tsx @@ -86,7 +86,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const { organizationId, organizations, currentOrganization } = await requireOrganisationId(authSession, request); - // setWorkspaceSwitching(); return json( { @@ -128,8 +127,9 @@ export default function App() {
{workspaceSwitching ? ( -
+
+

Switching workspaces...

) : ( From b2b907fa365aa481b852be05e12846e03fad3c8a Mon Sep 17 00:00:00 2001 From: Donkoko Date: Thu, 1 Feb 2024 10:43:00 +0200 Subject: [PATCH 25/30] small fixes --- app/atoms/switching-workspace.ts | 2 +- app/components/layout/sidebar/sidebar.tsx | 6 +----- app/routes/_layout+/_layout.tsx | 5 ++--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/atoms/switching-workspace.ts b/app/atoms/switching-workspace.ts index 0fbb133b5..0dfb6a8de 100644 --- a/app/atoms/switching-workspace.ts +++ b/app/atoms/switching-workspace.ts @@ -1,3 +1,3 @@ import { atom } from "jotai"; -export const switchingWorkspaceAtom = atom(false); +export const switchingWorkspaceAtom = atom(true); diff --git a/app/components/layout/sidebar/sidebar.tsx b/app/components/layout/sidebar/sidebar.tsx index c59fbee25..6aedf9cb7 100644 --- a/app/components/layout/sidebar/sidebar.tsx +++ b/app/components/layout/sidebar/sidebar.tsx @@ -18,11 +18,7 @@ import MenuItems from "./menu-items"; import { OrganizationSelect } from "./organization-select"; import Overlay from "./overlay"; -export default function Sidebar({ - disabled, -}: { - disabled?: boolean; // used to disable the sidebar when the user is switching workspaces -}) { +export default function Sidebar() { const { user, minimizedSidebar, currentOrganizationId } = useLoaderData(); const [isMobileNavOpen, toggleMobileNav] = useAtom(toggleMobileNavAtom); diff --git a/app/routes/_layout+/_layout.tsx b/app/routes/_layout+/_layout.tsx index 2d9e86958..0d68abb58 100644 --- a/app/routes/_layout+/_layout.tsx +++ b/app/routes/_layout+/_layout.tsx @@ -86,7 +86,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const { organizationId, organizations, currentOrganization } = await requireOrganisationId(authSession, request); - return json( { user, @@ -123,11 +122,11 @@ export default function App() { <>
- +
{workspaceSwitching ? ( -
+

Switching workspaces...

From de833043f5a617bbeb333e9ff4e2ecf66df45113 Mon Sep 17 00:00:00 2001 From: Madhurjya Kalita <88551650+melsonic@users.noreply.github.com> Date: Thu, 1 Feb 2024 11:00:22 +0000 Subject: [PATCH 26/30] made sidebar items non interactive during workspace switching --- app/components/layout/sidebar/bottom.tsx | 5 ++++- app/components/layout/sidebar/menu-items.tsx | 15 +++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/components/layout/sidebar/bottom.tsx b/app/components/layout/sidebar/bottom.tsx index 2e7295f3d..67161b49b 100644 --- a/app/components/layout/sidebar/bottom.tsx +++ b/app/components/layout/sidebar/bottom.tsx @@ -1,6 +1,8 @@ import { useState } from "react"; import type { User } from "@prisma/client"; import { Form } from "@remix-run/react"; +import { useAtom } from "jotai"; +import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; import { ChevronRight, QuestionsIcon } from "~/components/icons"; import { CrispButton } from "~/components/marketing/crisp"; import { Button } from "~/components/shared"; @@ -20,9 +22,10 @@ interface Props { export default function SidebarBottom({ user }: Props) { const [dropdownOpen, setDropdownOpen] = useState(false); + const [workspaceSwitching] = useAtom(switchingWorkspaceAtom); return ( -
+
setDropdownOpen((prev) => !prev)} diff --git a/app/components/layout/sidebar/menu-items.tsx b/app/components/layout/sidebar/menu-items.tsx index 530a53221..9492a1533 100644 --- a/app/components/layout/sidebar/menu-items.tsx +++ b/app/components/layout/sidebar/menu-items.tsx @@ -9,6 +9,7 @@ import type { loader } from "~/routes/_layout+/_layout"; import { tw } from "~/utils"; import { toggleMobileNavAtom } from "./atoms"; import { ChatWithAnExpert } from "./chat-with-an-expert"; +import { switchingWorkspaceAtom } from "~/atoms/switching-workspace"; const MenuItems = ({ fetcher }: { fetcher: FetcherWithComponents }) => { const [, toggleMobileNav] = useAtom(toggleMobileNavAtom); @@ -18,6 +19,7 @@ const MenuItems = ({ fetcher }: { fetcher: FetcherWithComponents }) => { const location = useLocation(); /** We need to do this becasue of a special way we handle the bookings link that doesnt allow us to use NavLink currently */ const isBookingsRoute = location.pathname.includes("/bookings"); + const [workspaceSwitching] = useAtom(switchingWorkspaceAtom); return (
@@ -29,7 +31,8 @@ const MenuItems = ({ fetcher }: { fetcher: FetcherWithComponents }) => { className={({ isActive }) => tw( "my-1 flex items-center gap-3 rounded px-3 py-2.5 text-[16px] font-semibold text-gray-700 transition-all duration-75 hover:bg-primary-50 hover:text-primary-600", - isActive ? "active bg-primary-50 text-primary-600" : "" + isActive ? "active bg-primary-50 text-primary-600" : "", + workspaceSwitching ? "pointer-events-none" : "" ) } to={"/admin-dashboard/users"} @@ -77,6 +80,7 @@ const MenuItems = ({ fetcher }: { fetcher: FetcherWithComponents }) => { "data-test-id": `${item.label.toLowerCase()}SidebarMenuItem`, onClick: toggleMobileNav, title: item.label, + disabled: workspaceSwitching, className: tw( "my-1 flex items-center gap-3 rounded border-0 bg-transparent px-3 text-left text-[16px] font-semibold text-gray-700 transition-all duration-75 hover:bg-primary-50 hover:text-primary-600", canUseBookings @@ -95,7 +99,8 @@ const MenuItems = ({ fetcher }: { fetcher: FetcherWithComponents }) => { className={({ isActive }) => tw( "my-1 flex items-center gap-3 rounded px-3 py-2.5 text-[16px] font-semibold text-gray-700 transition-all duration-75 hover:bg-primary-50 hover:text-primary-600", - isActive ? "active bg-primary-50 text-primary-600" : "" + isActive ? "active bg-primary-50 text-primary-600" : "", + workspaceSwitching ? "pointer-events-none" : "" ) } to={item.to} @@ -131,7 +136,8 @@ const MenuItems = ({ fetcher }: { fetcher: FetcherWithComponents }) => { className={({ isActive }) => tw( "my-1 flex items-center gap-3 rounded px-3 py-2.5 text-[16px] font-semibold text-gray-700 transition-all duration-75 hover:bg-primary-50 hover:text-primary-600", - isActive ? "active bg-primary-50 text-primary-600" : "" + isActive ? "active bg-primary-50 text-primary-600" : "", + workspaceSwitching ? "pointer-events-none" : "" ) } to={item.to} @@ -159,7 +165,8 @@ const MenuItems = ({ fetcher }: { fetcher: FetcherWithComponents }) => {