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} ); });