diff --git a/apps/console/app/components/EarlyAccess/EarlyAccessPanel.tsx b/apps/console/app/components/EarlyAccess/EarlyAccessPanel.tsx index 6ac7aa3ed2..b59944242c 100644 --- a/apps/console/app/components/EarlyAccess/EarlyAccessPanel.tsx +++ b/apps/console/app/components/EarlyAccess/EarlyAccessPanel.tsx @@ -23,6 +23,8 @@ type EarlyAccessPanelProps = { featurePlan?: ServicePlanType identityURN: IdentityURN earlyAccess: boolean + showPreviewButton?: boolean + handlePreviewButtonClick?: () => void } const EarlyAccessPanel = ({ @@ -37,6 +39,8 @@ const EarlyAccessPanel = ({ featurePlan, identityURN, earlyAccess, + showPreviewButton = false, + handlePreviewButtonClick, }: EarlyAccessPanelProps) => { return ( <> @@ -72,13 +76,22 @@ const EarlyAccessPanel = ({
- {featurePlan && isPlanGuarded(currentPlan, featurePlan) && ( - - + + )} + + {showPreviewButton && ( + - - )} + )} +
+ // @ts-ignore :( import('../../../web3/lazyAuth').then((module) => ({ default: module.LazyAuth, })) @@ -323,6 +323,8 @@ const AuthPanel = ({ appTheme, avatarURL, appIconURL, + authorizationURL, + appPublished, setLoading, errors, }: { @@ -330,6 +332,8 @@ const AuthPanel = ({ appTheme?: AppTheme avatarURL: string appIconURL?: string + authorizationURL: string + appPublished: boolean setLoading: React.Dispatch> errors?: { [key: string]: string @@ -402,6 +406,13 @@ const AuthPanel = ({ ) } + const previewAuth = () => { + const authURL = new URL(authorizationURL) + authURL.searchParams.set('rollup_action', 'preview') + + window.open(authURL.toString(), '_blank') + } + return ( <> @@ -667,21 +678,6 @@ const AuthPanel = ({
- - - - -
-
+ + + + + +
+ + + + + +
@@ -1478,17 +1523,6 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper( ...traceHeader, }) - const appDetails = await coreClient.starbase.getAppDetails.query({ - clientId, - }) - - await planGuardWithToastException( - appDetails.appPlan, - ServicePlanType.PRO, - request, - context.env - ) - let errors: { [key: string]: string } = {} @@ -1676,13 +1710,23 @@ export default () => { }[] }>() - const { appDetails, appContactAddress, appContactEmail, identityURN } = - useOutletContext<{ - appDetails: appDetailsProps - appContactAddress?: AccountURN - appContactEmail?: string - identityURN: IdentityURN - }>() + const { + appDetails, + appContactAddress, + appContactEmail, + identityURN, + avatarUrl, + notificationHandler, + authorizationURL, + } = useOutletContext<{ + appDetails: appDetailsProps + appContactAddress?: AccountURN + appContactEmail?: string + identityURN: IdentityURN + avatarUrl: string + notificationHandler: notificationHandlerType + authorizationURL: string + }>() const actionData = useActionData() const errors = actionData?.errors @@ -1693,14 +1737,10 @@ export default () => { } }, [errors]) - const { avatarUrl, notificationHandler } = useOutletContext<{ - avatarUrl: string - notificationHandler: notificationHandlerType - }>() - const [loading, setLoading] = useState(false) + const [previewState, setPreviewState] = useState(false) - if (appDetails.appPlan === ServicePlanType.FREE) { + if (appDetails.appPlan === ServicePlanType.FREE && !previewState) { return ( { currentPlan={appDetails.appPlan} featurePlan={ServicePlanType.PRO} identityURN={identityURN} + showPreviewButton={true} + handlePreviewButtonClick={() => { + setPreviewState(true) + }} /> ) } @@ -1791,6 +1835,8 @@ export default () => { appTheme={appTheme} avatarURL={avatarUrl} appIconURL={appDetails.app.icon} + authorizationURL={authorizationURL} + appPublished={appDetails.published ?? false} setLoading={setLoading} errors={errors} /> diff --git a/apps/passport/app/root.tsx b/apps/passport/app/root.tsx index 413fd2298e..6e2b1f46a3 100644 --- a/apps/passport/app/root.tsx +++ b/apps/passport/app/root.tsx @@ -72,7 +72,10 @@ export const links: LinksFunction = () => [ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( async ({ request, context, params }) => { const clientId = new URL(request.url).searchParams.get('client_id') - if (request.cf && + if ( + // @ts-ignore :( + request.cf && + // @ts-ignore :( request.cf.botManagement.score <= 30 && !['localhost', '127.0.0.1'].includes(new URL(request.url).hostname) ) { @@ -122,8 +125,14 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( } } else { const coreClient = getCoreClient({ context }) + + const rollupAction = new URL(request.url).searchParams.get( + 'rollup_action' + ) + appProps = await coreClient.starbase.getAppPublicProps.query({ clientId: (params.clientId || clientId) as string, + previewMode: rollupAction === 'preview', }) } } diff --git a/apps/passport/app/routes/authenticate/$clientId.tsx b/apps/passport/app/routes/authenticate/$clientId.tsx index 1233843af8..6356351ad2 100644 --- a/apps/passport/app/routes/authenticate/$clientId.tsx +++ b/apps/passport/app/routes/authenticate/$clientId.tsx @@ -1,6 +1,6 @@ import { Outlet, useLoaderData, useOutletContext } from '@remix-run/react' import { json } from '@remix-run/cloudflare' -import { getAuthzCookieParams, isSupportedRollupAction } from '~/session.server' +import { getAuthzCookieParams } from '~/session.server' import sideGraphics from '~/assets/auth-side-graphics.svg' @@ -12,6 +12,7 @@ import { useContext } from 'react' import { getRollupReqFunctionErrorWrapper } from '@proofzero/utils/errors' import { AuthenticationScreenDefaults } from '@proofzero/design-system/src/templates/authentication/Authentication' import { ThemeContext } from '@proofzero/design-system/src/contexts/theme' +import { isSupportedRollupAction } from '~/utils/actions' export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( async ({ request, context, params }) => { @@ -54,23 +55,23 @@ export default () => { @@ -84,8 +85,9 @@ export default () => { 'basis-2/5 h-[100dvh] w-full hidden lg:flex justify-center items-center bg-indigo-50 dark:bg-[#1F2937] overflow-hidden' } style={{ - backgroundImage: `url(${appProps?.appTheme?.graphicURL ?? sideGraphics - })`, + backgroundImage: `url(${ + appProps?.appTheme?.graphicURL ?? sideGraphics + })`, backgroundSize: 'cover', backgroundPosition: 'center', backgroundRepeat: 'no-repeat', diff --git a/apps/passport/app/routes/authenticate/$clientId/index.tsx b/apps/passport/app/routes/authenticate/$clientId/index.tsx index ddf70bf65f..3410bd7d5a 100644 --- a/apps/passport/app/routes/authenticate/$clientId/index.tsx +++ b/apps/passport/app/routes/authenticate/$clientId/index.tsx @@ -36,6 +36,7 @@ import { obfuscateAlias, } from '@proofzero/utils' import _ from 'lodash' +import { isConnectRollupAction } from '~/utils/actions' const LazyAuth = lazy(() => import('../../../web3/lazyAuth').then((module) => ({ @@ -190,7 +191,8 @@ const InnerComponent = ({ history.replaceState(null, '', url.toString()) }, []) - const generic = Boolean(rollup_action) && !rollup_action?.startsWith('group_') + const generic = + Boolean(rollup_action) && isConnectRollupAction(rollup_action!) return ( {`${_.upperFirst(invitationData.accountType)} Account: ${invitationData.identifier - }`} + >{`${_.upperFirst(invitationData.accountType)} Account: ${ + invitationData.identifier + }`} )} @@ -238,8 +241,8 @@ const InnerComponent = ({ {appProps?.appTheme?.heading ? appProps.appTheme.heading : appProps?.name - ? `Login to ${appProps?.name}` - : AuthenticationScreenDefaults.defaultHeading} + ? `Login to ${appProps?.name}` + : AuthenticationScreenDefaults.defaultHeading}

{`${_.upperFirst(invitationData.accountType)} Account: ${invitationData.identifier - }`} + >{`${_.upperFirst(invitationData.accountType)} Account: ${ + invitationData.identifier + }`} )} diff --git a/apps/passport/app/routes/authorize.tsx b/apps/passport/app/routes/authorize.tsx index 9cbc03df7c..8ab83585d8 100644 --- a/apps/passport/app/routes/authorize.tsx +++ b/apps/passport/app/routes/authorize.tsx @@ -14,7 +14,6 @@ import { destroyAuthzCookieParamsSession, getAuthzCookieParams, getValidatedSessionContext, - isSupportedRollupAction, } from '~/session.server' import { validatePersonaData } from '@proofzero/security/persona' @@ -46,6 +45,11 @@ import { Helmet } from 'react-helmet' import { getRGBColor, getTextColor } from '@proofzero/design-system/src/helpers' import { AccountURNSpace } from '@proofzero/urns/account' import type { DropdownSelectListItem } from '@proofzero/design-system/src/atoms/dropdown/DropdownSelectList' +import { ToastType, toast } from '@proofzero/design-system/src/atoms/toast' +import { + getSupportedRollupActions, + isSupportedRollupAction, +} from '~/utils/actions' export type UserProfile = { displayName: string @@ -66,6 +70,7 @@ export type LoaderData = { dataForScopes: DataForScopes profile: GetProfileOutputParams prompt?: string + previewMode: boolean } export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( @@ -99,8 +104,9 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( if (rollup_action && !isSupportedRollupAction(rollup_action)) throw new BadRequestError({ - message: - 'only Rollup action supported are connect, create, and reconnect ', + message: `only Rollup actions supported are: ${getSupportedRollupActions().join( + ', ' + )}`, }) const lastCP = await getAuthzCookieParams(request, context.env) @@ -182,6 +188,7 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( coreClient.starbase.getScopes.query(), coreClient.starbase.getAppPublicProps.query({ clientId: clientId as string, + previewMode: lastCP?.rollup_action === 'preview', }), ]) @@ -211,7 +218,9 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( //If requested scope values are a subset of allowed scope values if ( - !scope.every((scopeValue) => appPublicProps.scopes.includes(scopeValue)) + !scope.every((scopeValue) => + (appPublicProps.scopes ?? []).includes(scopeValue) + ) ) throw new BadRequestError({ message: @@ -287,6 +296,7 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( dataForScopes, profile, prompt, + previewMode: lastCP?.rollup_action === 'preview', }, { headers, @@ -397,6 +407,7 @@ export default function Authorize() { redirectUri, profile, prompt, + previewMode, } = useLoaderData() const userProfile = profile as UserProfile @@ -460,6 +471,12 @@ export default function Authorize() { useConnectResult() const cancelCallback = () => { + if (previewMode) { + window.close() + + return + } + const redirectURL = new URL(redirectUri) redirectURL.search = `?error=access_denied&state=${state}` @@ -472,6 +489,16 @@ export default function Authorize() { } const authorizeCallback = async (scopes: string[]) => { + if (previewMode) { + toast( + ToastType.Warning, + { message: 'This step cannot be completed in Preview Mode' }, + { duration: 2000 } + ) + + return + } + const form = new FormData() form.append('scopes', scopes.join(' ')) form.append('state', state) diff --git a/apps/passport/app/session.server.ts b/apps/passport/app/session.server.ts index 6c3fd625b7..4d729065d2 100644 --- a/apps/passport/app/session.server.ts +++ b/apps/passport/app/session.server.ts @@ -381,14 +381,3 @@ export function parseJwt(token: string): JWTPayload { } return payload } - -export function isSupportedRollupAction(rollupAction: string) { - return [ - 'connect', - 'create', - 'reconnect', - 'group', - 'groupconnect', - 'groupemailconnect', - ].includes(rollupAction.split('_')[0]) -} diff --git a/apps/passport/app/utils/actions.ts b/apps/passport/app/utils/actions.ts new file mode 100644 index 0000000000..cd296fe95d --- /dev/null +++ b/apps/passport/app/utils/actions.ts @@ -0,0 +1,19 @@ +export const getConnectRollupActions = (): string[] => { + return ['connect', 'create', 'reconnect', 'groupconnect', 'groupemailconnect'] +} + +export const getNonConnectRollupActions = (): string[] => { + return ['group', 'preview'] +} + +export const getSupportedRollupActions = (): string[] => { + return [...getConnectRollupActions(), ...getNonConnectRollupActions()] +} + +export const isSupportedRollupAction = (rollupAction: string): boolean => { + return getSupportedRollupActions().includes(rollupAction.split('_')[0]) +} + +export const isConnectRollupAction = (rollupAction: string): boolean => { + return getConnectRollupActions().includes(rollupAction.split('_')[0]) +} diff --git a/platform/starbase/src/jsonrpc/methods/getAppPublicProps.ts b/platform/starbase/src/jsonrpc/methods/getAppPublicProps.ts index aaa35635cc..8e90ba7a0f 100644 --- a/platform/starbase/src/jsonrpc/methods/getAppPublicProps.ts +++ b/platform/starbase/src/jsonrpc/methods/getAppPublicProps.ts @@ -9,7 +9,12 @@ import { import type { CustomDomain } from '../../types' import { ServicePlanType } from '@proofzero/types/billing' -export const GetAppPublicPropsInput = AppClientIdParamSchema +export const GetAppPublicPropsInput = AppClientIdParamSchema.merge( + z.object({ + previewMode: z.boolean().optional(), + }) +) + export const GetAppPublicPropsOutput = AppPublicPropsSchema export const GetAppPublicPropsBatchInput = z.object({ apps: z.array(GetAppPublicPropsInput), @@ -28,7 +33,7 @@ export const getAppPublicProps = async ({ input: z.infer ctx: Context }): Promise => { - return await getPublicPropsForApp(input.clientId, ctx) + return await getPublicPropsForApp(ctx, input.clientId, input.previewMode) } export const getAppPublicPropsBatch = async ({ @@ -40,10 +45,10 @@ export const getAppPublicPropsBatch = async ({ }): Promise> => { const { apps, silenceErrors } = input const resultMap: (AppPublicProps | undefined)[] = [] - for (const { clientId } of apps) { + for (const { clientId, previewMode } of apps) { let appResult try { - appResult = await getPublicPropsForApp(clientId, ctx) + appResult = await getPublicPropsForApp(ctx, clientId, previewMode) } catch (e) { if (silenceErrors) appResult = undefined else throw e @@ -53,13 +58,17 @@ export const getAppPublicPropsBatch = async ({ return resultMap } -async function getPublicPropsForApp(clientId: string, ctx: Context) { +async function getPublicPropsForApp( + ctx: Context, + clientId: string, + previewMode?: boolean +) { const appDO = await getApplicationNodeByClientId(clientId, ctx.StarbaseApp) const appDetails = await appDO.class.getDetails() const { appPlan } = appDetails let appTheme = await appDO.class.getTheme() - if (!appPlan || appPlan === ServicePlanType.FREE) { + if (!previewMode && (!appPlan || appPlan === ServicePlanType.FREE)) { appTheme = undefined }