diff --git a/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx b/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx
index b9cc01548..e9225fbb8 100644
--- a/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx
+++ b/apps/web/src/routes/_app/datahub/$subjectId/assignments.tsx
@@ -9,10 +9,11 @@ import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
import { CopyButton } from '@opendatacapture/react-core';
import type { Assignment, AssignmentStatus } from '@opendatacapture/schemas/assignment';
import type { UnilingualInstrumentInfo } from '@opendatacapture/schemas/instrument';
-import { createFileRoute } from '@tanstack/react-router';
+import { createFileRoute, redirect } from '@tanstack/react-router';
import { AssignmentEmailForm } from '@/components/AssignmentEmailForm';
import { QRCode } from '@/components/QRCode';
+import { config } from '@/config';
import { useAssignmentsQuery } from '@/hooks/useAssignmentsQuery';
import { useInstrument } from '@/hooks/useInstrument';
import { useInstrumentInfoQuery } from '@/hooks/useInstrumentInfoQuery';
@@ -180,5 +181,10 @@ const RouteComponent = () => {
};
export const Route = createFileRoute('/_app/datahub/$subjectId/assignments')({
+ beforeLoad: ({ params }) => {
+ if (!config.setup.isGatewayEnabled) {
+ throw redirect({ params, to: '/datahub/$subjectId/table' });
+ }
+ },
component: RouteComponent
});
diff --git a/apps/web/src/routes/_app/group/email-templates.tsx b/apps/web/src/routes/_app/group/email-templates.tsx
index bd15d9666..a7b819f36 100644
--- a/apps/web/src/routes/_app/group/email-templates.tsx
+++ b/apps/web/src/routes/_app/group/email-templates.tsx
@@ -2,10 +2,11 @@ import React from 'react';
import { Heading } from '@douglasneuroinformatics/libui/components';
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
-import { createFileRoute } from '@tanstack/react-router';
+import { createFileRoute, redirect } from '@tanstack/react-router';
import { GroupEmailTemplates } from '@/components/GroupEmailTemplates';
import { PageHeader } from '@/components/PageHeader';
+import { config } from '@/config';
const RouteComponent = () => {
const { t } = useTranslation();
@@ -23,5 +24,10 @@ const RouteComponent = () => {
};
export const Route = createFileRoute('/_app/group/email-templates')({
+ beforeLoad: () => {
+ if (!config.setup.isGatewayEnabled) {
+ throw redirect({ to: '/dashboard' });
+ }
+ },
component: RouteComponent
});
diff --git a/apps/web/src/routes/_app/session/remote-assignment.tsx b/apps/web/src/routes/_app/session/remote-assignment.tsx
index 9092ec273..60dfcbe06 100644
--- a/apps/web/src/routes/_app/session/remote-assignment.tsx
+++ b/apps/web/src/routes/_app/session/remote-assignment.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
import { CopyButton } from '@opendatacapture/react-core';
import type { Assignment, CreateAssignmentData } from '@opendatacapture/schemas/assignment';
import type { TranslatedInstrumentInfo } from '@opendatacapture/schemas/instrument';
-import { createFileRoute, useNavigate } from '@tanstack/react-router';
+import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';
import { z } from 'zod/v4';
import { AssignmentEmailForm } from '@/components/AssignmentEmailForm';
@@ -13,6 +13,7 @@ import { InstrumentShowcase } from '@/components/InstrumentShowcase';
import { PageHeader } from '@/components/PageHeader';
import { QRCode } from '@/components/QRCode';
import { WithFallback } from '@/components/WithFallback';
+import { config } from '@/config';
import { useCreateAssignment } from '@/hooks/useCreateAssignment';
import { useInstrumentInfoQuery } from '@/hooks/useInstrumentInfoQuery';
import { useSetupStateQuery } from '@/hooks/useSetupStateQuery';
@@ -211,5 +212,10 @@ const RouteComponent = () => {
};
export const Route = createFileRoute('/_app/session/remote-assignment')({
+ beforeLoad: () => {
+ if (!config.setup.isGatewayEnabled) {
+ throw redirect({ to: '/dashboard' });
+ }
+ },
component: RouteComponent
});
diff --git a/testing/src/specs/remote-assignment.spec.ts b/testing/src/specs/remote-assignment.spec.ts
index c8579f1c7..787d6339b 100644
--- a/testing/src/specs/remote-assignment.spec.ts
+++ b/testing/src/specs/remote-assignment.spec.ts
@@ -101,6 +101,25 @@ test.describe('remote assignment', () => {
expect(secondUrl).not.toBe(firstUrl);
});
+ // Entering by URL is the path a bookmarked link takes, and it runs the GATEWAY_ENABLED guard in
+ // `beforeLoad` on a cold document load rather than on a client-side transition from the tab.
+ // `getPageModel` asserts it landed on the URL it asked for, so a guard that wrongly bounced the
+ // request to the table tab fails here.
+ test('should allow deep-linking to the assignments tab while the gateway is deployed', async ({
+ getPageModel,
+ page,
+ uniqueId
+ }) => {
+ await startSession(getPageModel, `Deep${uniqueId}`, `Subject${uniqueId}`, 'Male');
+
+ await page.locator('[data-testid^="nav-button-/datahub/"]').click();
+ await page.waitForURL('**/datahub/**/table');
+ const subjectId = page.url().split('/datahub/')[1]!.split('/')[0]!;
+
+ const assignmentsPage = await getPageModel('/datahub/$subjectId/assignments', { subjectId });
+ await expect(assignmentsPage.assignmentRows).toHaveCount(0);
+ });
+
test("should let a group manager cancel an outstanding assignment from the subject's assignments tab", async ({
getPageModel,
page,