From 6c231ca88248aafd525b0961edfd185e3eec5789 Mon Sep 17 00:00:00 2001 From: thomasgross Date: Fri, 22 Nov 2024 11:16:19 +0100 Subject: [PATCH 1/7] feat: add reader page to show publizon reader and enhance work page layout with smartLink --- .../[id]/article.dpl-cms.graphql | 0 app/{article => }/[id]/loadArticle.ts | 0 app/{article => }/[id]/page.tsx | 0 app/work/[id]/read/page.tsx | 20 ++++++ .../pages/workPageLayout/WorkPageLayout.tsx | 21 +++++- .../shared/publizonReader/PublizonReader.tsx | 67 ++++++++----------- components/shared/publizonReader/helper.ts | 12 ++++ components/shared/smartLink/SmartLink.tsx | 36 ++++++++++ components/shared/workCard/WorkCard.tsx | 2 +- lib/helpers/helper.routes.ts | 36 +++++----- lib/shadcn/utils.ts | 3 + package.json | 3 +- styles/globals.css | 1 + tailwind.config.ts | 1 + 14 files changed, 138 insertions(+), 64 deletions(-) rename app/{article => }/[id]/article.dpl-cms.graphql (100%) rename app/{article => }/[id]/loadArticle.ts (100%) rename app/{article => }/[id]/page.tsx (100%) create mode 100644 app/work/[id]/read/page.tsx create mode 100644 components/shared/smartLink/SmartLink.tsx create mode 100644 lib/shadcn/utils.ts diff --git a/app/article/[id]/article.dpl-cms.graphql b/app/[id]/article.dpl-cms.graphql similarity index 100% rename from app/article/[id]/article.dpl-cms.graphql rename to app/[id]/article.dpl-cms.graphql diff --git a/app/article/[id]/loadArticle.ts b/app/[id]/loadArticle.ts similarity index 100% rename from app/article/[id]/loadArticle.ts rename to app/[id]/loadArticle.ts diff --git a/app/article/[id]/page.tsx b/app/[id]/page.tsx similarity index 100% rename from app/article/[id]/page.tsx rename to app/[id]/page.tsx diff --git a/app/work/[id]/read/page.tsx b/app/work/[id]/read/page.tsx new file mode 100644 index 00000000..2f261079 --- /dev/null +++ b/app/work/[id]/read/page.tsx @@ -0,0 +1,20 @@ +"use client" + +import React from "react" + +import Reader from "@/components/shared/publizonReader/PublizonReader" + +function Page({ searchParams: { id } }: { searchParams: { id: string } }) { + const handleBack = () => { + window.history.back() + } + + return ( +
+
+ handleBack()} type="demo" identifier={id} /> +
+ ) +} + +export default Page diff --git a/components/pages/workPageLayout/WorkPageLayout.tsx b/components/pages/workPageLayout/WorkPageLayout.tsx index 6867f43b..48ec87b5 100644 --- a/components/pages/workPageLayout/WorkPageLayout.tsx +++ b/components/pages/workPageLayout/WorkPageLayout.tsx @@ -3,17 +3,34 @@ import { useQuery } from "@tanstack/react-query" import React from "react" +import { Button } from "@/components/shared/button/Button" +import SmartLink from "@/components/shared/smartLink/SmartLink" import { useGetMaterialQuery } from "@/lib/graphql/generated/fbi/graphql" +import { resolveUrl } from "@/lib/helpers/helper.routes" function WorkPageLayout({ wid }: { wid: string }) { - const data = useQuery({ + const { data } = useQuery({ queryKey: useGetMaterialQuery.getKey({ wid }), queryFn: useGetMaterialQuery.fetcher({ wid }), }) + const manifestations = data?.work?.manifestations.all + const identifier = manifestations?.[0].identifiers?.[0].value || "" + + const url = resolveUrl({ + routeParams: { work: "work", ":wid": wid, read: "read" }, + queryParams: { id: identifier }, + }) + return (
-
{JSON.stringify({ data }, null, 2)}
+ {identifier && ( + + )}
) } diff --git a/components/shared/publizonReader/PublizonReader.tsx b/components/shared/publizonReader/PublizonReader.tsx index 05c07d9a..45eb891d 100644 --- a/components/shared/publizonReader/PublizonReader.tsx +++ b/components/shared/publizonReader/PublizonReader.tsx @@ -1,81 +1,70 @@ -import React, { CSSProperties, useEffect } from "react" +"use client" -import { appendAsset, readerAssets } from "./helper" +import React, { useEffect } from "react" -// type ReaderType = { identifier?: string; orderId?: string }; +import { appendAsset, readerAssets, removeAsset } from "./helper" // Define mutually exclusive types for identifier and orderId type ReaderType = | { + type: "demo" identifier: string - orderId?: never + onBackCallback: () => void } | { - identifier?: never + type: "rent" orderId: string + onBackCallback: () => void } -const Reader = ({ identifier, orderId }: ReaderType) => { +const Reader = (props: ReaderType) => { useEffect(() => { readerAssets.forEach(appendAsset) - }, [identifier, orderId]) - const readerStyles: CSSProperties = { - position: "absolute", - top: "0", // Padding from the top - left: "0", // Padding from the left - right: "0", // Padding from the right - bottom: "0", // Padding from the bottom - padding: "20px", // Padding for the reader - width: "100%", - maxWidth: "unset", - zIndex: 1000, - // border: "1px dotted black", // Should be removed in production - margin: "0", - } - - const handleBack = () => { - window.history.back() - } + // Attach the onReaderBackCallback function to the window object to be able to enable callback methods calls through the close button + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-shadow + window.onReaderBackCallback = () => { + props.onBackCallback() + } - const handleKeyDown = (event: React.KeyboardEvent) => { - if (event.key === "Escape") { - handleBack() + return () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete window.onReaderBackCallback + readerAssets.forEach(removeAsset) } - } + }, [props]) - if (orderId) { + if (props.type === "rent") { return (
-

orderId: {orderId}

+

orderId: {props.orderId}

) } - if (identifier) { + if (props.type === "demo") { return (
) diff --git a/components/shared/publizonReader/helper.ts b/components/shared/publizonReader/helper.ts index feb64fa7..fd6cc8a2 100644 --- a/components/shared/publizonReader/helper.ts +++ b/components/shared/publizonReader/helper.ts @@ -39,3 +39,15 @@ export const appendAsset = ({ src, type }: AssetType) => { document.head.appendChild(linkElement) } } + +export const removeAsset = ({ src, type }: AssetType) => { + if (type === "script") { + const scriptElement = document.querySelector(`script[src="${src}"]`) + scriptElement?.remove() + } + + if (type === "link") { + const linkElement = document.querySelector(`link[href="${src}"]`) + linkElement?.remove() + } +} diff --git a/components/shared/smartLink/SmartLink.tsx b/components/shared/smartLink/SmartLink.tsx new file mode 100644 index 00000000..a79dd3fc --- /dev/null +++ b/components/shared/smartLink/SmartLink.tsx @@ -0,0 +1,36 @@ +import Link from "next/link" +import React from "react" + +function SmartLink({ + href, + target = "_self", + linkType = "internal", + children, + className, +}: { + href: string + target?: string + linkType?: "internal" | "external" + children: React.ReactNode + className?: string +}) { + // Internal link + if (linkType === "internal") { + return ( + + {children} + + ) + } + + // External link + if (linkType === "external") { + return ( + + {children} + + ) + } +} + +export default SmartLink diff --git a/components/shared/workCard/WorkCard.tsx b/components/shared/workCard/WorkCard.tsx index 99ff1b03..dc91e215 100644 --- a/components/shared/workCard/WorkCard.tsx +++ b/components/shared/workCard/WorkCard.tsx @@ -58,7 +58,7 @@ const WorkCard = ({ work }: WorkCardProps) => { return ( + href={resolveUrl({ routeParams: { work: "work", wid: work.workId } })}>
{ const value = encodeURIComponent(params[key]) - return acc.replace(`:${key}`, value) + return `${acc}/${value}` }, route) } @@ -33,28 +32,25 @@ export function buildRoute({ type ResolveUrlOptions = | { - type: "work" - routeParams?: { wid: number | string } + routeParams?: { work: "work"; wid: number | string } queryParams?: QueryParams } | { - type: "search" - routeParams?: undefined + routeParams?: { work: "work"; ":wid": number | string; read: "read" } + queryParams?: QueryParams + } + | { + routeParams?: { search: "search" } queryParams?: QueryParams } -export const resolveUrl = ({ type, routeParams, queryParams }: ResolveUrlOptions) => { - switch (type as ResolveUrlOptions["type"]) { - case "work": - if (!routeParams?.wid) return "" - return buildRoute({ - route: "/work/:wid", - params: { wid: routeParams.wid }, - query: queryParams, - }) - case "search": - return buildRoute({ route: "/search", query: queryParams }) - default: - return "" +export const resolveUrl = ({ routeParams, queryParams }: ResolveUrlOptions) => { + if (!routeParams) { + throw new Error("routeParams is required") } + + return buildRoute({ + params: routeParams, + query: queryParams, + }) } diff --git a/lib/shadcn/utils.ts b/lib/shadcn/utils.ts new file mode 100644 index 00000000..06651c7d --- /dev/null +++ b/lib/shadcn/utils.ts @@ -0,0 +1,3 @@ +import { cn as classNames } from "../helpers/helper.cn" + +export const cn = classNames diff --git a/package.json b/package.json index a8c75a03..83149eb4 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,13 @@ "dependencies": { "@next/env": "^14.2.15", "@radix-ui/react-accordion": "^1.2.1", - "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.1.0", "@redux-devtools/extension": "^3.3.0", "@tanstack/react-query": "^5.59.0", "@types/lodash": "^4.17.12", - "@xstate/react": "^4.1.3", "@uidotdev/usehooks": "^2.4.1", + "@xstate/react": "^4.1.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", diff --git a/styles/globals.css b/styles/globals.css index 7801c930..b5c93f7b 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -72,6 +72,7 @@ --blue-overlay: hsla(238, 54%, 89%, 0.09); --border: hsla(48, 4.4%, 77.8%, 1); + --reader-grey: hsla(0, 0%, 94.1%, 1); --shadow-1: hsla(0, 0%, 0%, 0.5); --shadow-2: hsla(0, 0%, 0%, 0.15); diff --git a/tailwind.config.ts b/tailwind.config.ts index 7edfe08f..71ee037e 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -192,6 +192,7 @@ export const extendedTheme = { }, foreground: "var(--foreground)", border: "var(--border)", + "reader-grey": "var(--reader-grey)", content: { pink: "var(--content-pink)", blue: "var(--content-blue)", From f92c02b11ab42a17e419ea43990f2f9365522ce0 Mon Sep 17 00:00:00 2001 From: thomasgross Date: Fri, 22 Nov 2024 11:26:11 +0100 Subject: [PATCH 2/7] fix: failing ci-checks --- __tests__/url.test.ts | 7 +++---- components/pages/workPageLayout/WorkPageLayout.tsx | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/__tests__/url.test.ts b/__tests__/url.test.ts index eb160586..e8b97a35 100644 --- a/__tests__/url.test.ts +++ b/__tests__/url.test.ts @@ -3,20 +3,19 @@ import { expect, test } from "vitest" import { resolveUrl } from "../lib/helpers/helper.routes" test("That resolveUrl can return a work url", async () => { - const workUrl = resolveUrl({ type: "work", routeParams: { wid: 123 } }) + const workUrl = resolveUrl({ routeParams: { work: "work", wid: 123 } }) expect(workUrl).toBe("/work/123") }) test("That resolveUrl can return a work url with a manifestation type", async () => { const workUrl = resolveUrl({ - type: "work", - routeParams: { wid: 123 }, + routeParams: { work: "work", wid: 123 }, queryParams: { audio: "true" }, }) expect(workUrl).toBe("/work/123?audio=true") }) test("That resolveUrl can return a search url", async () => { - const workUrl = resolveUrl({ type: "search", queryParams: { q: "test" } }) + const workUrl = resolveUrl({ routeParams: { search: "search" }, queryParams: { q: "test" } }) expect(workUrl).toBe("/search?q=test") }) diff --git a/components/pages/workPageLayout/WorkPageLayout.tsx b/components/pages/workPageLayout/WorkPageLayout.tsx index 48ec87b5..b9d9e8e4 100644 --- a/components/pages/workPageLayout/WorkPageLayout.tsx +++ b/components/pages/workPageLayout/WorkPageLayout.tsx @@ -25,7 +25,7 @@ function WorkPageLayout({ wid }: { wid: string }) { return (
{identifier && ( - )} diff --git a/components/shared/publizonReader/PublizonReader.tsx b/components/shared/publizonReader/PublizonReader.tsx index 45eb891d..2dcde1b0 100644 --- a/components/shared/publizonReader/PublizonReader.tsx +++ b/components/shared/publizonReader/PublizonReader.tsx @@ -9,15 +9,17 @@ type ReaderType = | { type: "demo" identifier: string + orderId?: never onBackCallback: () => void } | { type: "rent" + identifier?: never orderId: string onBackCallback: () => void } -const Reader = (props: ReaderType) => { +const Reader = ({ type, onBackCallback, identifier, orderId }: ReaderType) => { useEffect(() => { readerAssets.forEach(appendAsset) @@ -26,7 +28,7 @@ const Reader = (props: ReaderType) => { // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-shadow window.onReaderBackCallback = () => { - props.onBackCallback() + onBackCallback() } return () => { @@ -35,16 +37,16 @@ const Reader = (props: ReaderType) => { delete window.onReaderBackCallback readerAssets.forEach(removeAsset) } - }, [props]) + }, []) - if (props.type === "rent") { + if (type === "rent") { return (
-

orderId: {props.orderId}

+

orderId: {orderId}

{ ) } - if (props.type === "demo") { + if (type === "demo") { return (
{ +export const appendAsset = ({ src, type }: TAssetType) => { if (type === "script") { const scriptElement = document.createElement("script") scriptElement.src = src @@ -40,7 +40,7 @@ export const appendAsset = ({ src, type }: AssetType) => { } } -export const removeAsset = ({ src, type }: AssetType) => { +export const removeAsset = ({ src, type }: TAssetType) => { if (type === "script") { const scriptElement = document.querySelector(`script[src="${src}"]`) scriptElement?.remove() From a58d5c95ee598221d78d0937047c438bddeee1bd Mon Sep 17 00:00:00 2001 From: thomasgross Date: Mon, 25 Nov 2024 13:00:25 +0100 Subject: [PATCH 6/7] fix: make test and url helper use goConfig app url --- __tests__/url.test.ts | 11 ++++++++--- lib/helpers/helper.routes.ts | 18 ++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/__tests__/url.test.ts b/__tests__/url.test.ts index e8b97a35..aa1f397d 100644 --- a/__tests__/url.test.ts +++ b/__tests__/url.test.ts @@ -1,10 +1,13 @@ import { expect, test } from "vitest" +import goConfig from "@/lib/config/config" + import { resolveUrl } from "../lib/helpers/helper.routes" test("That resolveUrl can return a work url", async () => { const workUrl = resolveUrl({ routeParams: { work: "work", wid: 123 } }) - expect(workUrl).toBe("/work/123") + const appUrl = goConfig("app.url") + expect(workUrl).toBe(`${appUrl}/work/123`) }) test("That resolveUrl can return a work url with a manifestation type", async () => { @@ -12,10 +15,12 @@ test("That resolveUrl can return a work url with a manifestation type", async () routeParams: { work: "work", wid: 123 }, queryParams: { audio: "true" }, }) - expect(workUrl).toBe("/work/123?audio=true") + const appUrl = goConfig("app.url") + expect(workUrl).toBe(`${appUrl}/work/123?audio=true`) }) test("That resolveUrl can return a search url", async () => { const workUrl = resolveUrl({ routeParams: { search: "search" }, queryParams: { q: "test" } }) - expect(workUrl).toBe("/search?q=test") + const appUrl = goConfig("app.url") + expect(workUrl).toBe(`${appUrl}/search?q=test`) }) diff --git a/lib/helpers/helper.routes.ts b/lib/helpers/helper.routes.ts index 5273126d..2fccdd7b 100644 --- a/lib/helpers/helper.routes.ts +++ b/lib/helpers/helper.routes.ts @@ -1,3 +1,5 @@ +import goConfig from "../config/config" + type RouteParams = { [key: string]: string | number } type QueryParams = { [key: string]: string | number } @@ -8,26 +10,22 @@ export function buildRoute({ params?: RouteParams query?: QueryParams }): string { - let route = "" + let routeParams = "" if (params) { - route = Object.keys(params).reduce((acc, key) => { + routeParams = Object.keys(params).reduce((acc, key) => { const value = encodeURIComponent(params[key]) return `${acc}/${value}` - }, route) + }, routeParams) } - const queryParams = new URLSearchParams() + const url = new URL(routeParams, goConfig("app.url")) if (query) { Object.keys(query).forEach(key => { - queryParams.append(key, query[key].toString()) + url.searchParams.append(key, query[key].toString()) }) } - if (queryParams.toString()) { - return `${route}?${queryParams}` - } - - return route + return url.toString() } type ResolveUrlOptions = From de83dc968bdfa7bf4ee40a59b92ad40bc565a0cb Mon Sep 17 00:00:00 2001 From: thomasgross Date: Wed, 27 Nov 2024 15:04:18 +0100 Subject: [PATCH 7/7] feat: add TODO in WorkPageLayout --- components/pages/workPageLayout/WorkPageLayout.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/pages/workPageLayout/WorkPageLayout.tsx b/components/pages/workPageLayout/WorkPageLayout.tsx index 665fa1e4..9854bfd4 100644 --- a/components/pages/workPageLayout/WorkPageLayout.tsx +++ b/components/pages/workPageLayout/WorkPageLayout.tsx @@ -14,6 +14,7 @@ function WorkPageLayout({ wid }: { wid: string }) { queryFn: useGetMaterialQuery.fetcher({ wid }), }) + // TODO: Handle potential error states const manifestations = data?.work?.manifestations.all const identifier = manifestations?.[0].identifiers?.[0].value || ""