From 2ebb942d9f62cad5f462412b3f12ed7797ca4100 Mon Sep 17 00:00:00 2001 From: Oksamies Date: Tue, 2 Sep 2025 17:58:05 +0300 Subject: [PATCH] 7.0.3 fixes Fix sentry dsn client key not being in window env Fix auth link building Refactor Tabs to be more stable and simple Fix package and wiki pages data loading on demand --- .../Navigation/Navigation.tsx | 46 +- apps/cyberstorm-remix/app/entry.client.tsx | 11 +- apps/cyberstorm-remix/app/middlewares.ts | 29 -- .../cyberstorm-remix/app/p/packageListing.tsx | 414 ++++++++++-------- .../cyberstorm-remix/app/p/tabs/Wiki/Wiki.tsx | 178 ++++---- .../app/p/tabs/Wiki/WikiContent.tsx | 47 +- .../app/p/tabs/Wiki/WikiFirstPage.tsx | 140 ++++-- .../app/p/tabs/Wiki/WikiNewPage.tsx | 69 ++- .../app/p/tabs/Wiki/WikiPage.tsx | 182 +++++--- .../app/p/tabs/Wiki/WikiPageEdit.tsx | 71 ++- .../app/settings/teams/team/teamSettings.tsx | 111 +++-- .../settings/user/Connections/Connections.tsx | 13 +- .../app/settings/user/Settings.tsx | 57 +-- .../cyberstorm/utils/ThunderstoreAuth.tsx | 16 +- .../cyberstorm/utils/typeChecks.ts | 5 + .../src/components/Tabs/Tabs.css | 140 +++--- .../src/newComponents/Tabs/Tabs.css | 6 +- .../src/newComponents/Tabs/Tabs.tsx | 56 +-- 18 files changed, 876 insertions(+), 715 deletions(-) delete mode 100644 apps/cyberstorm-remix/app/middlewares.ts diff --git a/apps/cyberstorm-remix/app/commonComponents/Navigation/Navigation.tsx b/apps/cyberstorm-remix/app/commonComponents/Navigation/Navigation.tsx index 81843df51..b3b0d5dae 100644 --- a/apps/cyberstorm-remix/app/commonComponents/Navigation/Navigation.tsx +++ b/apps/cyberstorm-remix/app/commonComponents/Navigation/Navigation.tsx @@ -39,6 +39,7 @@ import { faDiscord, faGithub } from "@fortawesome/free-brands-svg-icons"; import { classnames } from "@thunderstore/cyberstorm/src/utils/utils"; import { buildAuthLoginUrl } from "cyberstorm/utils/ThunderstoreAuth"; import { faArrowUpRight } from "@fortawesome/pro-solid-svg-icons"; +import { getPublicEnvVariables } from "cyberstorm/security/publicEnvVariables"; export function Navigation(props: { // hydrationCheck: boolean; @@ -226,6 +227,11 @@ export function Navigation(props: { } export function DesktopLoginPopover() { + const publicEnvVariables = getPublicEnvVariables([ + "VITE_AUTH_BASE_URL", + "VITE_AUTH_RETURN_URL", + ]); + return ( @@ -291,7 +301,11 @@ export function DesktopLoginPopover() { @@ -301,7 +315,11 @@ export function DesktopLoginPopover() { @@ -520,6 +538,10 @@ export function MobileUserPopoverContent(props: { }) { const { user, domain } = props; const avatar = user?.connections.find((c) => c.avatar !== null)?.avatar; + const publicEnvVariables = getPublicEnvVariables([ + "VITE_AUTH_BASE_URL", + "VITE_AUTH_RETURN_URL", + ]); return ( @@ -601,7 +623,11 @@ export function MobileUserPopoverContent(props: {
@@ -611,7 +637,11 @@ export function MobileUserPopoverContent(props: { @@ -621,7 +651,11 @@ export function MobileUserPopoverContent(props: { diff --git a/apps/cyberstorm-remix/app/entry.client.tsx b/apps/cyberstorm-remix/app/entry.client.tsx index 68d9507a1..9ccbaf1bc 100644 --- a/apps/cyberstorm-remix/app/entry.client.tsx +++ b/apps/cyberstorm-remix/app/entry.client.tsx @@ -5,15 +5,14 @@ import { hydrateRoot } from "react-dom/client"; import { useLocation, useMatches } from "react-router"; import * as Sentry from "@sentry/remix"; import { useEffect } from "react"; -// import { DapperTs } from "@thunderstore/dapper-ts"; +import { getPublicEnvVariables } from "cyberstorm/security/publicEnvVariables"; -// INIT DAPPER -// window.Dapper = new DapperTs(getConfig, () => { -// clearSession(); -// }); +const sentryClientDSN = getPublicEnvVariables([ + "VITE_CLIENT_SENTRY_DSN", +]).VITE_CLIENT_SENTRY_DSN; Sentry.init({ - dsn: import.meta.env.VITE_CLIENT_SENTRY_DSN, + dsn: sentryClientDSN, integrations: [ Sentry.browserTracingIntegration({ useEffect, diff --git a/apps/cyberstorm-remix/app/middlewares.ts b/apps/cyberstorm-remix/app/middlewares.ts deleted file mode 100644 index 602d7c05b..000000000 --- a/apps/cyberstorm-remix/app/middlewares.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - ContextInterface, - getSessionContext, -} from "@thunderstore/ts-api-react/src/SessionContext"; -import { - unstable_createContext, - unstable_RouterContextProvider, -} from "react-router"; - -// MIDDLEWARES START -const sessionContext = unstable_createContext( - getSessionContext(import.meta.env.VITE_API_URL) -); - -// @ts-expect-error No good typing for middleware context yet -export const sessionClientMiddleware = async ({ context }) => { - const session = getSessionContext(import.meta.env.VITE_API_URL); - - context.set(sessionContext, session); -}; - -export function getSessionTools(context: unstable_RouterContextProvider) { - return context.get(sessionContext); -} - -// export const unstable_middleware = [sessionMiddleware]; -// export const unstable_clientMiddleware = [sessionClientMiddleware]; - -// MIDDLEWARES END diff --git a/apps/cyberstorm-remix/app/p/packageListing.tsx b/apps/cyberstorm-remix/app/p/packageListing.tsx index 7996cdb9e..f1c9dbfa6 100644 --- a/apps/cyberstorm-remix/app/p/packageListing.tsx +++ b/apps/cyberstorm-remix/app/p/packageListing.tsx @@ -75,12 +75,16 @@ import { getPublicEnvVariables, getSessionTools, } from "cyberstorm/security/publicEnvVariables"; -import { getPackagePermissions } from "@thunderstore/dapper-ts/src/methods/package"; +import { + getPackagePermissions, + getPackageWiki, +} from "@thunderstore/dapper-ts/src/methods/package"; import { useToast } from "@thunderstore/cyberstorm/src/newComponents/Toast/Provider"; import { ApiAction } from "@thunderstore/ts-api-react-actions"; import { TagVariants } from "@thunderstore/cyberstorm-theme/src/components"; import { SelectOption } from "@thunderstore/cyberstorm/src/newComponents/Select/Select"; import { useStrongForm } from "cyberstorm/utils/StrongForm/useStrongForm"; +import { isPromise } from "cyberstorm/utils/typeChecks"; export const meta: MetaFunction = ({ data, location }) => { return [ @@ -132,33 +136,41 @@ export const meta: MetaFunction = ({ data, location }) => { export async function loader({ params }: LoaderFunctionArgs) { if (params.communityId && params.namespaceId && params.packageId) { - try { - const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); - const dapper = new DapperTs(() => { - return { - apiHost: publicEnvVariables.VITE_API_URL, - sessionId: undefined, - }; - }); + const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]); + const dapper = new DapperTs(() => { return { - community: await dapper.getCommunity(params.communityId), - communityFilters: await dapper.getCommunityFilters(params.communityId), - listing: await dapper.getPackageListingDetails( - params.communityId, - params.namespaceId, - params.packageId - ), - team: await dapper.getTeamDetails(params.namespaceId), - permissions: undefined, + apiHost: publicEnvVariables.VITE_API_URL, + sessionId: undefined, }; + }); + + let wiki: Awaited> | undefined; + + try { + wiki = await dapper.getPackageWiki(params.namespaceId, params.packageId); } catch (error) { if (error instanceof ApiError) { - throw new Response("Package not found", { status: 404 }); - } else { - // REMIX TODO: Add sentry - throw error; + if (error.response.status === 404) { + wiki = undefined; + } else { + wiki = undefined; + console.error("Error fetching package wiki:", error); + } } } + + return { + community: await dapper.getCommunity(params.communityId), + communityFilters: await dapper.getCommunityFilters(params.communityId), + listing: await dapper.getPackageListingDetails( + params.communityId, + params.namespaceId, + params.packageId + ), + team: await dapper.getTeamDetails(params.namespaceId), + permissions: undefined, + wiki: wiki, + }; } throw new Response("Package not found", { status: 404 }); } @@ -166,52 +178,43 @@ export async function loader({ params }: LoaderFunctionArgs) { // TODO: Needs to check if package is available for the logged in user export async function clientLoader({ params }: LoaderFunctionArgs) { if (params.communityId && params.namespaceId && params.packageId) { - try { - const tools = getSessionTools(); - const dapper = new DapperTs(() => { - return { - apiHost: tools?.getConfig().apiHost, - sessionId: tools?.getConfig().sessionId, - }; - }); - - // We do some trickery right here to prevent unnecessary request when the user is not logged in - let permissionsPromise = undefined; - const cu = await tools.getSessionCurrentUser(); - if (cu.username) { - const wrapperPromise = - Promise.withResolvers< - Awaited> - >(); - dapper - .getPackagePermissions( - params.communityId, - params.namespaceId, - params.packageId - ) - .then(wrapperPromise.resolve, wrapperPromise.reject); - permissionsPromise = wrapperPromise.promise; - } - + const tools = getSessionTools(); + const dapper = new DapperTs(() => { return { - community: await dapper.getCommunity(params.communityId), - communityFilters: await dapper.getCommunityFilters(params.communityId), - listing: await dapper.getPackageListingDetails( + apiHost: tools?.getConfig().apiHost, + sessionId: tools?.getConfig().sessionId, + }; + }); + + // We do some trickery right here to prevent unnecessary request when the user is not logged in + let permissionsPromise = undefined; + const cu = await tools.getSessionCurrentUser(); + if (cu.username) { + const wrapperPromise = + Promise.withResolvers< + Awaited> + >(); + dapper + .getPackagePermissions( params.communityId, params.namespaceId, params.packageId - ), - team: await dapper.getTeamDetails(params.namespaceId), - permissions: permissionsPromise, - }; - } catch (error) { - if (error instanceof ApiError) { - throw new Response("Package not found", { status: 404 }); - } else { - // REMIX TODO: Add sentry - throw error; - } + ) + .then(wrapperPromise.resolve, wrapperPromise.reject); + permissionsPromise = wrapperPromise.promise; } + return { + community: await dapper.getCommunity(params.communityId), + communityFilters: await dapper.getCommunityFilters(params.communityId), + listing: await dapper.getPackageListingDetails( + params.communityId, + params.namespaceId, + params.packageId + ), + team: await dapper.getTeamDetails(params.namespaceId), + permissions: permissionsPromise, + wiki: dapper.getPackageWiki(params.namespaceId, params.packageId), + }; } throw new Response("Package not found", { status: 404 }); } @@ -219,7 +222,7 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { clientLoader.hydrate = true; export default function PackageListing() { - const { community, listing, team, permissions } = useLoaderData< + const { community, listing, team, permissions, wiki } = useLoaderData< typeof loader | typeof clientLoader >(); @@ -561,6 +564,41 @@ export default function PackageListing() { ); + const canAccessWikiTabPromise = Promise.withResolvers(); + if (wiki && isPromise(wiki)) { + wiki + .then(async (a) => { + if (a.pages.length > 0) { + canAccessWikiTabPromise.resolve(true); + } else { + if ((await permissions)?.permissions.can_manage) { + canAccessWikiTabPromise.resolve(true); + } else { + canAccessWikiTabPromise.resolve(false); + } + } + }) + .catch(async () => { + if ((await permissions)?.permissions.can_manage) { + canAccessWikiTabPromise.resolve(true); + } else { + canAccessWikiTabPromise.resolve(false); + } + }); + } else { + if (permissions) { + permissions.then((a) => { + if (a?.permissions.can_manage) { + canAccessWikiTabPromise.resolve(true); + } else { + canAccessWikiTabPromise.resolve(false); + } + }); + } else { + canAccessWikiTabPromise.resolve(false); + } + } + return ( <>
@@ -575,8 +613,8 @@ export default function PackageListing() {
- }> - }> + + {(resolvedValue) => resolvedValue ? (
@@ -694,119 +732,139 @@ export default function PackageListing() { {actions}
- Description, - } as React.ComponentPropsWithRef, - current: currentTab === "details", - key: "description", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "PackageRequired", - community: listing.community_identifier, - namespace: listing.namespace, - package: listing.name, - "aria-current": currentTab === "required", - children: <>Required ({listing.dependency_count}), - } as React.ComponentPropsWithRef, - current: currentTab === "required", - key: "required", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "PackageWiki", - community: listing.community_identifier, - namespace: listing.namespace, - package: listing.name, - "aria-current": currentTab === "wiki", - children: <>Wiki, - } as React.ComponentPropsWithRef, - current: currentTab === "wiki", - key: "wiki", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "PackageChangelog", - community: listing.community_identifier, - namespace: listing.namespace, - package: listing.name, - "aria-current": currentTab === "changelog", - children: <>Changelog, - disabled: !listing.has_changelog, - } as React.ComponentPropsWithRef, - current: currentTab === "changelog", - key: "changelog", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "PackageVersions", - community: listing.community_identifier, - namespace: listing.namespace, - package: listing.name, - "aria-current": currentTab === "versions", - children: <>Versions, - } as React.ComponentPropsWithRef, - current: currentTab === "versions", - key: "versions", - // TODO: Version count field needs to be added to the endpoint - // numberSlateValue: listing.versionCount, - }, - // TODO: Once Analysis page is ready, enable it - // { - // itemProps: { - // key: "source", - // primitiveType: "cyberstormLink", - // linkId: "PackageSource", - // community: listing.community_identifier, - // namespace: listing.namespace, - // package: listing.name, - // "aria-current": currentTab === "source", - // children: <>Analysis, - // }, - // current: currentTab === "source", - // }, - { - itemProps: { - href: `${domain}/c/${listing.community_identifier}/p/${listing.namespace}/${listing.name}/source`, - primitiveType: "link", - "aria-current": currentTab === "source", - children: ( - <> - Analysis{" "} - - - - - ), - } as React.ComponentPropsWithRef, - current: currentTab === "source", - key: "source", - }, - ]} - renderTabItem={(key, itemProps, numberSlate) => { - const { children, ...fItemProps } = - itemProps as React.ComponentPropsWithRef; - return ( - - {children} - {numberSlate} + + + Details + + + Required ({listing.dependency_count}) + + + Wiki - ); - }} - /> + } + > + + Wiki + + } + > + {(resolvedValue) => { + return ( + + Wiki + + ); + }} + +
+ + Changelog + + + Versions + + + Analysis{" "} + + + + +
diff --git a/apps/cyberstorm-remix/app/p/tabs/Wiki/Wiki.tsx b/apps/cyberstorm-remix/app/p/tabs/Wiki/Wiki.tsx index bdb41d932..7ea0383ec 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Wiki/Wiki.tsx +++ b/apps/cyberstorm-remix/app/p/tabs/Wiki/Wiki.tsx @@ -1,6 +1,11 @@ import "./Wiki.css"; -import { LoaderFunctionArgs, Outlet, useOutletContext } from "react-router"; +import { + Await, + LoaderFunctionArgs, + Outlet, + useOutletContext, +} from "react-router"; import { useLoaderData } from "react-router"; import { DapperTs } from "@thunderstore/dapper-ts"; import { @@ -11,6 +16,9 @@ import { NewButton, NewIcon } from "@thunderstore/cyberstorm"; import { faPlus } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { OutletContextShape } from "~/root"; +import { Suspense } from "react"; +import { ApiError } from "../../../../../../packages/thunderstore-api/src"; +import { getPackageWiki } from "@thunderstore/dapper-ts/src/methods/package"; export async function loader({ params }: LoaderFunctionArgs) { if (params.communityId && params.namespaceId && params.packageId) { @@ -21,10 +29,22 @@ export async function loader({ params }: LoaderFunctionArgs) { sessionId: undefined, }; }); - const wiki = await dapper.getPackageWiki( - params.namespaceId, - params.packageId - ); + + let wiki: Awaited> | undefined; + + try { + wiki = await dapper.getPackageWiki(params.namespaceId, params.packageId); + } catch (error) { + if (error instanceof ApiError) { + if (error.response.status === 404) { + wiki = undefined; + } else { + wiki = undefined; + console.error("Error fetching package wiki:", error); + } + } + } + return { wiki: wiki, communityId: params.communityId, @@ -48,12 +68,9 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { }; }); - const wiki = await dapper.getPackageWiki( - params.namespaceId, - params.packageId - ); + const wiki = dapper.getPackageWiki(params.namespaceId, params.packageId); - const permissions = await dapper.getPackagePermissions( + const permissions = dapper.getPackagePermissions( params.communityId, params.namespaceId, params.packageId @@ -81,77 +98,90 @@ export default function Wiki() { return (
- {permissions?.permissions.can_manage ? ( -
- - - - - New Page - -
- ) : null} -
-
- {wiki.pages.map((page, index) => { - if (!slug && index === 0) { - return ( + + + {(resolvedValue) => + resolvedValue?.permissions.can_manage ? ( +
- {page.title} + + + + New Page - ); - } - if (page.slug === slug) { - return ( - - {page.title} - - ); - } - return ( - - {page.title} - - ); - })} +
+ ) : null + } +
+
+
+
+ Loading...
}> + }> + {(resolvedValue) => + resolvedValue && + resolvedValue.pages.map((page, index) => { + if (!slug && index === 0) { + return ( + + {page.title} + + ); + } + if (page.slug === slug) { + return ( + + {page.title} + + ); + } + return ( + + {page.title} + + ); + }) + } + +
diff --git a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiContent.tsx b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiContent.tsx index bc2736125..42be7a7a3 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiContent.tsx +++ b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiContent.tsx @@ -9,8 +9,9 @@ import { faEdit, } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { memo } from "react"; +import { memo, Suspense } from "react"; import { Markdown } from "~/commonComponents/Markdown/Markdown"; +import { Await } from "react-router"; interface WikiContentProps { page: PackageWikiPageResponseData; @@ -19,7 +20,7 @@ interface WikiContentProps { packageId: string; previousPage?: string; nextPage?: string; - canManage?: boolean; + canManage?: Promise; } export const WikiContent = memo(function WikiContent({ @@ -48,24 +49,30 @@ export const WikiContent = memo(function WikiContent({
- {canManage ? ( -
- - - - - Edit - -
- ) : null} + + + {(resolvedValue) => + resolvedValue ? ( +
+ + + + + Edit + +
+ ) : null + } +
+
diff --git a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiFirstPage.tsx b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiFirstPage.tsx index b1136ee8b..2a579c580 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiFirstPage.tsx +++ b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiFirstPage.tsx @@ -8,6 +8,21 @@ import { getSessionTools, } from "cyberstorm/security/publicEnvVariables"; import { WikiContent } from "./WikiContent"; +import { isApiError } from "../../../../../../packages/thunderstore-api/src"; +import { + getPackagePermissions, + getPackageWiki, + getPackageWikiPage, +} from "@thunderstore/dapper-ts/src/methods/package"; + +type ResultType = { + wiki: Awaited> | undefined; + firstPage: Awaited> | undefined; + communityId: string; + namespaceId: string; + packageId: string; + permissions: ReturnType | undefined; +}; export async function loader({ params }: LoaderFunctionArgs) { if (params.communityId && params.namespaceId && params.packageId) { @@ -18,19 +33,49 @@ export async function loader({ params }: LoaderFunctionArgs) { sessionId: undefined, }; }); - const wiki = await dapper.getPackageWiki( - params.namespaceId, - params.packageId - ); - const firstPage = await dapper.getPackageWikiPage(wiki.pages[0].id); - return { - wiki: wiki, - firstPage: firstPage, + let result: ResultType = { + wiki: undefined, + firstPage: undefined, communityId: params.communityId, namespaceId: params.namespaceId, packageId: params.packageId, permissions: undefined, }; + + try { + const wiki = await dapper.getPackageWiki( + params.namespaceId, + params.packageId + ); + const firstPage = await dapper.getPackageWikiPage(wiki.pages[0].id); + result = { + wiki: wiki, + firstPage: firstPage, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: undefined, + }; + } catch (error) { + if (isApiError(error)) { + // There is no wiki or the User does not have permission to view the wiki, return empty wiki and undefined firstPage + if (error.response.status === 404) { + result = { + wiki: undefined, + firstPage: undefined, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: undefined, + }; + } else { + throw error; + } + } else { + throw error; + } + } + return result; } else { throw new Error("Namespace ID or Package ID is missing"); } @@ -46,43 +91,78 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { }; }); - const wiki = await dapper.getPackageWiki( - params.namespaceId, - params.packageId - ); - const firstPage = await dapper.getPackageWikiPage(wiki.pages[0].id); - - const permissions = await dapper.getPackagePermissions( + const permissions = dapper.getPackagePermissions( params.communityId, params.namespaceId, params.packageId ); - return { - wiki: wiki, - firstPage: firstPage, + + let result: ResultType = { + wiki: undefined, + firstPage: undefined, communityId: params.communityId, namespaceId: params.namespaceId, packageId: params.packageId, permissions: permissions, }; + + try { + const wiki = await dapper.getPackageWiki( + params.namespaceId, + params.packageId + ); + const firstPage = await dapper.getPackageWikiPage(wiki.pages[0].id); + result = { + wiki: wiki, + firstPage: firstPage, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: permissions, + }; + } catch (error) { + if (isApiError(error)) { + // There is no wiki or the User does not have permission to view the wiki, return empty wiki and undefined firstPage + if (error.response.status === 404) { + result = { + wiki: undefined, + firstPage: undefined, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: permissions, + }; + } else { + throw error; + } + } else { + throw error; + } + } + return result; } else { throw new Error("Namespace ID or Package ID is missing"); } } -export default function Wiki() { +export default function WikiFirstPage() { const { wiki, firstPage, communityId, namespaceId, packageId, permissions } = useLoaderData(); - return ( - - ); + if (wiki && firstPage) { + return ( + 1 ? wiki.pages[1].slug : undefined} + canManage={permissions?.then((perms) => + typeof perms === "undefined" ? false : perms.permissions.can_manage + )} + /> + ); + } + return <>There are no wiki pages available.; } diff --git a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiNewPage.tsx b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiNewPage.tsx index 21765ce5a..b31e04b96 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiNewPage.tsx +++ b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiNewPage.tsx @@ -47,10 +47,6 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { } } -type TabsButtonType = React.ButtonHTMLAttributes & { - rootClasses?: string; -}; - export default function Wiki() { const { communityId, namespaceId, packageId } = useLoaderData< typeof loader | typeof clientLoader @@ -144,45 +140,32 @@ export default function Wiki() {
- setSelectedTab("write"), - "aria-current": selectedTab === "write", - children: <>Write, - } as TabsButtonType, - current: selectedTab === "write", - key: "write", - }, - { - itemProps: { - onClick: () => setSelectedTab("preview"), - "aria-current": selectedTab === "preview", - children: <>Preview, - } as TabsButtonType, - current: selectedTab === "preview", - key: "preview", - }, - ]} - renderTabItem={(key, itemProps) => { - const { children, ...fItemProps } = itemProps as TabsButtonType; - - return ( - - ); - }} - /> + + + + {selectedTab === "write" ? ( <> > | undefined; + page: Awaited> | undefined; + communityId: string; + namespaceId: string; + packageId: string; + permissions: ReturnType | undefined; +}; export async function loader({ params }: LoaderFunctionArgs) { if ( @@ -23,40 +38,50 @@ export async function loader({ params }: LoaderFunctionArgs) { sessionId: undefined, }; }); - const wiki = await dapper.getPackageWiki( - params.namespaceId, - params.packageId - ); - const pageId = wiki.pages.find((p) => p.slug === params.slug)?.id; - if (!pageId) { - throw new Error("Page not found"); - } - const page = await dapper.getPackageWikiPage(pageId); - const currentPageIndex = wiki.pages.findIndex((p) => p.id === page.id); - let previousPage = undefined; - let nextPage = undefined; - - if (currentPageIndex === 0) { - previousPage = undefined; - } else { - previousPage = wiki.pages[currentPageIndex - 1]?.slug; - } - - if (currentPageIndex === wiki.pages.length) { - nextPage = undefined; - } else { - nextPage = wiki.pages[currentPageIndex + 1]?.slug; - } - - return { - page: page, + let result: ResultType = { + wiki: undefined, + page: undefined, communityId: params.communityId, namespaceId: params.namespaceId, packageId: params.packageId, - previousPage: previousPage, - nextPage: nextPage, + permissions: undefined, }; + + try { + const wiki = await dapper.getPackageWiki( + params.namespaceId, + params.packageId + ); + const page = await dapper.getPackageWikiPage(params.slug); + result = { + wiki: wiki, + page: page, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: undefined, + }; + } catch (error) { + if (isApiError(error)) { + // There is no wiki or the User does not have permission to view the wiki, return empty wiki and undefined firstPage + if (error.response.status === 404) { + result = { + wiki: undefined, + page: undefined, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: undefined, + }; + } else { + throw error; + } + } else { + throw error; + } + } + return result; } else { throw new Error("Namespace ID or Package ID is missing"); } @@ -76,15 +101,67 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { sessionId: tools?.getConfig().sessionId, }; }); - const wiki = await dapper.getPackageWiki( + + const permissions = dapper.getPackagePermissions( + params.communityId, params.namespaceId, params.packageId ); - const pageId = wiki.pages.find((p) => p.slug === params.slug)?.id; - if (!pageId) { - throw new Error("Page not found"); + + let result: ResultType = { + wiki: undefined, + page: undefined, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: permissions, + }; + + try { + const wiki = await dapper.getPackageWiki( + params.namespaceId, + params.packageId + ); + const page = await dapper.getPackageWikiPage(params.slug); + result = { + wiki: wiki, + page: page, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: permissions, + }; + } catch (error) { + if (isApiError(error)) { + // There is no wiki or the User does not have permission to view the wiki, return empty wiki and undefined firstPage + if (error.response.status === 404) { + result = { + wiki: undefined, + page: undefined, + communityId: params.communityId, + namespaceId: params.namespaceId, + packageId: params.packageId, + permissions: permissions, + }; + } else { + throw error; + } + } else { + throw error; + } } - const page = await dapper.getPackageWikiPage(pageId); + return result; + } else { + throw new Error("Namespace ID or Package ID is missing"); + } +} + +export default function WikiPage() { + const { wiki, page, communityId, namespaceId, packageId } = useLoaderData< + typeof loader | typeof clientLoader + >(); + + if (wiki && page) { const currentPageIndex = wiki.pages.findIndex((p) => p.id === page.id); let previousPage = undefined; @@ -102,31 +179,16 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { nextPage = wiki.pages[currentPageIndex + 1]?.slug; } - return { - page: page, - communityId: params.communityId, - namespaceId: params.namespaceId, - packageId: params.packageId, - previousPage: previousPage, - nextPage: nextPage, - }; - } else { - throw new Error("Namespace ID or Package ID is missing"); + return ( + + ); } -} - -export default function Wiki() { - const { page, communityId, namespaceId, packageId, previousPage, nextPage } = - useLoaderData(); - - return ( - - ); + return <>Wiki Page Not Found; } diff --git a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiPageEdit.tsx b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiPageEdit.tsx index 085c944e9..fa60cdd1a 100644 --- a/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiPageEdit.tsx +++ b/apps/cyberstorm-remix/app/p/tabs/Wiki/WikiPageEdit.tsx @@ -104,11 +104,7 @@ export async function clientLoader({ params }: LoaderFunctionArgs) { } } -type TabsButtonType = React.ButtonHTMLAttributes & { - rootClasses?: string; -}; - -export default function Wiki() { +export default function WikiEdit() { const { page, communityId, namespaceId, packageId } = useLoaderData< typeof loader | typeof clientLoader >(); @@ -239,45 +235,32 @@ export default function Wiki() {
- setSelectedTab("write"), - "aria-current": selectedTab === "write", - children: <>Write, - } as TabsButtonType, - current: selectedTab === "write", - key: "write", - }, - { - itemProps: { - onClick: () => setSelectedTab("preview"), - "aria-current": selectedTab === "preview", - children: <>Preview, - } as TabsButtonType, - current: selectedTab === "preview", - key: "preview", - }, - ]} - renderTabItem={(key, itemProps) => { - const { children, ...fItemProps } = itemProps as TabsButtonType; - - return ( - - ); - }} - /> + + + + {selectedTab === "write" ? ( <>
- Profile, - } as React.ComponentPropsWithRef, - key: "profile", - current: currentTab === "profile", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "TeamSettingsMembers", - team: team.name, - "aria-current": currentTab === "members", - children: <>Members, - } as React.ComponentPropsWithRef, - key: "members", - current: currentTab === "members", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "TeamSettingsServiceAccounts", - team: team.name, - "aria-current": currentTab === "service-accounts", - children: <>Service Accounts, - } as React.ComponentPropsWithRef, - key: "service-accounts", - current: currentTab === "service-accounts", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "TeamSettingsSettings", - team: team.name, - "aria-current": currentTab === "settings", - children: <>Settings, - } as React.ComponentPropsWithRef, - key: "settings", - current: currentTab === "settings", - }, - ]} - renderTabItem={(key, itemProps, numberSlate) => { - const { children, ...fItemProps } = - itemProps as React.ComponentPropsWithRef; - return ( - )} - > - {children} - {numberSlate} - - ); - }} - /> + + + Profile + + + Members + + + Service Accounts + + + Settings + +
diff --git a/apps/cyberstorm-remix/app/settings/user/Connections/Connections.tsx b/apps/cyberstorm-remix/app/settings/user/Connections/Connections.tsx index 18ee95fea..ac847ab7e 100644 --- a/apps/cyberstorm-remix/app/settings/user/Connections/Connections.tsx +++ b/apps/cyberstorm-remix/app/settings/user/Connections/Connections.tsx @@ -14,6 +14,7 @@ import { ReactElement } from "react"; import { OutletContextShape } from "~/root"; import { ApiAction } from "@thunderstore/ts-api-react-actions"; import { NotLoggedIn } from "~/commonComponents/NotLoggedIn/NotLoggedIn"; +import { getPublicEnvVariables } from "cyberstorm/security/publicEnvVariables"; type ProvidersType = { name: string; @@ -71,6 +72,11 @@ export default function Connections() { onSubmitError: onSubmitError, }); + const publicEnvVariables = getPublicEnvVariables([ + "VITE_AUTH_BASE_URL", + "VITE_AUTH_RETURN_URL", + ]); + return (
@@ -105,7 +111,12 @@ export default function Connections() { connection={outletContext.currentUser.connections?.find( (c) => c.provider.toLowerCase() === p.identifier )} - connectionLink={buildAuthLoginUrl({ type: p.identifier })} + connectionLink={buildAuthLoginUrl({ + type: p.identifier, + authBaseDomain: publicEnvVariables.VITE_AUTH_BASE_URL || "", + authReturnDomain: + publicEnvVariables.VITE_AUTH_RETURN_URL || "", + })} disconnectFunction={(p) => { if ( !outletContext.currentUser || diff --git a/apps/cyberstorm-remix/app/settings/user/Settings.tsx b/apps/cyberstorm-remix/app/settings/user/Settings.tsx index 7df3c1f16..5a598bdbc 100644 --- a/apps/cyberstorm-remix/app/settings/user/Settings.tsx +++ b/apps/cyberstorm-remix/app/settings/user/Settings.tsx @@ -57,39 +57,30 @@ export default function Community() { Settings
- Settings, - }, - current: currentTab === "settings", - key: "settings", - }, - { - itemProps: { - primitiveType: "cyberstormLink", - linkId: "SettingsAccount", - "aria-current": currentTab === "account", - children: <>Account, - }, - current: currentTab === "account", - key: "account", - }, - ]} - renderTabItem={(key, itemProps, numberSlate) => { - const { children, ...fItemProps } = itemProps; - return ( - - {children} - {numberSlate} - - ); - }} - /> + + + Settings + + + Account + +
diff --git a/apps/cyberstorm-remix/cyberstorm/utils/ThunderstoreAuth.tsx b/apps/cyberstorm-remix/cyberstorm/utils/ThunderstoreAuth.tsx index f92e6cbe5..3e47bdfe1 100644 --- a/apps/cyberstorm-remix/cyberstorm/utils/ThunderstoreAuth.tsx +++ b/apps/cyberstorm-remix/cyberstorm/utils/ThunderstoreAuth.tsx @@ -1,22 +1,14 @@ interface Props { type: "discord" | "github" | "overwolf"; + authBaseDomain: string; + authReturnDomain: string; nextUrl?: string; } export function buildAuthLoginUrl(props: Props) { - let domain: string; - let returnDomain: string; - if (import.meta.env.SSR) { - domain = process.env.VITE_SITE_URL || ""; - returnDomain = process.env.VITE_AUTH_RETURN_URL || ""; - } else { - domain = import.meta.env.VITE_SITE_URL; - returnDomain = import.meta.env.VITE_AUTH_RETURN_URL; - } - - return `${domain}/auth/login/${props.type}/${ + return `${props.authBaseDomain}/auth/login/${props.type}/${ props.nextUrl ? `?next=${encodeURIComponent(props.nextUrl)}` - : `?next=${encodeURIComponent(`${returnDomain}/communities/`)}` + : `?next=${encodeURIComponent(`${props.authReturnDomain}/communities/`)}` }`; } diff --git a/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts b/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts index 83c7db6dd..a6bbb0b83 100644 --- a/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts +++ b/apps/cyberstorm-remix/cyberstorm/utils/typeChecks.ts @@ -1,2 +1,7 @@ export const isRecord = (obj: unknown): obj is Record => obj instanceof Object; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isPromise(value: any): value is Promise { + return typeof value?.then === "function"; +} diff --git a/packages/cyberstorm-theme/src/components/Tabs/Tabs.css b/packages/cyberstorm-theme/src/components/Tabs/Tabs.css index 4104ab1cf..d1f29f42d 100644 --- a/packages/cyberstorm-theme/src/components/Tabs/Tabs.css +++ b/packages/cyberstorm-theme/src/components/Tabs/Tabs.css @@ -3,82 +3,94 @@ .tabs { --tabs-box-shadow-height: 3px; --tabs-box-shadow-color: var(--color-surface-5); - } - - /* Tabs Item */ - /* SIZES */ - .tabs__item:where(.tabs__item--size--medium) { - --tabs-item-height: 3.25rem; - --tabs-item-padding: 0.75rem 1rem; - --tabs-item-gap: 0.75rem; - --tabs-item-font-size: var(--font-size-body-lg); - --tabs-item-font-weight: 700; - --tabs-item-border-bottom-width: 3px; - } + /* Tabs Item */ - /* VARIANTS */ - .tabs__item:where(.tabs__item--variant--default) { - --tabs-item-color: var(--tab-nav-item-text-color--default); - --tabs-item-icon-color: var(--tab-nav-item-icon-color--default); - --tabs-item-border-bottom-style: var(--tab-nav-item-border-style--default); - --tabs-item-border-bottom-color: var(--tab-nav-item-border-color--default); + /* SIZES */ + &:where(.tabs--size--medium) { + .tabs-item { + --tabs-item-height: 3.25rem; + --tabs-item-padding: 0.75rem 1rem; + --tabs-item-gap: 0.75rem; + --tabs-item-font-size: var(--font-size-body-lg); + --tabs-item-font-weight: 700; + --tabs-item-border-bottom-width: 3px; + } - &:active { - --tabs-item-color: var(--tab-nav-item-text-color--active); - --tabs-item-icon-color: var(--tab-nav-item-icon-color--active); - --tabs-item-border-bottom-color: var(--tab-nav-item-border-color--active); + .tabs-number-slate { + --tabs-item-number-slate-padding: var(--space-2) var(--space-4); + --tabs-item-number-slate-gap: 0.5rem; + --tabs-item-number-slate-border-radius: var(--radius-sm); + --tabs-item-number-slate-background-color: var(--color-surface-a4); + } } - &:hover { - --tabs-item-color: var(--tab-nav-item-text-color--hover); - --tabs-item-icon-color: var(--tab-nav-item-icon-color--hover); - --tabs-item-border-bottom-color: var(--tab-nav-item-border-color--hover); - } + /* VARIANTS */ + &:where(.tabs--variant--default) { + .tabs-item { + --tabs-item-color: var(--tab-nav-item-text-color--default); + --tabs-item-icon-color: var(--tab-nav-item-icon-color--default); + --tabs-item-border-bottom-style: var( + --tab-nav-item-border-style--default + ); + --tabs-item-border-bottom-color: var( + --tab-nav-item-border-color--default + ); - &.tabs__item--current { - --tabs-item-color: var(--tab-nav-item-text-color--current); - --tabs-item-icon-color: var(--tab-nav-item-icon-color--current); - --tabs-item-border-bottom-color: var( - --tab-nav-item-border-color--current - ); - } - } + &:active { + --tabs-item-color: var(--tab-nav-item-text-color--active); + --tabs-item-icon-color: var(--tab-nav-item-icon-color--active); + --tabs-item-border-bottom-color: var( + --tab-nav-item-border-color--active + ); + } - /* Tabs Item Number Slate */ + &:hover { + --tabs-item-color: var(--tab-nav-item-text-color--hover); + --tabs-item-icon-color: var(--tab-nav-item-icon-color--hover); + --tabs-item-border-bottom-color: var( + --tab-nav-item-border-color--hover + ); + } - /* SIZES */ - .tabs__number-slate:where(.tabs__number-slate--size--medium) { - --tabs-item-number-slate-padding: var(--space-2) var(--space-4); - --tabs-item-number-slate-gap: 0.5rem; - --tabs-item-number-slate-border-radius: var(--radius-sm); - --tabs-item-number-slate-background-color: var(--color-surface-a4); - } + &.tabs-item--current { + --tabs-item-color: var(--tab-nav-item-text-color--current); + --tabs-item-icon-color: var(--tab-nav-item-icon-color--current); + --tabs-item-border-bottom-color: var( + --tab-nav-item-border-color--current + ); + } + } - /* VARIANTS */ - .tabs__number-slate:where(.tabs__number-slate--variant--default) { - --tabs-item-number-slate-color: var(--tab-nav-item-text-color--default); - --tabs-item-number-slate-background-color: var(--color-surface-a4); + .tabs-number-slate { + --tabs-item-number-slate-color: var(--tab-nav-item-text-color--default); + --tabs-item-number-slate-background-color: var(--color-surface-a4); - &:active { - --tabs-item-number-slate-color: var(--tab-nav-item-text-color--active); - --tabs-item-number-slate-border-bottom-color: var( - --tab-nav-item-border-color--active - ); - } + &:active { + --tabs-item-number-slate-color: var( + --tab-nav-item-text-color--active + ); + --tabs-item-number-slate-border-bottom-color: var( + --tab-nav-item-border-color--active + ); + } - &:hover { - --tabs-item-number-slate-color: var(--tab-nav-item-text-color--hover); - --tabs-item-number-slate-border-bottom-color: var( - --tab-nav-item-border-color--hover - ); - } + &:hover { + --tabs-item-number-slate-color: var(--tab-nav-item-text-color--hover); + --tabs-item-number-slate-border-bottom-color: var( + --tab-nav-item-border-color--hover + ); + } - &.tabs__item--current { - --tabs-item-number-slate-color: var(--tab-nav-item-text-color--current); - --tabs-item-number-slate-border-bottom-color: var( - --tab-nav-item-border-color--current - ); + &.tabs-item--current { + --tabs-item-number-slate-color: var( + --tab-nav-item-text-color--current + ); + --tabs-item-number-slate-border-bottom-color: var( + --tab-nav-item-border-color--current + ); + } + } } } } diff --git a/packages/cyberstorm/src/newComponents/Tabs/Tabs.css b/packages/cyberstorm/src/newComponents/Tabs/Tabs.css index 33fd879a3..9be70296a 100644 --- a/packages/cyberstorm/src/newComponents/Tabs/Tabs.css +++ b/packages/cyberstorm/src/newComponents/Tabs/Tabs.css @@ -9,7 +9,7 @@ scrollbar-width: none; } - .tabs__item { + .tabs-item { position: relative; display: inline-flex; flex-shrink: 0; @@ -27,12 +27,12 @@ --icon-color: var(--tabs-item-icon-color); - &.tabs__item--current { + &.tabs-item--current { pointer-events: none; } } - .tabs__number-slate { + .tabs-number-slate { display: flex; gap: var(--tabs-item-number-slate-gap); align-items: center; diff --git a/packages/cyberstorm/src/newComponents/Tabs/Tabs.tsx b/packages/cyberstorm/src/newComponents/Tabs/Tabs.tsx index 9300733c4..a8d74e8b9 100644 --- a/packages/cyberstorm/src/newComponents/Tabs/Tabs.tsx +++ b/packages/cyberstorm/src/newComponents/Tabs/Tabs.tsx @@ -1,42 +1,23 @@ import "./Tabs.css"; -import React, { memo, ReactElement } from "react"; +import { memo } from "react"; import { Frame, FrameWindowProps } from "../../primitiveComponents/Frame/Frame"; import { classnames, componentClasses } from "../../utils/utils"; import { TabsSizes, TabsVariants, } from "@thunderstore/cyberstorm-theme/src/components"; -import { NewLink } from "../.."; -interface TabsProps - extends Omit { +interface TabsProps extends Omit { csVariant?: TabsVariants; csSize?: TabsSizes; - tabItems: { - itemProps: IP; - current: boolean; - numberSlateValue?: number; - disabled?: boolean; - tabItemNumberSlateRootClasses?: string; - key: string; - }[]; - renderTabItem: ( - key: string, - itemProps: IP, - numberSlate?: ReactElement - ) => ReturnType; - ref?: React.Ref; } -export const Tabs = memo(function Tabs( - props: TabsProps -) { +export const Tabs = memo(function Tabs(props: TabsProps) { const { csVariant = "default", csSize = "medium", rootClasses, - tabItems, - renderTabItem, + children, ...forwardedProps } = props; @@ -50,34 +31,7 @@ export const Tabs = memo(function Tabs( rootClasses )} > - {tabItems.map((item) => { - const tabsClasses = classnames( - "tabs__item", - ...componentClasses("tabs__item", csVariant, csSize), - item.current ? "tabs__item--current" : undefined - ); - if (item.itemProps.rootClasses) { - item.itemProps.rootClasses += tabsClasses; - } else { - item.itemProps.rootClasses = tabsClasses; - } - return renderTabItem( - item.key, - item.itemProps, - item.numberSlateValue ? ( -
- {item.numberSlateValue} -
- ) : undefined - ); - })} + {children} ); });