Skip to content

Commit

Permalink
Upgrade website to react router v7 (#1214)
Browse files Browse the repository at this point in the history
* feat(website): upgrade to react router v7

* chore(website): collapse import into one

* build(ts): upgrade dependencies

* chore(website): migrate to `safe-routes`

* chore(frontend): take confirmation text as prop

* refactor(frontend): separate component to expire cache key

* refactor(frontend): move component to dedicate file

* feat(frontend): refresh collections list correctly

* chore(frontend): remove useless handling for form data

* docs: add info about develop docker tag

* build(website): upgrade `remix-utils` dep

* chore(website): add remix-utils deps
  • Loading branch information
IgnisDa authored Jan 29, 2025
1 parent a86ecc3 commit 7d81766
Show file tree
Hide file tree
Showing 31 changed files with 1,518 additions and 539 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
.env
.moon/cache
.moon/docker
.react-router
.next/
.pnp.*
Thumbs.db
Expand Down
38 changes: 38 additions & 0 deletions apps/frontend/app/components/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
Form,
Link,
useFetcher,
useLocation,
useNavigate,
useRevalidator,
} from "@remix-run/react";
Expand Down Expand Up @@ -70,6 +71,7 @@ import {
IconMoodHappy,
IconMoodSad,
IconRefresh,
IconRotateClockwise,
IconScaleOutline,
IconSearch,
IconServer,
Expand All @@ -95,6 +97,7 @@ import {
getMetadataIcon,
getSurroundingElements,
openConfirmationModal,
redirectToQueryParam,
reviewYellow,
} from "~/lib/generals";
import {
Expand Down Expand Up @@ -1358,3 +1361,38 @@ export const DisplayListDetailsAndRefresh = (props: {
</Group>
);
};

export const ExpireCacheKeyButton = (props: {
cacheId: string;
confirmationText?: string;
}) => {
const submit = useConfirmSubmit();
const location = useLocation();

return (
<Form
replace
method="POST"
action={withQuery($path("/actions"), {
intent: "expireCacheKey",
[redirectToQueryParam]: location.pathname,
})}
>
<input type="hidden" name="cacheId" value={props.cacheId} />
<ActionIcon
type="submit"
variant="subtle"
onClick={(e) => {
if (!props.confirmationText) return;
const form = e.currentTarget.form;
if (form) {
e.preventDefault();
openConfirmationModal(props.confirmationText, () => submit(form));
}
}}
>
<IconRotateClockwise />
</ActionIcon>
</Form>
);
};
7 changes: 6 additions & 1 deletion apps/frontend/app/lib/utilities.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,17 @@ export const getUserPreferences = async (request: Request) => {
return userDetails.preferences;
};

export const getUserCollectionsList = async (request: Request) => {
export const getUserCollectionsListRaw = async (request: Request) => {
const { userCollectionsList } = await serverGqlService.authenticatedRequest(
request,
UserCollectionsListDocument,
{},
);
return userCollectionsList;
};

export const getUserCollectionsList = async (request: Request) => {
const userCollectionsList = await getUserCollectionsListRaw(request);
return userCollectionsList.response;
};

Expand Down
64 changes: 12 additions & 52 deletions apps/frontend/app/routes/_dashboard._index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import {
ActionIcon,
Alert,
Container,
Group,
Stack,
Text,
} from "@mantine/core";
import { Alert, Container, Group, Stack, Text } from "@mantine/core";
import type {
ActionFunctionArgs,
LoaderFunctionArgs,
MetaArgs,
} from "@remix-run/node";
import { Form, useLoaderData } from "@remix-run/react";
import { useLoaderData } from "@remix-run/react";
import {
type CalendarEventPartFragment,
CollectionContentsDocument,
Expand All @@ -24,32 +17,23 @@ import {
UserUpcomingCalendarEventsDocument,
} from "@ryot/generated/graphql/backend/graphql";
import { isNumber } from "@ryot/ts-utils";
import {
IconBackpack,
IconInfoCircle,
IconRotateClockwise,
} from "@tabler/icons-react";
import { IconBackpack, IconInfoCircle } from "@tabler/icons-react";
import CryptoJS from "crypto-js";
import type { ReactNode } from "react";
import { $path } from "remix-routes";
import { ClientOnly } from "remix-utils/client-only";
import invariant from "tiny-invariant";
import { match } from "ts-pattern";
import { withQuery } from "ufo";
import { useLocalStorage } from "usehooks-ts";
import {
ApplicationGrid,
DisplaySummarySection,
ExpireCacheKeyButton,
ProRequiredAlert,
} from "~/components/common";
import { DisplayCollectionEntity } from "~/components/common";
import { MetadataDisplayItem } from "~/components/media";
import { dayjsLib, openConfirmationModal } from "~/lib/generals";
import {
useConfirmSubmit,
useCoreDetails,
useUserPreferences,
} from "~/lib/hooks";
import { dayjsLib } from "~/lib/generals";
import { useCoreDetails, useUserPreferences } from "~/lib/hooks";
import {
getUserCollectionsList,
getUserPreferences,
Expand Down Expand Up @@ -179,7 +163,6 @@ export default function Page() {
.length > 0 ? (
<Section key={v} lot={v}>
<SectionTitleWithRefreshIcon
bypassConfirm
text="In Progress"
cacheId={loaderData.inProgressCollectionContents.cacheId}
/>
Expand All @@ -202,6 +185,7 @@ export default function Page() {
<SectionTitleWithRefreshIcon
text="Recommendations"
cacheId={loaderData.userMetadataRecommendations.cacheId}
confirmationText="Are you sure you want to refresh the recommendations?"
/>
{coreDetails.isServerKeyValidated ? (
<ApplicationGrid>
Expand Down Expand Up @@ -240,39 +224,15 @@ export default function Page() {
const SectionTitleWithRefreshIcon = (props: {
text: string;
cacheId: string;
bypassConfirm?: true;
confirmationText?: string;
}) => {
const submit = useConfirmSubmit();

return (
<Group justify="space-between">
<SectionTitle text={props.text} />
<Form
replace
method="POST"
action={withQuery($path("/actions"), {
intent: "expireCacheKey",
})}
>
<input type="hidden" name="cacheId" value={props.cacheId} />
<ActionIcon
type="submit"
variant="subtle"
onClick={(e) => {
if (props.bypassConfirm) return;
const form = e.currentTarget.form;
if (form) {
e.preventDefault();
openConfirmationModal(
"Are you sure you want to refresh the recommendations?",
() => submit(form),
);
}
}}
>
<IconRotateClockwise />
</ActionIcon>
</Form>
<ExpireCacheKeyButton
cacheId={props.cacheId}
confirmationText={props.confirmationText}
/>
</Group>
);
};
Expand Down
55 changes: 33 additions & 22 deletions apps/frontend/app/routes/_dashboard.collections.list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ import { $path } from "remix-routes";
import { match } from "ts-pattern";
import { withQuery } from "ufo";
import { z } from "zod";
import { DebouncedSearchInput, ProRequiredAlert } from "~/components/common";
import {
DebouncedSearchInput,
ExpireCacheKeyButton,
ProRequiredAlert,
} from "~/components/common";
import {
PRO_REQUIRED_MESSAGE,
clientGqlService,
Expand All @@ -89,17 +93,19 @@ import {
import {
createToastHeaders,
getEnhancedCookieName,
getUserCollectionsListRaw,
redirectUsingEnhancedCookieSearchParams,
serverGqlService,
} from "~/lib/utilities.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
const cookieName = await getEnhancedCookieName("collections.list", request);
await redirectUsingEnhancedCookieSearchParams(request, cookieName);
const [{ usersList }] = await Promise.all([
const [{ usersList }, userCollectionsList] = await Promise.all([
serverGqlService.authenticatedRequest(request, UsersListDocument, {}),
getUserCollectionsListRaw(request),
]);
return { usersList, cookieName };
return { usersList, cookieName, userCollectionsList };
};

export const meta = (_args: MetaArgs<typeof loader>) => {
Expand Down Expand Up @@ -223,25 +229,30 @@ export default function Page() {
return (
<Container size="sm">
<Stack>
<Flex align="center" gap="md">
<Title>Your collections</Title>
<ActionIcon
color="green"
variant="outline"
onClick={() => setToUpdateCollection({})}
>
<IconPlus size={20} />
</ActionIcon>
<Modal
centered
size="lg"
withCloseButton={false}
opened={toUpdateCollection !== null}
onClose={() => setToUpdateCollection(null)}
>
<CreateOrUpdateModal toUpdateCollection={toUpdateCollection} />
</Modal>
</Flex>
<Group justify="space-between" wrap="nowrap">
<Flex align="center" gap="md">
<Title>Your collections</Title>
<ActionIcon
color="green"
variant="outline"
onClick={() => setToUpdateCollection({})}
>
<IconPlus size={20} />
</ActionIcon>
<Modal
centered
size="lg"
withCloseButton={false}
opened={toUpdateCollection !== null}
onClose={() => setToUpdateCollection(null)}
>
<CreateOrUpdateModal toUpdateCollection={toUpdateCollection} />
</Modal>
</Flex>
<ExpireCacheKeyButton
cacheId={loaderData.userCollectionsList.cacheId}
/>
</Group>
<DebouncedSearchInput
initialValue={query}
enhancedQueryParams={loaderData.cookieName}
Expand Down
7 changes: 3 additions & 4 deletions apps/frontend/app/routes/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ export const loader = async () => redirect($path("/"));
export const action = async ({ request }: ActionFunctionArgs) => {
const formData = await request.clone().formData();
const intent = getActionIntent(request);
const redirectToForm = formData.get(redirectToQueryParam);
let redirectTo = redirectToForm ? redirectToForm.toString() : undefined;
const { searchParams } = new URL(request.url);
const redirectToSearchParams = searchParams.get(redirectToQueryParam);
let redirectTo = redirectToSearchParams || undefined;
let returnData = {};
const headers = new Headers();
let status = undefined;
Expand Down Expand Up @@ -420,7 +421,6 @@ export const action = async ({ request }: ActionFunctionArgs) => {
: "Progress updated successfully",
}),
);
redirectTo = submission[redirectToQueryParam];
})
.with("individualProgressUpdate", async () => {
const submission = processSubmission(formData, bulkUpdateSchema);
Expand Down Expand Up @@ -564,7 +564,6 @@ const progressUpdateSchema = z
date: z.string().optional(),
metadataLot: z.nativeEnum(MediaLot),
providerWatchedOn: z.string().optional(),
[redirectToQueryParam]: z.string().optional(),
showAllEpisodesBefore: zodBoolAsString.optional(),
animeAllEpisodesBefore: zodCheckboxAsString.optional(),
podcastAllEpisodesBefore: zodCheckboxAsString.optional(),
Expand Down
26 changes: 13 additions & 13 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
"@formkit/auto-animate": "0.8.2",
"@hello-pangea/dnd": "17.0.0",
"@lukemorales/query-key-factory": "1.3.4",
"@mantine/carousel": "7.16.1",
"@mantine/charts": "7.16.1",
"@mantine/code-highlight": "7.16.1",
"@mantine/core": "7.16.1",
"@mantine/dates": "7.16.1",
"@mantine/form": "7.16.1",
"@mantine/hooks": "7.16.1",
"@mantine/modals": "7.16.1",
"@mantine/notifications": "7.16.1",
"@mantine/carousel": "7.16.2",
"@mantine/charts": "7.16.2",
"@mantine/code-highlight": "7.16.2",
"@mantine/core": "7.16.2",
"@mantine/dates": "7.16.2",
"@mantine/form": "7.16.2",
"@mantine/hooks": "7.16.2",
"@mantine/modals": "7.16.2",
"@mantine/notifications": "7.16.2",
"@remix-pwa/sw": "3.0.10",
"@remix-pwa/worker-runtime": "2.1.4",
"@remix-run/node": "2.15.2",
Expand All @@ -30,8 +30,8 @@
"@ryot/graphql": "workspace:*",
"@ryot/ts-utils": "workspace:*",
"@tabler/icons-react": "3.17.0",
"@tanstack/react-query": "5.64.2",
"@tanstack/react-query-devtools": "5.64.2",
"@tanstack/react-query": "5.65.1",
"@tanstack/react-query-devtools": "5.65.1",
"clsx": "2.1.1",
"cookie": "1.0.2",
"crypto-js": "4.2.0",
Expand All @@ -45,7 +45,7 @@
"html2canvas": "1.4.1",
"humanize-duration-ts": "2.1.1",
"immer": "10.1.1",
"isbot": "5.1.21",
"isbot": "5.1.22",
"jotai": "2.11.1",
"js-cookie": "3.0.5",
"jwt-decode": "4.0.0",
Expand All @@ -54,7 +54,7 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-virtuoso": "4.12.3",
"recharts": "2.15.0",
"recharts": "2.15.1",
"remix-routes": "1.7.7",
"remix-utils": "7.7.0",
"tailwind-merge": "2.6.0",
Expand Down
2 changes: 1 addition & 1 deletion apps/website/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ COPY --from=website-builder --chown=ryot:ryot /app/apps/website/node_modules ./n
COPY --from=website-builder --chown=ryot:ryot /app/apps/website/package.json ./package.json
COPY --from=website-builder --chown=ryot:ryot /app/apps/website/build ./build
COPY --chown=ryot:ryot apps/website/app/drizzle/migrations app/drizzle/migrations
CMD npx remix-serve ./build/server/index.js
CMD npx react-router-serve ./build/server/index.js
4 changes: 2 additions & 2 deletions apps/website/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { RemixBrowser } from "@remix-run/react";
import { StrictMode, startTransition } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
<HydratedRouter />
</StrictMode>,
);
});
Loading

0 comments on commit 7d81766

Please sign in to comment.