diff --git a/apps/passport/app/routes/token.tsx b/apps/passport/app/routes/token.tsx
index 728d636922..59345c0c78 100644
--- a/apps/passport/app/routes/token.tsx
+++ b/apps/passport/app/routes/token.tsx
@@ -1,10 +1,10 @@
import { ActionFunction } from '@remix-run/cloudflare'
import { json } from '@remix-run/cloudflare'
-import { GrantType } from '@proofzero/types/access'
+import { GrantType } from '@proofzero/types/authorization'
import { getRollupReqFunctionErrorWrapper } from '@proofzero/utils/errors'
-import createAccessClient from '@proofzero/platform-clients/access'
+import { getCoreClient } from '../platform.server'
import { generateTraceContextHeaders } from '@proofzero/platform-middleware/trace'
export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
@@ -19,20 +19,17 @@ export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
(formData.get('grant_type') as GrantType.AuthorizationCode) ||
GrantType.RefreshToken
- const accessClient = createAccessClient(
- context.env.Access,
- generateTraceContextHeaders(context.traceSpan)
- )
+ const coreClient = getCoreClient({ context })
const tokens = refreshToken
- ? await accessClient.exchangeToken.mutate({
+ ? await coreClient.authorization.exchangeToken.mutate({
grantType: GrantType.RefreshToken,
refreshToken,
clientId,
clientSecret,
issuer,
})
- : await accessClient.exchangeToken.mutate({
+ : await coreClient.authorization.exchangeToken.mutate({
grantType: GrantType.AuthorizationCode,
code,
clientId,
diff --git a/apps/passport/app/routes/userinfo.tsx b/apps/passport/app/routes/userinfo.tsx
index 647e56dfbb..096fa503e8 100644
--- a/apps/passport/app/routes/userinfo.tsx
+++ b/apps/passport/app/routes/userinfo.tsx
@@ -1,20 +1,23 @@
import type { LoaderFunction } from '@remix-run/cloudflare'
-import createAccessClient from '@proofzero/platform-clients/access'
import { getAuthzTokenFromReq } from '@proofzero/utils'
-import { generateTraceContextHeaders } from '@proofzero/platform-middleware/trace'
import { getRollupReqFunctionErrorWrapper } from '@proofzero/utils/errors'
import { UnauthorizedError } from '@proofzero/errors'
+import { getCoreClient } from '../platform.server'
+
export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper(
async ({ request, context }) => {
const access_token = getAuthzTokenFromReq(request)
if (!access_token)
throw new UnauthorizedError({ message: 'No access token provided' })
- const accessClient = createAccessClient(context.env.Access, {
- ...generateTraceContextHeaders(context.traceSpan),
+ const { origin: issuer } = new URL(request.url)
+
+ const coreClient = getCoreClient({ context })
+ const result = await coreClient.authorization.getUserInfo.query({
+ access_token,
+ issuer,
})
- const result = await accessClient.getUserInfo.query({ access_token })
return result
}
)
diff --git a/apps/passport/app/session.server.ts b/apps/passport/app/session.server.ts
index a649d93fc2..4d729065d2 100644
--- a/apps/passport/app/session.server.ts
+++ b/apps/passport/app/session.server.ts
@@ -16,24 +16,25 @@ import {
import { encryptSession, decryptSession } from '@proofzero/utils/session'
-import { getAccountClient } from './platform.server'
+import { getCoreClient } from './platform.server'
import type { TraceSpan } from '@proofzero/platform-middleware/trace'
import { InternalServerError, UnauthorizedError } from '@proofzero/errors'
-import { AccountURNSpace } from '@proofzero/urns/account'
-import type { AccountURN } from '@proofzero/urns/account'
+import { IdentityURNSpace } from '@proofzero/urns/identity'
+import type { IdentityURN } from '@proofzero/urns/identity'
import { FLASH_MESSAGE, FLASH_MESSAGE_KEY } from './utils/flashMessage.server'
+import { getCookieDomain } from './utils/cookie'
-export const InvalidSessionAccountError = new UnauthorizedError({
- message: 'Session account is not valid',
+export const InvalidSessionIdentityError = new UnauthorizedError({
+ message: 'Session identity is not valid',
})
// FLASH SESSION
-const getFlashSessionStorage = (env: Env) => {
+const getFlashSessionStorage = (request: Request, env: Env) => {
return createCookieSessionStorage({
cookie: {
- domain: env.COOKIE_DOMAIN,
+ domain: getCookieDomain(request, env),
name: '_rollup_flash',
path: '/',
sameSite: 'lax',
@@ -46,18 +47,23 @@ const getFlashSessionStorage = (env: Env) => {
}
export function getFlashSession(request: Request, env: Env) {
- const storage = getFlashSessionStorage(env)
+ const storage = getFlashSessionStorage(request, env)
return storage.getSession(request.headers.get('Cookie'))
}
-export function commitFlashSession(env: Env, session: Session) {
- const storage = getFlashSessionStorage(env)
+export function commitFlashSession(
+ request: Request,
+ env: Env,
+ session: Session
+) {
+ const storage = getFlashSessionStorage(request, env)
return storage.commitSession(session)
}
// USER PARAMS
const getUserSessionStorage = (
+ request: Request,
env: Env,
clientId?: string,
MAX_AGE = 7776000 /*60 * 60 * 24 * 90*/
@@ -68,7 +74,7 @@ const getUserSessionStorage = (
}
return createCookie(cookieName, {
- domain: env.COOKIE_DOMAIN,
+ domain: getCookieDomain(request, env),
path: '/',
sameSite: 'lax',
secure: process.env.NODE_ENV == 'production',
@@ -78,12 +84,13 @@ const getUserSessionStorage = (
}
export async function createUserSession(
+ request: Request,
jwt: string,
redirectTo: string,
env: Env,
clientId?: string
) {
- const cookie = getUserSessionStorage(env, clientId)
+ const cookie = getUserSessionStorage(request, env, clientId)
return redirect(redirectTo, {
headers: {
'Set-Cookie': await cookie.serialize(
@@ -98,7 +105,7 @@ export async function getUserSession(
env: Env,
clientId?: string
) {
- const cookie = getUserSessionStorage(env, clientId)
+ const cookie = getUserSessionStorage(request, env, clientId)
const data = await cookie.parse(request.headers.get('Cookie'))
if (!data) return ''
@@ -122,13 +129,13 @@ export async function destroyUserSession(
) {
const headers = new Headers()
- const cookie = await getUserSessionStorage(env, clientId)
+ const cookie = await getUserSessionStorage(request, env, clientId)
headers.append(
'Set-Cookie',
await cookie.serialize('', { expires: new Date(0) })
)
- const flashStorage = getFlashSessionStorage(env)
+ const flashStorage = getFlashSessionStorage(request, env)
const flashSession = await flashStorage.getSession()
flashSession.flash(FLASH_MESSAGE_KEY, flashMessage)
headers.append('Set-Cookie', await flashStorage.commitSession(flashSession))
@@ -137,6 +144,7 @@ export async function destroyUserSession(
}
const getAuthzCookieParamsSessionStorage = (
+ request: Request,
env: Env,
clientId: string = 'last',
// https://developer.chrome.com/blog/cookie-max-age-expires/
@@ -146,7 +154,7 @@ const getAuthzCookieParamsSessionStorage = (
) => {
return createCookieSessionStorage({
cookie: {
- domain: env.COOKIE_DOMAIN,
+ domain: getCookieDomain(request, env),
name: `_rollup_authz_params_${clientId}`,
path: '/',
sameSite: 'lax',
@@ -162,12 +170,14 @@ const getAuthzCookieParamsSessionStorage = (
* and redirects to the authentication route
*/
export async function createAuthzParamsCookieAndAuthenticate(
+ request: Request,
authzQueryParams: AuthzParams,
env: Env,
qp: URLSearchParams = new URLSearchParams()
) {
let redirectURL = `/authenticate/${authzQueryParams.clientId}${
- ['connect', 'reconnect'].includes(authzQueryParams.rollup_action || '')
+ ['connect', 'reconnect'].includes(authzQueryParams.rollup_action || '') ||
+ authzQueryParams.rollup_action?.startsWith('group')
? ''
: `/account`
}`
@@ -185,6 +195,7 @@ export async function createAuthzParamsCookieAndAuthenticate(
throw redirect(redirectURL, {
headers: await createAuthorizationParamsCookieHeaders(
+ request,
authzQueryParams,
env
),
@@ -192,6 +203,7 @@ export async function createAuthzParamsCookieAndAuthenticate(
}
export async function createAuthorizationParamsCookieHeaders(
+ request: Request,
authzParams: AuthzParams,
env: Env
) {
@@ -204,22 +216,28 @@ export async function createAuthorizationParamsCookieHeaders(
const headers = new Headers()
headers.append(
'Set-Cookie',
- await setAuthzCookieParamsSession(authzParams, env, authzParams.clientId)
+ await setAuthzCookieParamsSession(
+ request,
+ authzParams,
+ env,
+ authzParams.clientId
+ )
)
headers.append(
'Set-Cookie',
- await setAuthzCookieParamsSession(authzParams, env)
+ await setAuthzCookieParamsSession(request, authzParams, env)
)
return headers
}
export async function setAuthzCookieParamsSession(
+ request: Request,
authzParams: AuthzParams,
env: Env,
clientId?: string
) {
- const storage = getAuthzCookieParamsSessionStorage(env, clientId)
+ const storage = getAuthzCookieParamsSessionStorage(request, env, clientId)
const session = await storage.getSession()
//Convert string array scope to space-delimited scope before setting cookie value
@@ -235,7 +253,7 @@ export async function getAuthzCookieParamsSession(
env: Env,
clientId?: string
) {
- const storage = getAuthzCookieParamsSessionStorage(env, clientId)
+ const storage = getAuthzCookieParamsSessionStorage(request, env, clientId)
return storage.getSession(request.headers.get('Cookie'))
}
@@ -245,7 +263,7 @@ export async function destroyAuthzCookieParamsSession(
clientId?: string
) {
const gps = await getAuthzCookieParamsSession(request, env, clientId)
- const storage = getAuthzCookieParamsSessionStorage(env, clientId)
+ const storage = getAuthzCookieParamsSessionStorage(request, env, clientId)
return storage.destroySession(gps)
}
@@ -286,7 +304,7 @@ export function getDefaultAuthzParams(request: Request): AuthzParams {
export type ValidatedSessionContext = {
jwt: string
- accountUrn: AccountURN
+ identityURN: IdentityURN
}
export async function getValidatedSessionContext(
@@ -303,26 +321,40 @@ export async function getValidatedSessionContext(
try {
const payload = checkToken(jwt)
- const accountClient = getAccountClient(jwt, env, traceSpan)
+ const context = { env: { Core: env.Core }, traceSpan: traceSpan }
+ const coreClient = getCoreClient({ context, jwt })
if (
- !AccountURNSpace.is(payload.sub!) ||
- !(await accountClient.isValid.query())
+ !IdentityURNSpace.is(payload.sub!) ||
+ !(await coreClient.identity.isValid.query())
)
- throw InvalidSessionAccountError
+ throw InvalidSessionIdentityError
return {
jwt,
- accountUrn: payload.sub as AccountURN,
+ identityURN: payload.sub as IdentityURN,
}
} catch (error) {
- // TODO: Revise this logic
- const redirectTo = `/authenticate/${authzParams?.clientId}`
+ const url = new URL(request.url)
+ const { href } = url
+
+ const qp = new URLSearchParams()
+ qp.append('client_id', 'passport')
+ qp.append('redirect_uri', `${href}`)
+ qp.append('scope', '')
+ qp.append('state', 'skip')
+
+ const redirectTo = `/authorize?${qp.toString()}`
+
if (error === InvalidTokenError)
if (authzParams.clientId)
- throw await createAuthzParamsCookieAndAuthenticate(authzParams, env)
+ throw await createAuthzParamsCookieAndAuthenticate(
+ request,
+ authzParams,
+ env
+ )
else throw redirect(redirectTo)
else if (
error === ExpiredTokenError ||
- error === InvalidSessionAccountError
+ error === InvalidSessionIdentityError
) {
console.error(
'Session/token error encountered. Invalidating session and redirecting to login page'
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/apps/passport/app/utils/authenticate.server.ts b/apps/passport/app/utils/authenticate.server.ts
index b4c1af5dae..2b592f1e7c 100644
--- a/apps/passport/app/utils/authenticate.server.ts
+++ b/apps/passport/app/utils/authenticate.server.ts
@@ -1,18 +1,14 @@
-import { AddressURNSpace } from '@proofzero/urns/address'
-import type { AddressURN } from '@proofzero/urns/address'
+import { AccountURNSpace } from '@proofzero/urns/account'
import type { AccountURN } from '@proofzero/urns/account'
+import type { IdentityURN } from '@proofzero/urns/identity'
-import { JsonError } from '@proofzero/utils/errors'
-import { GrantType, ResponseType } from '@proofzero/types/access'
+import { GrantType, ResponseType } from '@proofzero/types/authorization'
-import {
- getAccessClient,
- getAccountClient,
- getAddressClient,
-} from '~/platform.server'
+import { getCoreClient } from '~/platform.server'
import {
createUserSession,
getAuthzCookieParamsSession,
+ getUserSession,
parseJwt,
} from '~/session.server'
import { generateGradient } from './gradient.server'
@@ -24,9 +20,9 @@ import {
createAuthenticatorSessionStorage,
} from '~/auth.server'
-export const authenticateAddress = async (
- address: AddressURN,
+export const authenticateAccount = async (
account: AccountURN,
+ identity: IdentityURN,
appData: AuthzParams,
request: Request,
env: Env,
@@ -40,27 +36,40 @@ export const authenticateAddress = async (
})
}
+ const jwt = await getUserSession(request, env, appData?.clientId)
if (
appData.rollup_action &&
- ['connect', 'reconnect'].includes(appData?.rollup_action)
+ (['connect', 'reconnect'].includes(appData?.rollup_action) ||
+ appData?.rollup_action.startsWith('groupconnect'))
) {
- const redirectURL = getAuthzRedirectURL(
- appData,
- existing && appData.rollup_action === 'connect'
- ? 'ALREADY_CONNECTED'
- : undefined
- )
+ let result = undefined
+
+ if (
+ existing &&
+ (appData.rollup_action === 'connect' ||
+ appData.rollup_action.startsWith('groupconnect'))
+ ) {
+ const loggedInIdentity = parseJwt(jwt).sub
+ if (identity !== loggedInIdentity) {
+ result = 'ACCOUNT_CONNECT_ERROR'
+ } else {
+ result = 'ALREADY_CONNECTED_ERROR'
+ }
+ }
+
+ const redirectURL = getAuthzRedirectURL(appData, result)
return redirect(redirectURL)
}
- const accessClient = getAccessClient(env, traceSpan)
- const clientId = AddressURNSpace.decode(address)
+ const context = { env: { Core: env.Core }, traceSpan }
+ const coreClient = getCoreClient({ context })
+ const clientId = AccountURNSpace.decode(account)
const redirectUri = env.PASSPORT_REDIRECT_URL
const scope = ['admin']
const state = ''
- const { code } = await accessClient.authorize.mutate({
- account,
+ const { code } = await coreClient.authorization.authorize.mutate({
+ identity,
responseType: ResponseType.Code,
clientId,
redirectUri,
@@ -69,16 +78,17 @@ export const authenticateAddress = async (
})
const grantType = GrantType.AuthenticationCode
- const { accessToken } = await accessClient.exchangeToken.mutate({
+ const { accessToken } = await coreClient.authorization.exchangeToken.mutate({
grantType,
code,
clientId,
issuer: new URL(request.url).origin,
})
- await provisionProfile(accessToken, env, traceSpan, address)
+ await provisionProfile(accessToken, env, traceSpan, account)
return createUserSession(
+ request,
accessToken,
getAuthzRedirectURL(appData),
env,
@@ -114,20 +124,22 @@ const provisionProfile = async (
jwt: string,
env: Env,
traceSpan: TraceSpan,
- address: AddressURN
+ accountURN: AccountURN
) => {
- const accountClient = getAccountClient(jwt, env, traceSpan)
+ const context = { env: { Core: env.Core }, traceSpan }
+ const coreClient = getCoreClient({ context, accountURN, jwt })
const parsedJWT = parseJwt(jwt)
- const account = parsedJWT.sub as AccountURN
+ const identity = parsedJWT.sub as IdentityURN
- const profile = await accountClient.getProfile.query({
- account,
+ const profile = await coreClient.identity.getProfile.query({
+ identity,
})
if (!profile) {
- console.log(`Profile doesn't exist for account ${account}. Creating one...`)
- const addressClient = getAddressClient(address, env, traceSpan)
- const newProfile = await addressClient.getAddressProfile
+ console.log(
+ `Profile doesn't exist for identity ${identity}. Creating one...`
+ )
+ const newProfile = await coreClient.account.getAccountProfile
.query()
.then(async (res) => {
const gradient = await generateGradient(res.address, env, traceSpan)
@@ -139,40 +151,41 @@ const provisionProfile = async (
}
})
// set the default profile
- await accountClient.setProfile.mutate({
- name: account,
- profile: { ...newProfile, primaryAddressURN: address },
+ await coreClient.identity.setProfile.mutate({
+ name: identity,
+ profile: { ...newProfile, primaryAccountURN: accountURN },
})
} else {
- console.log(`Profile for account ${account} found. Continuing...`)
+ console.log(`Profile for identity ${identity} found. Continuing...`)
}
}
-export const setNewPrimaryAddress = async (
+export const setNewPrimaryAccount = async (
jwt: string,
env: Env,
traceSpan: TraceSpan,
- newPrimaryAddress: AddressURN,
+ newPrimaryAccount: AccountURN,
pfp: string,
displayName: string
) => {
- const accountClient = getAccountClient(jwt, env, traceSpan)
+ const context = { env: { Core: env.Core }, traceSpan }
+ const coreClient = getCoreClient({ context, jwt })
const parsedJWT = parseJwt(jwt)
- const account = parsedJWT.sub as AccountURN
+ const identity = parsedJWT.sub as IdentityURN
- const profile = await accountClient.getProfile.query({
- account,
+ const profile = await coreClient.identity.getProfile.query({
+ identity,
})
- // Update the profile with the new primary address if it exists
+ // Update the profile with the new primary account if it exists
if (profile) {
- await accountClient.setProfile.mutate({
- name: account,
+ await coreClient.identity.setProfile.mutate({
+ name: identity,
profile: {
displayName: displayName,
pfp: { ...profile.pfp, image: pfp },
- primaryAddressURN: newPrimaryAddress,
+ primaryAccountURN: newPrimaryAccount,
},
})
}
@@ -189,7 +202,10 @@ export const checkOAuthError = async (request: Request, env: Env) => {
console.error({ error, uri, description })
const authzParams = await getAuthzCookieParamsSession(request, env)
- const authenticatorStorage = await createAuthenticatorSessionStorage(env)
+ const authenticatorStorage = await createAuthenticatorSessionStorage(
+ request,
+ env
+ )
const session = await authenticatorStorage.getSession(
request.headers.get('Cookie')
)
diff --git a/apps/passport/app/utils/authorize.server.ts b/apps/passport/app/utils/authorize.server.ts
index f2885961b8..93e0b7b093 100644
--- a/apps/passport/app/utils/authorize.server.ts
+++ b/apps/passport/app/utils/authorize.server.ts
@@ -1,35 +1,33 @@
-import { getAccountClient, getAddressClient } from '~/platform.server'
+import { getCoreClient } from '~/platform.server'
import {
- getNormalisedConnectedEmails,
- getNormalisedSmartContractWallets,
+ getEmailDropdownItems,
+ getAccountDropdownItems,
} from '@proofzero/utils/getNormalisedConnectedAccounts'
import { BadRequestError, UnauthorizedError } from '@proofzero/errors'
import { createAuthorizationParamsCookieHeaders } from '~/session.server'
-import type { GetAddressProfileResult } from '@proofzero/platform.address/src/jsonrpc/methods/getAddressProfile'
-
import {
SCOPE_CONNECTED_ACCOUNTS,
SCOPE_EMAIL,
SCOPE_SMART_CONTRACT_WALLETS,
} from '@proofzero/security/scopes'
-import type { AccountURN } from '@proofzero/urns/account'
+import type { IdentityURN } from '@proofzero/urns/identity'
import type { PersonaData } from '@proofzero/types/application'
-import type {
- EmailSelectListItem,
- SCWalletSelectListItem,
-} from '@proofzero/utils/getNormalisedConnectedAccounts'
import { redirect } from '@remix-run/cloudflare'
-import { CryptoAddressType } from '@proofzero/types/address'
+import { CryptoAccountType, NodeType } from '@proofzero/types/account'
+import type { DropdownSelectListItem } from '@proofzero/design-system/src/atoms/dropdown/DropdownSelectList'
+import type { AccountURN } from '@proofzero/urns/account'
+import { Address } from 'viem'
+import { NO_OP_ACCOUNT_PLACEHOLDER } from '@proofzero/platform.account/src/constants'
export type DataForScopes = {
- connectedEmails?: EmailSelectListItem[]
- personaData?: PersonaData
+ connectedEmails: DropdownSelectListItem[]
+ personaData: PersonaData
requestedScope: string[]
- connectedAccounts?: GetAddressProfileResult[]
- connectedSmartContractWallets?: SCWalletSelectListItem[]
+ connectedAccounts: DropdownSelectListItem[]
+ connectedSmartContractWallets: DropdownSelectListItem[]
}
// Deterministically sort scopes so that they are always in the same order
@@ -56,54 +54,59 @@ export const reorderScope = (scopes: string[]): string[] => {
export const getDataForScopes = async (
requestedScope: string[],
- accountURN: AccountURN,
+ identityURN: IdentityURN,
jwt?: string,
env?: any,
traceSpan?: any
): Promise
=> {
- if (!accountURN)
- throw new UnauthorizedError({ message: 'Account URN is required' })
+ if (!identityURN)
+ throw new UnauthorizedError({ message: 'Identity URN is required' })
- let connectedSmartContractWallets: SCWalletSelectListItem[] = []
- let connectedEmails: EmailSelectListItem[] = []
- let connectedAddresses: GetAddressProfileResult[] = []
+ let connectedSmartContractWallets: Array = []
+ let connectedEmails: Array = []
+ let connectedAddresses: Array = []
- const accountClient = getAccountClient(jwt || '', env, traceSpan)
+ const context = { env: { Core: env.Core }, traceSpan }
+ const coreClient = getCoreClient({ context, jwt })
- const connectedAccounts = await accountClient.getAddresses.query({
- account: accountURN,
+ const connectedAccounts = await coreClient.identity.getAccounts.query({
+ URN: identityURN,
})
if (connectedAccounts && connectedAccounts.length) {
if (requestedScope.includes(Symbol.keyFor(SCOPE_EMAIL)!)) {
- connectedEmails = getNormalisedConnectedEmails(connectedAccounts)
+ connectedEmails = getEmailDropdownItems(connectedAccounts)
}
if (requestedScope.includes(Symbol.keyFor(SCOPE_CONNECTED_ACCOUNTS)!)) {
- connectedAddresses = await Promise.all(
- connectedAccounts
- .filter((ca) => {
- return ca.rc.addr_type !== CryptoAddressType.Wallet
- })
- .map((ca) => {
- const addressClient = getAddressClient(ca.baseUrn, env, traceSpan)
- return addressClient.getAddressProfile.query()
- })
- )
+ const accounts = connectedAccounts
+ .filter((ca) => {
+ return (
+ (ca.rc.node_type === NodeType.OAuth ||
+ ca.rc.node_type === NodeType.Email ||
+ ca.rc.node_type === NodeType.Crypto ||
+ ca.rc.node_type === NodeType.WebAuthN) &&
+ ca.rc.addr_type !== CryptoAccountType.Wallet
+ )
+ })
+ .map((ca) => {
+ return ca.baseUrn as AccountURN
+ })
+
+ const accountProfiles =
+ await coreClient.account.getAccountProfileBatch.query(accounts)
+ connectedAddresses = getAccountDropdownItems(accountProfiles)
}
if (requestedScope.includes(Symbol.keyFor(SCOPE_SMART_CONTRACT_WALLETS)!)) {
- const scWalletAddresses = await Promise.all(
- connectedAccounts
- .filter((ca) => {
- return ca.rc.addr_type === CryptoAddressType.Wallet
- })
- .map((ca) => {
- const addressClient = getAddressClient(ca.baseUrn, env, traceSpan)
- return addressClient.getAddressProfile.query()
- })
- )
-
- connectedSmartContractWallets =
- getNormalisedSmartContractWallets(scWalletAddresses)
+ const accounts = connectedAccounts
+ .filter((ca) => {
+ return ca.rc.addr_type === CryptoAccountType.Wallet
+ })
+ .map((ca) => {
+ return ca.baseUrn as AccountURN
+ })
+ const accountProfiles =
+ await coreClient.account.getAccountProfileBatch.query(accounts)
+ connectedSmartContractWallets = getAccountDropdownItems(accountProfiles)
}
}
@@ -163,6 +166,31 @@ export async function createAuthzParamCookieAndCreate(
throw new BadRequestError({ message: 'Invalid create_type' })
}
throw redirect(redirectURL, {
- headers: await createAuthorizationParamsCookieHeaders(authzParams, env),
+ headers: await createAuthorizationParamsCookieHeaders(
+ request,
+ authzParams,
+ env
+ ),
})
}
+
+export async function createNewSCWallet({
+ nickname,
+ primaryAccountURN,
+ env,
+ traceSpan,
+}: {
+ nickname: string
+ primaryAccountURN: AccountURN
+ env: Env
+ traceSpan?: any
+}) {
+ const context = { env: { Core: env.Core }, traceSpan }
+ const coreClient = getCoreClient({ context, accountURN: primaryAccountURN })
+ const { accountURN } = await coreClient.account.initSmartContractWallet.query(
+ {
+ nickname,
+ }
+ )
+ return { accountURN }
+}
diff --git a/apps/passport/app/utils/connect-proxy.ts b/apps/passport/app/utils/connect-proxy.ts
new file mode 100644
index 0000000000..7966c3758d
--- /dev/null
+++ b/apps/passport/app/utils/connect-proxy.ts
@@ -0,0 +1,51 @@
+import { redirect } from '@remix-run/cloudflare'
+import type { AppLoadContext } from '@remix-run/cloudflare'
+
+import { getAuthnParams } from '~/auth.server'
+
+export const setCustomDomainOrigin = (
+ request: Request,
+ context: AppLoadContext,
+ authnParams: URLSearchParams
+) => {
+ const referer = request.headers.get('referer')
+ if (!referer) return
+
+ const { host, origin } = new URL(referer)
+ if (context.env.DEFAULT_HOSTS.includes(host)) return
+
+ authnParams.set('origin', origin)
+}
+
+export const redirectToDefaultHost = (
+ request: Request,
+ response: Response,
+ context: AppLoadContext
+) => {
+ const url = new URL(request.url)
+ if (context.env.DEFAULT_HOSTS.includes(url.host)) throw response
+
+ url.host = context.env.DEFAULT_HOSTS[0]
+ const { searchParams } = new URL(response.headers.get('location') as string)
+ searchParams.forEach((value, key) => url.searchParams.set(key, value))
+ throw redirect(url.toString(), {
+ headers: {
+ 'set-cookie': response.headers.get('set-cookie') as string,
+ },
+ })
+}
+
+export const redirectToCustomDomainHost = async (
+ request: Request,
+ context: AppLoadContext
+) => {
+ const authnParams = await getAuthnParams(request, context.env)
+ const origin = authnParams.get('origin')
+ if (!origin) return
+
+ const url = new URL(request.url)
+ if (origin != url.origin) {
+ const { pathname, searchParams } = url
+ throw redirect(`${origin}${pathname}?${searchParams.toString()}`)
+ }
+}
diff --git a/apps/passport/app/utils/cookie.ts b/apps/passport/app/utils/cookie.ts
new file mode 100644
index 0000000000..41835ff6b8
--- /dev/null
+++ b/apps/passport/app/utils/cookie.ts
@@ -0,0 +1,14 @@
+import type { Request as CfRequest } from '@cloudflare/workers-types'
+
+type CfHostMetadata = {
+ clientId: string
+}
+
+export const getCookieDomain = (request: Request, env: Env): string => {
+ const host = request.headers.get('host') as string
+ if (env.DEFAULT_HOSTS.includes(host)) return env.COOKIE_DOMAIN
+ const cfRequest = request as unknown as CfRequest
+ const clientId = cfRequest.cf?.hostMetadata.clientId
+ if (clientId) return host
+ return env.COOKIE_DOMAIN
+}
diff --git a/apps/passport/app/utils/emailOTP.ts b/apps/passport/app/utils/emailOTP.ts
new file mode 100644
index 0000000000..55ef6b77c0
--- /dev/null
+++ b/apps/passport/app/utils/emailOTP.ts
@@ -0,0 +1,20 @@
+import { getErrorCause } from '@proofzero/utils/errors'
+
+export const generateEmailOTP = async (
+ email: string
+): Promise<{ message: string; state: string; status: number } | undefined> => {
+ const reqUrl = `/connect/email/otp?email=${encodeURIComponent(email)}`
+
+ const res = await fetch(reqUrl)
+
+ const resObj = await res.json<{
+ message: string
+ state: string
+ }>()
+
+ if (!res.ok) {
+ throw getErrorCause(resObj)
+ }
+
+ return { message: resObj.message, state: resObj.state, status: res.status }
+}
diff --git a/apps/passport/app/utils/profile.ts b/apps/passport/app/utils/profile.ts
index ceef68eb8c..2eb26b6572 100644
--- a/apps/passport/app/utils/profile.ts
+++ b/apps/passport/app/utils/profile.ts
@@ -1,28 +1,31 @@
import {
- EmailAddressType,
- OAuthAddressType,
- CryptoAddressType,
-} from '@proofzero/types/address'
+ EmailAccountType,
+ OAuthAccountType,
+ CryptoAccountType,
+ WebauthnAccountType,
+} from '@proofzero/types/account'
export const getProfileTypeTitle = (type: string) => {
switch (type) {
- case CryptoAddressType.ETH:
+ case CryptoAccountType.ETH:
return 'Ethereum'
- case CryptoAddressType.Wallet:
+ case CryptoAccountType.Wallet:
return 'Smart Wallet'
- case EmailAddressType.Email:
+ case EmailAccountType.Email:
return 'Email'
- case OAuthAddressType.Apple:
+ case WebauthnAccountType.WebAuthN:
+ return 'Passkey'
+ case OAuthAccountType.Apple:
return 'Apple'
- case OAuthAddressType.Discord:
+ case OAuthAccountType.Discord:
return 'Discord'
- case OAuthAddressType.GitHub:
+ case OAuthAccountType.GitHub:
return 'GitHub'
- case OAuthAddressType.Google:
+ case OAuthAccountType.Google:
return 'Google'
- case OAuthAddressType.Microsoft:
+ case OAuthAccountType.Microsoft:
return 'Microsoft'
- case OAuthAddressType.Twitter:
+ case OAuthAccountType.Twitter:
return 'Twitter'
default:
return ''
diff --git a/apps/passport/app/web3/lazyAuth.tsx b/apps/passport/app/web3/lazyAuth.tsx
new file mode 100644
index 0000000000..fdd20666b0
--- /dev/null
+++ b/apps/passport/app/web3/lazyAuth.tsx
@@ -0,0 +1,43 @@
+import { getDefaultConfig, ConnectKitProvider } from 'connectkit'
+import { useHydrated } from 'remix-utils'
+import { createConfig, WagmiConfig } from 'wagmi'
+import {
+ mainnet,
+ polygon,
+ optimism,
+ filecoin,
+ arbitrum,
+} from '@wagmi/core/chains'
+
+export function LazyAuth({
+ autoConnect = false,
+ children,
+}: {
+ context?: any
+ autoConnect?: boolean
+ children: JSX.Element
+}) {
+ const hydrated = useHydrated()
+ if (!hydrated) return null
+
+ const config = createConfig(
+ getDefaultConfig({
+ appName: 'Rollup',
+ autoConnect,
+ alchemyId: window.ENV.APIKEY_ALCHEMY_PUBLIC,
+ walletConnectProjectId: window.ENV.WALLET_CONNECT_PROJECT_ID,
+ chains: [mainnet, polygon, optimism, filecoin, arbitrum],
+ })
+ )
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/apps/passport/bindings.d.ts b/apps/passport/bindings.d.ts
index 9259fc25ec..bd403058e3 100644
--- a/apps/passport/bindings.d.ts
+++ b/apps/passport/bindings.d.ts
@@ -1,25 +1,25 @@
-import { TraceSpan } from '@proofzero/platform-middleware/trace'
+import type { TraceSpan } from '@proofzero/platform-middleware/trace'
+import type { GetAppPublicPropsResult } from '@proofzero/platform.starbase/src/jsonrpc/methods/getAppPublicProps'
export const seviceBindings = true
declare global {
interface Env {
- Address: Fetcher
- Account: Fetcher
+ Core: Fetcher
Galaxy: Fetcher
- Access: Fetcher
- Starbase: Fetcher
Images: Fetcher
+ DEFAULT_HOSTS: string[]
+ COOKIE_DOMAIN: string
SECRET_SESSION_KEY: string
SECRET_SESSION_SALT: string
- COOKIE_DOMAIN: string
+ SECRET_WEBAUTHN_SIGNING_KEY: string
PROFILE_APP_URL: string
CONSOLE_APP_URL: string
PASSPORT_REDIRECT_URL: string
APIKEY_ALCHEMY_PUBLIC: string
- REMIX_DEV_SERVER_WS_PORT: number
WALLET_CONNECT_PROJECT_ID: string
+ REMIX_DEV_SERVER_WS_PORT: number
INTERNAL_GOOGLE_ANALYTICS_TAG: string
@@ -46,10 +46,16 @@ declare global {
INTERNAL_DISCORD_OAUTH_CLIENT_ID: string
SECRET_DISCORD_OAUTH_CLIENT_SECRET: string
INTERNAL_DISCORD_OAUTH_CALLBACK_URL: string
+
+ POSTHOG_API_KEY: string
+ POSTHOG_PROXY_HOST: string
+ //Needed to make Remix work with Cloudflare module workers
+ __STATIC_CONTENT: string
}
interface AuthzParams {
clientId: string
+ response_type: string
redirectUri: string
scope: string[]
state: string
@@ -67,5 +73,8 @@ declare module '@remix-run/cloudflare' {
authzQueryParams: AuthzParams
env: Env
traceSpan: TraceSpan
+ waitUntil: (promise: Promise) => void
+ // for custom domains only
+ appProps?: GetAppPublicPropsResult
}
}
diff --git a/apps/passport/package.json b/apps/passport/package.json
index e8c1c1f5ce..8a67bc6589 100644
--- a/apps/passport/package.json
+++ b/apps/passport/package.json
@@ -12,7 +12,7 @@
"dev:css": "npx tailwindcss -o ./app/styles/tailwind.css --watch",
"dev:sass": "sass --watch app/:app/",
"dev:remix": "remix watch",
- "dev:wrangler": "wrangler dev --local --persist",
+ "dev:wrangler": "wrangler dev",
"dev": "echo \"No dev for passport. Please use start.\"",
"start": "env-cmd --file .dev.env run-p 'dev:*'",
"storybook:css": "npx tailwindcss -o ./app/styles/tailwind.css --watch",
@@ -21,9 +21,10 @@
"build-storybook": "npx tailwindcss -o ./app/styles/tailwind.css && build-storybook"
},
"dependencies": {
+ "@ethersproject/keccak256": "5.7.0",
"@proofzero/design-system": "*",
"@proofzero/platform-clients": "workspace:*",
- "@proofzero/platform.access": "workspace:*",
+ "@proofzero/platform.account": "workspace:*",
"@proofzero/utils": "workspace:*",
"@remix-run/cloudflare": "1.14.0",
"@remix-run/cloudflare-workers": "1.14.0",
@@ -31,29 +32,32 @@
"@tailwindcss/forms": "0.5.3",
"connectkit": "1.4.0",
"csp-header": "5.1.0",
+ "fido2-lib": "3.4.1",
"flowbite": "1.6.5",
"flowbite-react": "0.4.3",
"graphql-request": "5.0.0",
"jose": "4.11.0",
+ "posthog-js": "1.71.0",
"react": "18.2.0",
"react-dom": "18.2.0",
- "react-icons": "4.8.0",
+ "react-helmet": "6.1.0",
+ "react-icons": "4.10.1",
"remix-auth": "3.4.0",
"remix-auth-discord": "1.2.1",
"remix-auth-github": "1.3.0",
"remix-auth-google": "1.2.0",
"remix-auth-microsoft": "2.0.0",
"remix-auth-oauth2": "1.5.0",
- "remix-auth-twitter": "1.0.0",
+ "remix-auth-twitter": "2.0.1",
"remix-utils": "6.3.0",
- "viem": "0.3.39",
- "wagmi": "1.0.9"
+ "viem": "1.0.0",
+ "wagmi": "1.1.1"
},
"devDependencies": {
"@babel/core": "^7.20.2",
"@cloudflare/workers-types": "4.20221111.1",
"@mdx-js/react": "^1.6.22",
- "@playwright/test": "1.32.3",
+ "@playwright/test": "1.35.1",
"@remix-run/dev": "1.14.0",
"@remix-run/eslint-config": "1.14.0",
"@remix-run/serve": "1.14.0",
@@ -100,10 +104,10 @@
"sass-loader": "13.2.0",
"storybook-addon-sass-postcss": "0.1.3",
"style-loader": "3.3.1",
- "tailwindcss": "3.1.8",
+ "tailwindcss": "3.3.3",
"typescript": "5.0.4",
"webpack": "5.75.0",
- "wrangler": "2.14.0"
+ "wrangler": "3.2.0"
},
"engines": {
"node": ">=16.13"
diff --git a/apps/passport/remix.config.js b/apps/passport/remix.config.js
index 30e4a2b896..555b07db2c 100644
--- a/apps/passport/remix.config.js
+++ b/apps/passport/remix.config.js
@@ -1,9 +1,18 @@
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
- serverBuildTarget: 'cloudflare-workers',
server: './server.ts',
devServerBroadcastDelay: 1000,
ignoredRouteFiles: ['**/.*'],
+ // Bundles everything _except_ `__STATIC_CONTENT_MANIFEST`. Be careful if editing.
+ serverDependenciesToBundle: [/^(?!(__STATIC_CONTENT_MANIFEST)$).*$/u],
+ // Optional, but highly recommended for fitting within the Worker bundle size
+ serverMinify: true,
+ serverModuleFormat: 'esm',
+ // YMMV with setting to `node`, most likely anything but simple code won’t work with the CF node compat layer
+ serverPlatform: 'neutral',
+ serverMainFields: ['browser', 'module', 'main'],
+ // Try the `workerd` condition first (this is new and slowly standardising), then `worker`, then `browser` (equivalent of `serverPlatform: browser` but without extra behaviour.
+ serverConditions: ['workerd', 'worker', 'browser'],
// Keycloak rewrites. Needed here for CSR, also done in Cloudflare for SSR.
routes: async (defineRoutes) => {
@@ -11,20 +20,20 @@ module.exports = {
route('/protocol/openid-connect/auth', 'routes/authorize.tsx', {
id: 'keycloakAuthIndex',
caseSensitive: true,
- index: true
+ index: true,
})
-
+
route('/protocol/openid-connect/token', 'routes/token.tsx', {
id: 'keycloakToken',
caseSensitive: true,
- index: true
+ index: true,
})
-
+
route('/protocol/openid-connect/userinfo', 'routes/userinfo.tsx', {
id: 'keycloakUserInfo',
caseSensitive: true,
- index: true
+ index: true,
})
})
- }
+ },
}
diff --git a/apps/passport/server.ts b/apps/passport/server.ts
index 4afde0ec8a..344caa73f4 100644
--- a/apps/passport/server.ts
+++ b/apps/passport/server.ts
@@ -1,16 +1,33 @@
-import {
- generateTraceSpan,
- TraceableFetchEvent,
-} from '@proofzero/platform-middleware/trace'
+import type { Request as CfRequest } from '@cloudflare/workers-types'
import {
createRequestHandler,
handleAsset,
} from '@remix-run/cloudflare-workers'
import * as build from '@remix-run/dev/server-build'
+import createCoreClient from '@proofzero/platform-clients/core'
+import {
+ generateTraceContextHeaders,
+ generateTraceSpan,
+} from '@proofzero/platform-middleware/trace'
+import type { TraceableFetchEvent } from '@proofzero/platform-middleware/trace'
+import type { GetAppPublicPropsResult } from '@proofzero/platform/starbase/src/jsonrpc/methods/getAppPublicProps'
+import manifestJSON from '__STATIC_CONTENT_MANIFEST'
+import { getCoreClient } from '~/platform.server'
+let manifest = JSON.parse(manifestJSON)
+
+type CfHostMetadata = {
+ clientId: string
+}
+
+type CustomDomainRequest = Request & {
+ app_props: GetAppPublicPropsResult
+}
+
export function parseParams(request: Request) {
const url = new URL(request.url)
const clientId = url.searchParams.get('client_id') || ''
+ const responseType = url.searchParams.get('response_type') || ''
const state = url.searchParams.get('state') || ''
const redirectUri = url.searchParams.get('redirect_uri') || ''
const scope = url.searchParams.get('scope')
@@ -28,6 +45,7 @@ export function parseParams(request: Request) {
return {
clientId,
+ responseType,
state,
redirectUri,
scope: decodedScope ? decodedScope.split(' ') : [],
@@ -38,51 +56,88 @@ export function parseParams(request: Request) {
}
}
-const requestHandler = createRequestHandler({
- build,
- mode: process.env.NODE_ENV,
- getLoadContext: (event) => {
- const traceSpan = (event as TraceableFetchEvent).traceSpan
- return {
- authzQueryParams: parseParams(event.request),
- env: globalThis as unknown as Env,
- traceSpan,
- }
- },
-})
+const handleEvent = async (event: FetchEvent, env: Env) => {
+ let response = await handleAsset(event, build, {
+ ASSET_NAMESPACE: env.__STATIC_CONTENT,
+ ASSET_MANIFEST: manifest,
+ })
+ if (response) return response
-const handleEvent = async (event: FetchEvent) => {
- let response = await handleAsset(event, build)
+ //Create a new trace span with no parent
+ const newTraceSpan = generateTraceSpan()
- if (!response) {
- //Create a new trace span with no parent
- const newTraceSpan = generateTraceSpan()
+ const reqURL = new URL(event.request.url)
+ //Have to force injection of new field so it is available in the context setup above
+ const newEvent = Object.assign(event, { traceSpan: newTraceSpan })
- const reqURL = new URL(event.request.url)
- //Have to force injection of new field so it is available in the context setup above
- const newEvent = Object.assign(event, { traceSpan: newTraceSpan })
+ console.debug(
+ `Started HTTP handler for ${reqURL.pathname}/${reqURL.searchParams}`,
+ newTraceSpan.toString()
+ )
- console.debug(
- `Started HTTP handler for ${reqURL.pathname}/${reqURL.searchParams}`,
- newTraceSpan.toString()
+ const request = event.request as unknown as CfRequest
+ const host = request.headers.get('host') as string
+ if (!env.DEFAULT_HOSTS.includes(host)) {
+ const clientId = request.cf?.hostMetadata?.clientId
+ if (!clientId) return new Response(null, { status: 404 })
+ const coreClient = createCoreClient(
+ env.Core,
+ generateTraceContextHeaders(newTraceSpan)
)
+
try {
- response = await requestHandler(newEvent)
- } finally {
- console.debug(
- `Completed HTTP handler ${
- response?.status && response?.status >= 400 && response?.status <= 599
- ? 'with errors '
- : ''
- }for ${reqURL.pathname}/${reqURL.searchParams}`,
- newTraceSpan.toString()
- )
+ const app = await coreClient.starbase.getAppPublicProps.query({
+ clientId,
+ })
+ newEvent.request.app_props = app
+ const { customDomain } = app
+ if (!customDomain) return new Response(null, { status: 404 })
+ if (!customDomain.isActive || host !== customDomain?.hostname)
+ return new Response(null, { status: 404 })
+ } catch (error) {
+ return new Response(null, { status: 500 })
}
}
+ const requestHandler = createRequestHandler({
+ build,
+ mode: process.env.NODE_ENV,
+ getLoadContext: (event) => {
+ const authzQueryParams = parseParams(event.request)
+ const traceSpan = (event as TraceableFetchEvent).traceSpan
+ return {
+ authzQueryParams,
+ appProps: (event.request as CustomDomainRequest).app_props,
+ env,
+ traceSpan,
+ waitUntil: event.waitUntil,
+ }
+ },
+ })
+
+ try {
+ response = await requestHandler(newEvent)
+ } finally {
+ console.debug(
+ `Completed HTTP handler ${
+ response?.status && response?.status >= 400 && response?.status <= 599
+ ? 'with errors '
+ : ''
+ }for ${reqURL.pathname}/${reqURL.searchParams}`,
+ newTraceSpan.toString()
+ )
+ }
+
return response
}
-addEventListener('fetch', async (event: FetchEvent) => {
- event.respondWith(handleEvent(event))
-})
+export default {
+ async fetch(req: Request, env: Env, ctx: ExecutionContext) {
+ //This is the smallest set of event props Remix needs to handle assets correctly
+ const event = {
+ request: req,
+ waitUntil: ctx.waitUntil.bind(ctx),
+ } as FetchEvent
+ return await handleEvent(event, env)
+ },
+}
diff --git a/apps/passport/tailwind.config.js b/apps/passport/tailwind.config.js
index dec84a136a..2621547fd2 100644
--- a/apps/passport/tailwind.config.js
+++ b/apps/passport/tailwind.config.js
@@ -1,12 +1,54 @@
/** @type {import('tailwindcss').Config} */
+
+function withOpacity(variableName) {
+ return ({ opacityValue }) => {
+ if (opacityValue !== undefined) {
+ return `rgba(var(${variableName}), ${opacityValue})`
+ }
+ return `rgb(var(${variableName}))`
+ }
+}
+
module.exports = {
+ darkMode: 'class',
content: [
'./app/**/*.{ts,tsx,jsx,js,mdx}',
'./node_modules/flowbite/**/*.js',
'../../packages/design-system/src/**/*.{ts,tsx,jsx,js}',
],
theme: {
- extend: {},
+ extend: {
+ colors: {
+ 'dull-white': '#f9fafb',
+ },
+ textColor: {
+ skin: {
+ primary: withOpacity('--color-primary'),
+ text: withOpacity('--color-primary-contrast-text'),
+ },
+ },
+ backgroundColor: {
+ skin: {
+ primary: withOpacity('--color-primary'),
+ },
+ },
+ ringColor: {
+ skin: {
+ primary: withOpacity('--color-primary'),
+ },
+ },
+ borderColor: {
+ skin: {
+ primary: withOpacity('--color-primary'),
+ },
+ },
+ boxShadowColor: {
+ skin: {
+ primary: withOpacity('--color-primary'),
+ },
+ },
+ },
},
+ safelist: ['lg:rounded-sm', 'lg:rounded-md', 'lg:rounded-lg'],
plugins: [require('flowbite/plugin')],
}
diff --git a/apps/passport/tests/passport.spec.ts b/apps/passport/tests/passport.spec.ts
index 69058ee709..bb0676f86c 100644
--- a/apps/passport/tests/passport.spec.ts
+++ b/apps/passport/tests/passport.spec.ts
@@ -56,7 +56,7 @@ test('login to passport using Email', async ({ page, request }) => {
await page.getByRole('button').filter({ hasText: 'Verify' }).click()
await page.waitForURL(/.*settings\/dashboard/, {
- timeout: 10000,
+ timeout: 15000,
waitUntil: 'networkidle',
})
await expect(page).toHaveURL(/.*settings\/dashboard/)
diff --git a/apps/passport/wrangler.current.toml b/apps/passport/wrangler.current.toml
new file mode 100644
index 0000000000..c8cf6baed8
--- /dev/null
+++ b/apps/passport/wrangler.current.toml
@@ -0,0 +1,41 @@
+name = "passport"
+main = "./build/index.js"
+compatibility_date = "2022-04-05"
+compatibility_flags = ["streams_enable_constructors"]
+node_compat = true
+workers_dev = false
+logpush = true
+
+[build]
+command = "yarn build"
+
+[site]
+bucket = "./public"
+
+[env.current]
+routes = [
+ { pattern = "passport.rollup.id", custom_domain = true, zone_name = "rollup.id" },
+ { pattern = "passport.pz3r0.com", custom_domain = true, zone_name = "pz3r0.com" },
+]
+
+services = [
+ { binding = "Core", service = "core-current" },
+ { binding = "Images", service = "images-current" },
+]
+
+[env.current.vars]
+DEFAULT_HOSTS = ["passport.rollup.id", "passport.pz3r0.com"]
+COOKIE_DOMAIN = "rollup.id"
+PROFILE_APP_URL = "https://my.rollup.id"
+CONSOLE_APP_URL = "https://console.rollup.id"
+PASSPORT_REDIRECT_URL = "https://passport.rollup.id/connect/token"
+INTERNAL_GOOGLE_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/google/callback"
+INTERNAL_GITHUB_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/github/callback"
+INTERNAL_TWITTER_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/twitter/callback"
+INTERNAL_APPLE_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/apple/callback"
+INTERNAL_MICROSOFT_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/microsoft/callback"
+INTERNAL_DISCORD_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/discord/callback"
+INTERNAL_GOOGLE_ANALYTICS_TAG = "AW-11277204852"
+WALLET_CONNECT_PROJECT_ID = "6f2ebc8aa6a1d5c4d4ac1e700294e35f"
+POSTHOG_API_KEY = "phc_QGmYrKfXcyIAUBBTX3NBDJjNg2MX24ijFemeUuykzWr"
+POSTHOG_PROXY_HOST = "https://analytics.rollup.id"
diff --git a/apps/passport/wrangler.dev.toml b/apps/passport/wrangler.dev.toml
new file mode 100644
index 0000000000..4d0929b3e6
--- /dev/null
+++ b/apps/passport/wrangler.dev.toml
@@ -0,0 +1,41 @@
+name = "passport"
+main = "./build/index.js"
+compatibility_date = "2022-04-05"
+compatibility_flags = ["streams_enable_constructors"]
+node_compat = true
+workers_dev = false
+logpush = true
+
+[build]
+command = "yarn build"
+
+[site]
+bucket = "./public"
+
+[env.dev]
+routes = [
+ { pattern = "passport-dev.rollup.id", custom_domain = true, zone_name = "rollup.id" },
+ { pattern = "passport-dev.pz3r0.com", custom_domain = true, zone_name = "pz3r0.com" },
+]
+
+services = [
+ { binding = "Core", service = "core-dev" },
+ { binding = "Images", service = "images-dev" },
+]
+
+[env.dev.vars]
+DEFAULT_HOSTS = ["passport-dev.rollup.id", "passport-dev.pz3r0.com"]
+COOKIE_DOMAIN = "rollup.id"
+PROFILE_APP_URL = "https://my-dev.rollup.id"
+CONSOLE_APP_URL = "https://console-dev.rollup.id"
+PASSPORT_REDIRECT_URL = "https://passport-dev.rollup.id/connect/token"
+INTERNAL_GOOGLE_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/google/callback"
+INTERNAL_GITHUB_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/github/callback"
+INTERNAL_TWITTER_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/twitter/callback"
+INTERNAL_APPLE_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/apple/callback"
+INTERNAL_MICROSOFT_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/microsoft/callback"
+INTERNAL_DISCORD_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/discord/callback"
+INTERNAL_GOOGLE_ANALYTICS_TAG = "G-NHNH4KRWC3"
+WALLET_CONNECT_PROJECT_ID = "249578b973e49826abb32d2ad263e2a3"
+POSTHOG_API_KEY = "phc_f7q7V62YWsS0FKyIRxO36xyUMFYJ8oahurHPBAXkGe5"
+POSTHOG_PROXY_HOST = "https://analytics.rollup.id"
diff --git a/apps/passport/wrangler.next.toml b/apps/passport/wrangler.next.toml
new file mode 100644
index 0000000000..03eca8fd20
--- /dev/null
+++ b/apps/passport/wrangler.next.toml
@@ -0,0 +1,41 @@
+name = "passport"
+main = "./build/index.js"
+compatibility_date = "2022-04-05"
+compatibility_flags = ["streams_enable_constructors"]
+node_compat = true
+workers_dev = false
+logpush = true
+
+[build]
+command = "yarn build"
+
+[site]
+bucket = "./public"
+
+[env.next]
+routes = [
+ { pattern = "passport-next.rollup.id", custom_domain = true, zone_name = "rollup.id" },
+ { pattern = "passport-next.pz3r0.com", custom_domain = true, zone_name = "pz3r0.com" },
+]
+
+services = [
+ { binding = "Core", service = "core-next" },
+ { binding = "Images", service = "images-next" },
+]
+
+[env.next.vars]
+DEFAULT_HOSTS = ["passport-next.rollup.id", "passport-next.pz3r0.com"]
+COOKIE_DOMAIN = "rollup.id"
+PROFILE_APP_URL = "https://my-next.rollup.id"
+CONSOLE_APP_URL = "https://console-next.rollup.id"
+PASSPORT_REDIRECT_URL = "https://passport-next.rollup.id/connect/token"
+INTERNAL_GOOGLE_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/google/callback"
+INTERNAL_GITHUB_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/github/callback"
+INTERNAL_TWITTER_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/twitter/callback"
+INTERNAL_APPLE_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/apple/callback"
+INTERNAL_MICROSOFT_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/microsoft/callback"
+INTERNAL_DISCORD_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/discord/callback"
+INTERNAL_GOOGLE_ANALYTICS_TAG = "G-X7ZN16M4NB"
+WALLET_CONNECT_PROJECT_ID = "7bce942e261433eb70c2a72d555dd61f"
+POSTHOG_API_KEY = "phc_JsZaA3PKO8jNF6gJ7HusRN3C7yzp56JakJJZfNRI9n7"
+POSTHOG_PROXY_HOST = "https://analytics.rollup.id"
diff --git a/apps/passport/wrangler.toml b/apps/passport/wrangler.toml
index 7c29a5bd46..c7d968ed0b 100644
--- a/apps/passport/wrangler.toml
+++ b/apps/passport/wrangler.toml
@@ -1,36 +1,33 @@
-# https://developers.cloudflare.com/workers/platform/compatibility-dates
-compatibility_date = "2022-04-05"
-main = "./build/index.js"
name = "passport"
-workers_dev = false
+main = "./build/index.js"
+compatibility_date = "2022-04-05"
compatibility_flags = ["streams_enable_constructors"]
+node_compat = true
+workers_dev = false
logpush = true
-# Services binding for local development
services = [
- { binding = "Account", service = "account" },
- { binding = "Access", service = "access" },
- { binding = "Address", service = "address" },
- { binding = "Starbase", service = "starbase" },
+ { binding = "Core", service = "core" },
{ binding = "Images", service = "images" },
]
-[site]
-bucket = "./public"
-
[build]
command = "yarn build -- --sourcemap"
+[site]
+bucket = "./public"
+
[dev]
port = 10001
inspector_port = 11001
local_protocol = "http"
[vars]
+DEFAULT_HOSTS = ["localhost:10001", "127.0.0.1:10001"]
+COOKIE_DOMAIN = "localhost"
PROFILE_APP_URL = "http://localhost:10003"
CONSOLE_APP_URL = "http://localhost:10002"
PASSPORT_REDIRECT_URL = "http://localhost:10001/connect/token"
-COOKIE_DOMAIN = "localhost"
INTERNAL_GOOGLE_OAUTH_CALLBACK_URL = "http://localhost:10001/connect/google/callback"
INTERNAL_GITHUB_OAUTH_CALLBACK_URL = "http://localhost:10001/connect/github/callback"
INTERNAL_TWITTER_OAUTH_CALLBACK_URL = "http://localhost:10001/connect/twitter/callback"
@@ -39,91 +36,5 @@ INTERNAL_APPLE_OAUTH_CALLBACK_URL = "http://localhost:10001/connect/apple/callba
INTERNAL_DISCORD_OAUTH_CALLBACK_URL = "http://localhost:10001/connect/discord/callback"
INTERNAL_GOOGLE_ANALYTICS_TAG = "G-NHNH4KRWC3"
WALLET_CONNECT_PROJECT_ID = "36efbf71d7586e254d72041a15a42078"
-
-[env.dev]
-routes = [
- { pattern = "passport-dev.rollup.id", custom_domain = true, zone_name = "rollup.id" },
- { pattern = "passport-dev.pz3r0.com", custom_domain = true, zone_name = "pz3r0.com" },
-
-]
-services = [
- { binding = "Account", service = "account-dev" },
- { binding = "Access", service = "access-dev" },
- { binding = "Address", service = "address-dev" },
- { binding = "Starbase", service = "starbase-dev" },
- { binding = "Images", service = "images-dev" },
-]
-
-[env.dev.vars]
-PROFILE_APP_URL = "https://my-dev.rollup.id"
-CONSOLE_APP_URL = "https://console-dev.rollup.id"
-PASSPORT_REDIRECT_URL = "https://passport-dev.rollup.id/connect/token"
-COOKIE_DOMAIN = "rollup.id"
-INTERNAL_GOOGLE_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/google/callback"
-INTERNAL_GITHUB_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/github/callback"
-INTERNAL_TWITTER_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/twitter/callback"
-INTERNAL_APPLE_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/apple/callback"
-INTERNAL_MICROSOFT_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/microsoft/callback"
-INTERNAL_DISCORD_OAUTH_CALLBACK_URL = "https://passport-dev.rollup.id/connect/discord/callback"
-INTERNAL_GOOGLE_ANALYTICS_TAG = "G-NHNH4KRWC3"
-WALLET_CONNECT_PROJECT_ID = "249578b973e49826abb32d2ad263e2a3"
-
-[env.next]
-routes = [
- { pattern = "passport-next.rollup.id", custom_domain = true, zone_name = "rollup.id" },
- { pattern = "passport-next.pz3r0.com", custom_domain = true, zone_name = "pz3r0.com" },
-]
-services = [
- { binding = "Account", service = "account-next" },
- { binding = "Access", service = "access-next" },
- { binding = "Address", service = "address-next" },
- { binding = "Starbase", service = "starbase-next" },
- { binding = "Images", service = "images-next" },
-]
-
-[env.next.vars]
-PROFILE_APP_URL = "https://my-next.rollup.id"
-CONSOLE_APP_URL = "https://console-next.rollup.id"
-PASSPORT_REDIRECT_URL = "https://passport-next.rollup.id/connect/token"
-COOKIE_DOMAIN = "rollup.id"
-INTERNAL_GOOGLE_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/google/callback"
-INTERNAL_GITHUB_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/github/callback"
-INTERNAL_TWITTER_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/twitter/callback"
-INTERNAL_APPLE_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/apple/callback"
-INTERNAL_MICROSOFT_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/microsoft/callback"
-INTERNAL_DISCORD_OAUTH_CALLBACK_URL = "https://passport-next.rollup.id/connect/discord/callback"
-INTERNAL_GOOGLE_ANALYTICS_TAG = "G-X7ZN16M4NB"
-WALLET_CONNECT_PROJECT_ID = "7bce942e261433eb70c2a72d555dd61f"
-
-[env.next.build]
-command = "yarn build"
-
-[env.current]
-routes = [
- { pattern = "passport.rollup.id", custom_domain = true, zone_name = "rollup.id" },
- { pattern = "passport.pz3r0.com", custom_domain = true, zone_name = "pz3r0.com" },
-]
-services = [
- { binding = "Account", service = "account-current" },
- { binding = "Access", service = "access-current" },
- { binding = "Address", service = "address-current" },
- { binding = "Starbase", service = "starbase-current" },
- { binding = "Images", service = "images-current" },
-]
-
-[env.current.vars]
-PROFILE_APP_URL = "https://my.rollup.id"
-CONSOLE_APP_URL = "https://console.rollup.id"
-PASSPORT_REDIRECT_URL = "https://passport.rollup.id/connect/token"
-COOKIE_DOMAIN = "rollup.id"
-INTERNAL_GOOGLE_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/google/callback"
-INTERNAL_GITHUB_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/github/callback"
-INTERNAL_TWITTER_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/twitter/callback"
-INTERNAL_APPLE_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/apple/callback"
-INTERNAL_MICROSOFT_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/microsoft/callback"
-INTERNAL_DISCORD_OAUTH_CALLBACK_URL = "https://passport.rollup.id/connect/discord/callback"
-INTERNAL_GOOGLE_ANALYTICS_TAG = "G-675VJMWSRY"
-WALLET_CONNECT_PROJECT_ID = "6f2ebc8aa6a1d5c4d4ac1e700294e35f"
-
-[env.current.build]
-command = "yarn build"
+POSTHOG_API_KEY = "phc_f7q7V62YWsS0FKyIRxO36xyUMFYJ8oahurHPBAXkGe5"
+POSTHOG_PROXY_HOST = "https://analytics.rollup.id"
diff --git a/apps/profile/app/components/addresses/AddressList.stories.tsx b/apps/profile/app/components/accounts/AccountList.stories.tsx
similarity index 66%
rename from apps/profile/app/components/addresses/AddressList.stories.tsx
rename to apps/profile/app/components/accounts/AccountList.stories.tsx
index 528182e5aa..70eb8a9cd9 100644
--- a/apps/profile/app/components/addresses/AddressList.stories.tsx
+++ b/apps/profile/app/components/accounts/AccountList.stories.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-import { AddressList } from './AddressList'
-import type { AddressListItemProps } from './AddressListItem'
+import { AccountList } from './AccountList'
+import type { AccountListItemProps } from './AccountListItem'
import {
blue,
@@ -9,11 +9,11 @@ import {
} from '@proofzero/design-system/src/placeholders/rollup/b64'
export default {
- title: 'Molecules/Addresses/List',
- component: AddressList,
+ title: 'Molecules/Accounts/List',
+ component: AccountList,
}
-const addresses: AddressListItemProps[] = [
+const accounts: AccountListItemProps[] = [
{
id: '1',
title: 'Ondrej.eth',
@@ -21,7 +21,7 @@ const addresses: AddressListItemProps[] = [
wallet: 'Metamask',
network: 'Ethereum',
chain: 'Mainnet',
- address: '0x3c153bE191088a34bAc04013b511EF538718d645',
+ account: '0x3c153bE191088a34bAc04013b511EF538718d645',
primary: true,
onRenameAccount: (id) => {},
onChangeAvatar: (id) => {},
@@ -36,7 +36,7 @@ const addresses: AddressListItemProps[] = [
wallet: 'Metamask',
network: 'Ethereum',
chain: 'Mainnet',
- address: '0x2062dDb9924991c5CfB6af89A12FB4F405965d3d',
+ account: '0x2062dDb9924991c5CfB6af89A12FB4F405965d3d',
onRenameAccount: (id) => {},
onChangeAvatar: (id) => {},
onDisconnect: (id) => {},
@@ -48,11 +48,11 @@ const addresses: AddressListItemProps[] = [
wallet: 'Metamask',
network: 'Polygon',
chain: 'Mainnet',
- address: '0x012AC2a88F9244f004255b29AFCD349beE097B48',
+ account: '0x012AC2a88F9244f004255b29AFCD349beE097B48',
hidden: true,
},
]
-const Template = () =>
+const Template = () =>
export const Default = Template.bind({})
diff --git a/apps/profile/app/components/accounts/AccountList.tsx b/apps/profile/app/components/accounts/AccountList.tsx
new file mode 100644
index 0000000000..f10ee67ae0
--- /dev/null
+++ b/apps/profile/app/components/accounts/AccountList.tsx
@@ -0,0 +1,23 @@
+import { List } from '@proofzero/design-system/src/atoms/lists/List'
+import type { AccountListItemProps } from './AccountListItem'
+import { AccountListItem } from './AccountListItem'
+import { Text } from '@proofzero/design-system/src/atoms/text/Text'
+
+export type AccountListProps = {
+ accounts: AccountListItemProps[]
+}
+
+export const AccountList = ({ accounts }: AccountListProps) => {
+ return accounts.length ? (
+ ({ key: ali.id, val: ali }))}
+ itemRenderer={(item) => }
+ />
+ ) : (
+
+
+ No Vaults Account Detected ☹️
+
+
+ )
+}
diff --git a/apps/profile/app/components/addresses/AddressListItem.stories.tsx b/apps/profile/app/components/accounts/AccountListItem.stories.tsx
similarity index 74%
rename from apps/profile/app/components/addresses/AddressListItem.stories.tsx
rename to apps/profile/app/components/accounts/AccountListItem.stories.tsx
index 2225fc136c..3460121f7c 100644
--- a/apps/profile/app/components/addresses/AddressListItem.stories.tsx
+++ b/apps/profile/app/components/accounts/AccountListItem.stories.tsx
@@ -1,11 +1,11 @@
import React from 'react'
-import { AddressListItem } from './AddressListItem'
+import { AccountListItem } from './AccountListItem'
import { blue } from '@proofzero/design-system/src/placeholders/rollup/b64'
export default {
- title: 'Atoms/Addresses/List item',
- component: AddressListItem,
+ title: 'Atoms/Accounts/List item',
+ component: AccountListItem,
argTypes: {
id: {
defaultValue: 'Id String',
@@ -25,12 +25,12 @@ export default {
chain: {
defaultValue: 'Mainnet',
},
- address: {
+ account: {
defaultValue: '0x6c60Da9471181Aa54C548c6e201263A5801363F3',
},
},
}
-const Template = (args: any) =>
+const Template = (args: any) =>
export const Default = Template.bind({})
diff --git a/apps/profile/app/components/addresses/AddressListItem.tsx b/apps/profile/app/components/accounts/AccountListItem.tsx
similarity index 95%
rename from apps/profile/app/components/addresses/AddressListItem.tsx
rename to apps/profile/app/components/accounts/AccountListItem.tsx
index be6f543fb7..d8c6a23911 100644
--- a/apps/profile/app/components/addresses/AddressListItem.tsx
+++ b/apps/profile/app/components/accounts/AccountListItem.tsx
@@ -13,21 +13,21 @@ import { Menu, Transition } from '@headlessui/react'
import { Fragment } from 'react'
import { getProfileTypeTitle } from '../../helpers/profile'
-type AddressListItemIconProps = {
+type AccountListItemIconProps = {
iconUrl: string
}
-export const AddressListItemIcon = ({ iconUrl }: AddressListItemIconProps) => (
+export const AccountListItemIcon = ({ iconUrl }: AccountListItemIconProps) => (
)
-export type AddressListItemProps = {
+export type AccountListItemProps = {
id: string
icon: string
title: string
type: string
- address: string
+ account: string
primary?: boolean
hidden?: boolean
wallet?: string
@@ -38,12 +38,12 @@ export type AddressListItemProps = {
onSetPrivate?: (id: string) => void
onDisconnect?: (id: string) => void
}
-export const AddressListItem = ({
+export const AccountListItem = ({
id,
icon,
title,
type,
- address,
+ account,
primary = false,
hidden = false,
onRenameAccount,
@@ -51,7 +51,7 @@ export const AddressListItem = ({
onSetPrimary,
onSetPrivate,
onDisconnect,
-}: AddressListItemProps) => {
+}: AccountListItemProps) => {
const hasBehavior =
onRenameAccount ||
onChangeAvatar ||
@@ -62,7 +62,7 @@ export const AddressListItem = ({
return (
@@ -78,7 +78,7 @@ export const AddressListItem = ({
- {getProfileTypeTitle(type)} • {address}
+ {getProfileTypeTitle(type)} • {account}
diff --git a/apps/profile/app/components/accounts/NoCryptoAddresses.tsx b/apps/profile/app/components/accounts/NoCryptoAccounts.tsx
similarity index 76%
rename from apps/profile/app/components/accounts/NoCryptoAddresses.tsx
rename to apps/profile/app/components/accounts/NoCryptoAccounts.tsx
index c0ea5fb806..11c637ee32 100644
--- a/apps/profile/app/components/accounts/NoCryptoAddresses.tsx
+++ b/apps/profile/app/components/accounts/NoCryptoAccounts.tsx
@@ -1,14 +1,14 @@
import { Button } from '@proofzero/design-system'
import { Text } from '@proofzero/design-system'
-const NoCryptoAddresses = ({
+const NoCryptoAccounts = ({
redirectHandler,
}: {
redirectHandler: () => void
}) => {
return (
@@ -16,7 +16,7 @@ const NoCryptoAddresses = ({
{
redirectHandler()
@@ -28,4 +28,4 @@ const NoCryptoAddresses = ({
)
}
-export default NoCryptoAddresses
+export default NoCryptoAccounts
diff --git a/apps/profile/app/components/accounts/PfpNftModal.tsx b/apps/profile/app/components/accounts/PfpNftModal.tsx
index 961571a5c1..c73c0becc8 100644
--- a/apps/profile/app/components/accounts/PfpNftModal.tsx
+++ b/apps/profile/app/components/accounts/PfpNftModal.tsx
@@ -5,9 +5,10 @@ import FilteredNftGrid from '../nfts/grid/filtered'
import UnfilteredNftGrid from '../nfts/grid/unfiltered'
import SelectableNft from '../nfts/interactible'
import { LoadingGridSquares } from '../nfts/grid/loading'
+import { HiOutlineX } from 'react-icons/hi'
type PfpNftModalProps = {
- text?: string
+ text: string
nfts: any[]
pfp: string
displayName: string
@@ -37,11 +38,11 @@ const PfpNftModal = ({
return (
- {text?.length && (
+
{text}
- )}
+ {
+ handleClose(false)
+ }}
+ >
+
+
+
{collection.length ? (
{
return (
diff --git a/apps/profile/app/components/accounts/UnsavedChangesModal.tsx b/apps/profile/app/components/accounts/UnsavedChangesModal.tsx
index f1a9a5a5ad..4a087b0c96 100644
--- a/apps/profile/app/components/accounts/UnsavedChangesModal.tsx
+++ b/apps/profile/app/components/accounts/UnsavedChangesModal.tsx
@@ -4,18 +4,36 @@ import SaveButton from './SaveButton'
import { Text } from '@proofzero/design-system'
import warn from '../../assets/warning.svg'
+import { HiOutlineX } from 'react-icons/hi'
-const UnsavedChangesModal = ({ isOpen, handleClose }) => {
+const UnsavedChangesModal = ({
+ isOpen,
+ handleClose,
+}: {
+ isOpen: boolean
+ handleClose: (value: boolean) => void
+}) => {
return (
-
-
+
+
-
- You have Unsaved Changes
-
+
+
+ You have Unsaved Changes
+
+ {
+ handleClose(false)
+ }}
+ >
+
+
+
You have made some changes. Do you want to discard or save them?
diff --git a/apps/profile/app/components/addresses/AddressList.tsx b/apps/profile/app/components/addresses/AddressList.tsx
deleted file mode 100644
index 67f824f304..0000000000
--- a/apps/profile/app/components/addresses/AddressList.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { List } from '@proofzero/design-system/src/atoms/lists/List'
-import type { AddressListItemProps } from './AddressListItem'
-import { AddressListItem } from './AddressListItem'
-import { Text } from '@proofzero/design-system/src/atoms/text/Text'
-
-export type AddressListProps = {
- addresses: AddressListItemProps[]
-}
-
-export const AddressList = ({ addresses }: AddressListProps) => {
- return addresses.length ? (
-
({ key: ali.id, val: ali }))}
- itemRenderer={(item) => }
- />
- ) : (
-
-
- No Vaults Account Detected ☹️
-
-
- )
-}
diff --git a/apps/profile/app/components/cta/cta.tsx b/apps/profile/app/components/cta/cta.tsx
index a77d59aab2..2f28552d4a 100644
--- a/apps/profile/app/components/cta/cta.tsx
+++ b/apps/profile/app/components/cta/cta.tsx
@@ -1,8 +1,7 @@
import { Link } from '@remix-run/react'
import { useState, useEffect } from 'react'
-import { Text } from '@proofzero/design-system/src/atoms/text/Text'
-import { Button } from '@proofzero/design-system/src/atoms/buttons/Button'
+import { Button, Text } from '@proofzero/design-system'
import type { FullProfile } from '../../types'
import type { ProfileCompletionStatus } from '../../utils/cta.client'
@@ -10,10 +9,10 @@ import { determineProfileCompletionStatus } from '../../utils/cta.client'
const CTA = ({
profile,
- addresses,
+ accounts,
}: {
profile: FullProfile
- addresses: any[]
+ accounts: any[]
}) => {
const ctaDict: {
[key: string]: {
@@ -68,7 +67,7 @@ const CTA = ({
}
const handleCompletionStatus = () => {
- const pcs = determineProfileCompletionStatus(profile, addresses)
+ const pcs = determineProfileCompletionStatus(profile, accounts)
let ctaKey
ctaKey = handleCTAKey(pcs, 'gallery') || ctaKey
diff --git a/apps/profile/app/components/head-nav/index.tsx b/apps/profile/app/components/head-nav/index.tsx
index f4ed491d4b..0e63f7dc34 100644
--- a/apps/profile/app/components/head-nav/index.tsx
+++ b/apps/profile/app/components/head-nav/index.tsx
@@ -36,7 +36,7 @@ export const ProfileLogo = () => {
type HeadNavProps = {
open: boolean
- accountURN: string
+ identityURN: string
avatarUrl?: string
displayName?: string
isToken?: boolean
@@ -44,7 +44,7 @@ type HeadNavProps = {
}
export default function HeadNav({
- accountURN,
+ identityURN,
avatarUrl,
loggedIn,
displayName,
@@ -126,8 +126,8 @@ export default function HeadNav({
displayName={displayName}
close={close}
profileUrl={
- accountURN
- ? `/p/${accountURN}`
+ identityURN
+ ? `/p/${identityURN}`
: '/account/connections'
}
/>
@@ -204,7 +204,7 @@ export default function HeadNav({
displayName: displayName!,
}}
close={close}
- accountURN={accountURN!}
+ identityURN={identityURN!}
ref={setReferenceElement}
open={open}
/>
diff --git a/apps/profile/app/components/nfts/modal/index.tsx b/apps/profile/app/components/nfts/modal/index.tsx
index 16d14171e2..463d04bb86 100644
--- a/apps/profile/app/components/nfts/modal/index.tsx
+++ b/apps/profile/app/components/nfts/modal/index.tsx
@@ -1,7 +1,7 @@
import { Text } from '@proofzero/design-system/src/atoms/text/Text'
import { Modal } from '@proofzero/design-system/src/molecules/modal/Modal'
-import { HiChevronDown } from 'react-icons/hi'
+import { HiChevronDown, HiOutlineX } from 'react-icons/hi'
import { gatewayFromIpfs } from '@proofzero/utils'
@@ -31,18 +31,9 @@ const NftModal = ({
return (
@@ -50,7 +41,6 @@ const NftModal = ({
{!imgLoaded && (
)}
-
-
- {nft?.collectionTitle}
-
+
+
+ {nft?.collectionTitle}
+
+ {
+ handleClose(false)
+ }}
+ >
+
+
+
@@ -159,7 +160,7 @@ const NftModal = ({
{
navigator.clipboard.writeText(d.value)
@@ -179,7 +180,7 @@ const NftModal = ({
{d.value}
diff --git a/apps/profile/app/components/nfts/partners.tsx b/apps/profile/app/components/nfts/partners.tsx
index 1b0723ce60..ff5b5d6b39 100644
--- a/apps/profile/app/components/nfts/partners.tsx
+++ b/apps/profile/app/components/nfts/partners.tsx
@@ -45,7 +45,7 @@ const PartnerUrl = ({ title, description, imgSrc, url }: PartnerUrlProps) => {
-
+
Visit website
diff --git a/apps/profile/app/components/profile/links/links.tsx b/apps/profile/app/components/profile/links/links.tsx
index 3bfe66f9a7..966d59edf5 100644
--- a/apps/profile/app/components/profile/links/links.tsx
+++ b/apps/profile/app/components/profile/links/links.tsx
@@ -1,5 +1,5 @@
import { Text } from '@proofzero/design-system/src/atoms/text/Text'
-import { imageFromAddressType } from '../../../helpers'
+import { imageFromAccountType } from '../../../helpers'
type Link = {
name: string
@@ -36,7 +36,7 @@ export const Links = ({ links, isOwner = false, displayName }: LinksProps) => {
{links
.map((link) => ({
...link,
- providerIcon: imageFromAddressType(link.provider),
+ providerIcon: imageFromAccountType(link.provider),
}))
.map((link, i: number) => (
{
return (
)
@@ -59,13 +59,13 @@ export const DesktopSideNav = ({
export const MobileSideNav = ({
profile,
- accountURN,
+ identityURN,
close,
ref,
open,
}: {
profile: { displayName: string; pfp?: { image: string } }
- accountURN: string
+ identityURN: string
close: (
focusableElement?:
| HTMLElement
@@ -101,7 +101,7 @@ export const MobileSideNav = ({
@@ -125,11 +125,11 @@ export const MobileSideNav = ({
export const SideNavBarebone = ({
profile,
- accountURN,
+ identityURN,
close,
}: {
profile: { displayName: string; pfp?: { image: string } }
- accountURN: string
+ identityURN: string
close?: (
focusableElement?:
| HTMLElement
@@ -159,7 +159,7 @@ export const SideNavBarebone = ({
{profile.displayName}
diff --git a/apps/profile/app/helpers/alchemy.ts b/apps/profile/app/helpers/alchemy.ts
index c2233e21aa..3699346ffa 100644
--- a/apps/profile/app/helpers/alchemy.ts
+++ b/apps/profile/app/helpers/alchemy.ts
@@ -13,55 +13,65 @@ import type {
import type { Chain, Gallery, NFT } from '../types'
import { NFTContractNormalizer, NFTNormalizer } from './nfts'
import { sortNftsFn } from './strings'
-import type { AccountURN } from '@proofzero/urns/account'
-import { getAccountCryptoAddresses } from './profile'
+import type { IdentityURN } from '@proofzero/urns/identity'
+import { getIdentityCryptoAddresses } from './profile'
import type { TraceSpan } from '@proofzero/platform-middleware/trace'
// -------------------- TYPES --------------------------------------------------
-const getChainWithNetwork = (chain: AlchemyChain): Chain => {
+const getChainWithNetwork = (chain: AlchemyChain, env: Env): Chain => {
return chain === AlchemyChain.ethereum
? {
chain: AlchemyChain.ethereum,
- network: AlchemyNetwork[ALCHEMY_ETH_NETWORK],
+ network: AlchemyNetwork[env.ALCHEMY_ETH_NETWORK],
}
: {
chain: AlchemyChain.polygon,
- network: AlchemyNetwork[ALCHEMY_POLYGON_NETWORK],
+ network: AlchemyNetwork[env.ALCHEMY_POLYGON_NETWORK],
}
}
-export const getAlchemyClient = (chain: Chain): AlchemyClient => {
+export const getAlchemyClient = (chain: Chain, env: Env): AlchemyClient => {
return new AlchemyClient({
- key: chain.chain === 'eth' ? APIKEY_ALCHEMY_ETH : APIKEY_ALCHEMY_POLYGON,
+ key:
+ chain.chain === 'eth'
+ ? env.APIKEY_ALCHEMY_ETH
+ : env.APIKEY_ALCHEMY_POLYGON,
...chain,
})
}
-export const getAlchemyClients = () => {
+export const getAlchemyClients = (env: Env) => {
return {
ethereumClient: getAlchemyClient(
- getChainWithNetwork(AlchemyChain.ethereum)
+ getChainWithNetwork(AlchemyChain.ethereum, env),
+ env
+ ),
+ polygonClient: getAlchemyClient(
+ getChainWithNetwork(AlchemyChain.polygon, env),
+ env
),
- polygonClient: getAlchemyClient(getChainWithNetwork(AlchemyChain.polygon)),
}
}
// -------------------- ALL NFTS FOR SPECIFIED CONTRACTS -----------------------
-export const getNfts = async ({
- addresses,
- contractAddresses,
- maxRuns = 3,
- chain,
-}: {
- addresses: string[]
- contractAddresses: string[]
- maxRuns?: number
- chain: AlchemyChain
-}) => {
- const chainWithNetwork = getChainWithNetwork(chain)
- const alchemyClient = getAlchemyClient(chainWithNetwork)
+export const getNfts = async (
+ {
+ addresses,
+ contractAddresses,
+ maxRuns = 3,
+ chain,
+ }: {
+ addresses: string[]
+ contractAddresses: string[]
+ maxRuns?: number
+ chain: AlchemyChain
+ },
+ env: Env
+) => {
+ const chainWithNetwork = getChainWithNetwork(chain, env)
+ const alchemyClient = getAlchemyClient(chainWithNetwork, env)
const nfts: NFT[] = []
await Promise.all(
@@ -89,19 +99,22 @@ export const getNfts = async ({
// -------------------- ALL CONTRACTS ------------------------------------------
-export const getContracts = async ({
- addresses,
- excludeFilters,
- maxRuns = 3,
- chain,
-}: {
- addresses: string[]
- excludeFilters: string[]
- maxRuns?: number
- chain: AlchemyChain
-}) => {
- const chainWithNetwork = getChainWithNetwork(chain)
- const alchemyClient = getAlchemyClient(chainWithNetwork)
+export const getContracts = async (
+ {
+ addresses,
+ excludeFilters,
+ maxRuns = 3,
+ chain,
+ }: {
+ addresses: string[]
+ excludeFilters: string[]
+ maxRuns?: number
+ chain: AlchemyChain
+ },
+ env: Env
+) => {
+ const chainWithNetwork = getChainWithNetwork(chain, env)
+ const alchemyClient = getAlchemyClient(chainWithNetwork, env)
let contracts: NFT[] = []
await Promise.all(
@@ -130,28 +143,37 @@ export const getContracts = async ({
return contracts
}
-export const getContractsForAllChains = async ({
- addresses,
- excludeFilters,
-}: {
- addresses: string[]
- excludeFilters: string[]
-}) => {
+export const getContractsForAllChains = async (
+ {
+ addresses,
+ excludeFilters,
+ }: {
+ addresses: string[]
+ excludeFilters: string[]
+ },
+ env: Env
+) => {
// To avoid duplication - if one collection comes from different addresses
const visitedContracts = new Map
()
try {
const [ethereumContracts, polygonContracts] = await Promise.all([
- getContracts({
- addresses,
- excludeFilters,
- chain: AlchemyChain.ethereum,
- }),
- getContracts({
- addresses,
- excludeFilters,
- chain: AlchemyChain.polygon,
- }),
+ getContracts(
+ {
+ addresses,
+ excludeFilters,
+ chain: AlchemyChain.ethereum,
+ },
+ env
+ ),
+ getContracts(
+ {
+ addresses,
+ excludeFilters,
+ chain: AlchemyChain.polygon,
+ },
+ env
+ ),
])
const ownedNfts = ethereumContracts
@@ -174,21 +196,26 @@ export const getContractsForAllChains = async ({
}
}
-export const getValidGallery = async ({
- gallery,
- accountURN,
- traceSpan,
-}: {
- gallery: Gallery
- accountURN: AccountURN
+export const getValidGallery = async (
+ {
+ gallery,
+ identityURN,
+ }: {
+ gallery: Gallery
+ identityURN: IdentityURN
+ },
+ env: Env,
traceSpan: TraceSpan
-}) => {
- const { ethereumClient, polygonClient } = getAlchemyClients()
+) => {
+ const { ethereumClient, polygonClient } = getAlchemyClients(env)
- const cryptoAddresses = await getAccountCryptoAddresses({
- accountURN,
- traceSpan,
- })
+ const cryptoAddresses = await getIdentityCryptoAddresses(
+ {
+ identityURN,
+ },
+ env,
+ traceSpan
+ )
const [ethContractAddressesSet, polyContractAddressesSet] = gallery.reduce(
([ethereum, polygon], nft) => {
diff --git a/apps/profile/app/helpers/clients.ts b/apps/profile/app/helpers/clients.ts
index b7aaf05262..e2e3b7dd31 100644
--- a/apps/profile/app/helpers/clients.ts
+++ b/apps/profile/app/helpers/clients.ts
@@ -3,13 +3,13 @@ import { getSdk } from '@proofzero/galaxy-client'
import { TRACEPARENT_HEADER_NAME } from '@proofzero/platform-middleware/trace'
import { PlatformHeaders } from '@proofzero/platform-clients/base'
-export async function getGalaxyClient(reqHeaders: PlatformHeaders) {
+export async function getGalaxyClient(reqHeaders: PlatformHeaders, env: Env) {
const traceparent = JSON.stringify({
traceparent: reqHeaders ? reqHeaders[TRACEPARENT_HEADER_NAME] : '',
})
const gqlClient = new GraphQLClient('http://127.0.0.1/graphql', {
// @ts-ignore
- fetch: Galaxy.fetch.bind(Galaxy),
+ fetch: env.Galaxy.fetch.bind(env.Galaxy),
requestMiddleware: (r) => {
r.headers = { ...reqHeaders, ...(r.headers as Record) }
console.debug(`Starting GQL client request.`, traceparent)
diff --git a/apps/profile/app/helpers/icons.ts b/apps/profile/app/helpers/icons.ts
index d73a863926..c4114f5c31 100644
--- a/apps/profile/app/helpers/icons.ts
+++ b/apps/profile/app/helpers/icons.ts
@@ -1,8 +1,9 @@
import {
- CryptoAddressType,
- EmailAddressType,
- OAuthAddressType,
-} from '@proofzero/types/address'
+ CryptoAccountType,
+ EmailAccountType,
+ OAuthAccountType,
+ WebauthnAccountType,
+} from '@proofzero/types/account'
import ethereumIcon from '@proofzero/design-system/src/assets/social_icons/ethereum.svg'
import scWalletIcon from '@proofzero/design-system/src/assets/social_icons/sc_wallet.svg'
@@ -10,38 +11,42 @@ import emailIcon from '@proofzero/design-system/src/assets/social_icons/email.sv
import appleIcon from '@proofzero/design-system/src/atoms/providers/Apple'
import discordIcon from '@proofzero/design-system/src/assets/social_icons/discord.svg'
import githubIcon from '@proofzero/design-system/src/atoms/providers/Github'
-import googleIcon from '@proofzero/design-system/src/assets/social_icons/google.svg'
-import microsoftIcon from '@proofzero/design-system/src/assets/social_icons/microsoft.svg'
+import googleIcon from '@proofzero/design-system/src/atoms/providers/Google'
+import microsoftIcon from '@proofzero/design-system/src/atoms/providers/Microsoft'
import twitterIcon from '@proofzero/design-system/src/assets/social_icons/twitter.svg'
+import webauthnIcon from '@proofzero/design-system/src/atoms/providers/Webauthn'
-export const imageFromAddressType = (addressType: string) => {
+export const imageFromAccountType = (accountType: string) => {
let providerIcon = null
- switch (addressType) {
- case CryptoAddressType.ETH:
+ switch (accountType) {
+ case CryptoAccountType.ETH:
providerIcon = ethereumIcon
break
- case CryptoAddressType.Wallet:
+ case CryptoAccountType.Wallet:
providerIcon = scWalletIcon
break
- case EmailAddressType.Email:
+ case EmailAccountType.Email:
providerIcon = emailIcon
break
- case OAuthAddressType.Apple:
+ case WebauthnAccountType.WebAuthN:
+ providerIcon = webauthnIcon
+ break
+ case OAuthAccountType.Apple:
providerIcon = appleIcon
break
- case OAuthAddressType.Discord:
+ case OAuthAccountType.Discord:
providerIcon = discordIcon
break
- case OAuthAddressType.GitHub:
+ case OAuthAccountType.GitHub:
providerIcon = githubIcon
break
- case OAuthAddressType.Google:
+ case OAuthAccountType.Google:
providerIcon = googleIcon
break
- case OAuthAddressType.Microsoft:
+ case OAuthAccountType.Microsoft:
providerIcon = microsoftIcon
break
- case OAuthAddressType.Twitter:
+ case OAuthAccountType.Twitter:
providerIcon = twitterIcon
break
}
diff --git a/apps/profile/app/helpers/index.ts b/apps/profile/app/helpers/index.ts
index 8bab5ca05d..3d31837dac 100644
--- a/apps/profile/app/helpers/index.ts
+++ b/apps/profile/app/helpers/index.ts
@@ -1,6 +1,6 @@
import * as clients from './clients'
import hexStyle from './hex-style'
import * as strings from './strings'
-import { imageFromAddressType } from './icons'
+import { imageFromAccountType } from './icons'
-export { clients, hexStyle, strings, imageFromAddressType }
+export { clients, hexStyle, strings, imageFromAccountType }
diff --git a/apps/profile/app/helpers/ogImage.ts b/apps/profile/app/helpers/ogImage.ts
index 8a3928ea1a..a023c08663 100644
--- a/apps/profile/app/helpers/ogImage.ts
+++ b/apps/profile/app/helpers/ogImage.ts
@@ -4,9 +4,13 @@ import {
TraceSpan,
} from '@proofzero/platform-middleware/trace'
-export const ogImageFromProfile = async (pfp: string, traceSpan: TraceSpan) => {
- const imageClient = createImageClient(Images, {
- imagesURL: IMAGES_URL,
+export const ogImageFromProfile = async (
+ pfp: string,
+ env: Env,
+ traceSpan: TraceSpan
+) => {
+ const imageClient = createImageClient(env.Images, {
+ imagesURL: env.IMAGES_URL,
headers: generateTraceContextHeaders(traceSpan),
})
const ogImage = await imageClient.getOgImage.query({
diff --git a/apps/profile/app/helpers/profile.ts b/apps/profile/app/helpers/profile.ts
index f0fbea02dd..f58ac34bc2 100644
--- a/apps/profile/app/helpers/profile.ts
+++ b/apps/profile/app/helpers/profile.ts
@@ -1,47 +1,56 @@
import {
- CryptoAddressType,
- EmailAddressType,
+ CryptoAccountType,
+ EmailAccountType,
NodeType,
- OAuthAddressType,
-} from '@proofzero/types/address'
-import type { AddressURN } from '@proofzero/urns/address'
+ OAuthAccountType,
+ WebauthnAccountType,
+} from '@proofzero/types/account'
+import type { AccountURN } from '@proofzero/urns/account'
import { getAuthzHeaderConditionallyFromToken } from '@proofzero/utils'
import { getGalaxyClient } from './clients'
-import { imageFromAddressType } from './icons'
+import { imageFromAccountType } from './icons'
import type { FullProfile } from '../types'
-import type { AccountURN } from '@proofzero/urns/account'
+import type { IdentityURN } from '@proofzero/urns/identity'
import type { TraceSpan } from '@proofzero/platform-middleware/trace'
import { generateTraceContextHeaders } from '@proofzero/platform-middleware/trace'
import { getValidGallery } from './alchemy'
-import { GetAddressProfilesQuery } from '@proofzero/galaxy-client'
+import { GetAccountProfilesQuery } from '@proofzero/galaxy-client'
-export const getAccountProfile = async (
+export const getIdentityProfile = async (
{
- accountURN,
+ identityURN,
jwt,
}: {
- accountURN: AccountURN
+ identityURN: IdentityURN
jwt?: string
},
+ env: Env,
traceSpan: TraceSpan
) => {
- // note: jwt is only important for setting profile in profile account settings
-
- const profile = await ProfileKV.get(accountURN, 'json')
+ // note: jwt is only important for setting profile in profile identity settings
+ const profile = await env.ProfileKV.get(identityURN, 'json')
if (profile && profile.gallery)
- profile.gallery = await getValidGallery({
- gallery: profile.gallery,
- accountURN,
- traceSpan,
- })
+ profile.gallery = await getValidGallery(
+ {
+ gallery: profile.gallery,
+ identityURN,
+ },
+ env,
+ traceSpan
+ )
return profile
}
-export const getAuthorizedApps = async (jwt: string, traceSpan: TraceSpan) => {
+export const getAuthorizedApps = async (
+ jwt: string,
+ env: Env,
+ traceSpan: TraceSpan
+) => {
const galaxyClient = await getGalaxyClient(
- generateTraceContextHeaders(traceSpan)
+ generateTraceContextHeaders(traceSpan),
+ env
)
const { authorizedApps } = await galaxyClient.getAuthorizedApps(
@@ -52,84 +61,104 @@ export const getAuthorizedApps = async (jwt: string, traceSpan: TraceSpan) => {
return authorizedApps
}
-export const getAccountAddresses = async ({
- jwt,
- accountURN,
- traceSpan,
-}: {
- jwt?: string
- accountURN?: AccountURN
+export const getIdentityAccounts = async (
+ {
+ jwt,
+ identityURN,
+ }: {
+ jwt?: string
+ identityURN?: IdentityURN
+ },
+ env: Env,
traceSpan: TraceSpan
-}) => {
+) => {
const galaxyClient = await getGalaxyClient(
- generateTraceContextHeaders(traceSpan!)
+ generateTraceContextHeaders(traceSpan!),
+ env
)
- const addressesRes = await galaxyClient.getConnectedAddresses(
- { targetAccountURN: accountURN },
+
+ const accountsRes = await galaxyClient.getConnectedAccounts(
+ { targetIdentityURN: identityURN },
getAuthzHeaderConditionallyFromToken(jwt)
)
- return addressesRes.addresses || []
+ return accountsRes.accounts || []
}
-export const getAccountCryptoAddresses = async ({
- jwt,
- accountURN,
- traceSpan,
-}: {
- jwt?: string
- accountURN?: AccountURN
+export const getIdentityCryptoAddresses = async (
+ {
+ jwt,
+ identityURN,
+ }: {
+ jwt?: string
+ identityURN?: IdentityURN
+ },
+ env: Env,
traceSpan: TraceSpan
-}) => {
- const addresses = await getAccountAddresses({ jwt, accountURN, traceSpan })
+) => {
+ const accounts = await getIdentityAccounts(
+ { jwt, identityURN },
+ env,
+ traceSpan
+ )
// TODO: need to type qc and rc
- const cryptoAddresses =
- addresses
- .filter((e) => [NodeType.Crypto, NodeType.Vault].includes(e.rc.node_type))
- .map((address) => address.qc.alias.toLowerCase() as string) ||
- ([] as string[])
-
- return cryptoAddresses
+ const cryptoAccounts =
+ accounts
+ .filter((e) => {
+ return (
+ [NodeType.Crypto, NodeType.Vault].includes(e.rc.node_type) &&
+ e.rc.addr_type === CryptoAccountType.ETH
+ )
+ })
+ .map((account) => {
+ return account.qc.alias.toLowerCase() as string
+ }) || ([] as string[])
+
+ return cryptoAccounts
}
-export const getAddressProfiles = async (
+export const getAccountProfiles = async (
jwt: string,
- addressURNList: AddressURN[],
+ accountURNList: AccountURN[],
+ env: Env,
traceSpan: TraceSpan
-): Promise => {
+): Promise => {
const galaxyClient = await getGalaxyClient(
- generateTraceContextHeaders(traceSpan)
+ generateTraceContextHeaders(traceSpan),
+ env
)
- const addressProfilesRes = await galaxyClient.getAddressProfiles(
+ const accountProfilesRes = await galaxyClient.getAccountProfiles(
{
- addressURNList,
+ accountURNList,
},
getAuthzHeaderConditionallyFromToken(jwt)
)
- const { addressProfiles } = addressProfilesRes
+ const { accountProfiles } = accountProfilesRes
- return addressProfiles
+ return accountProfiles
}
export const getProfileTypeTitle = (type: string) => {
switch (type) {
- case CryptoAddressType.ETH:
+ case CryptoAccountType.ETH:
return 'Ethereum'
- case EmailAddressType.Email:
+ case EmailAccountType.Email:
return 'Email'
- case OAuthAddressType.Apple:
+ case WebauthnAccountType.WebAuthN:
+ return 'Passkey'
+ case OAuthAccountType.Apple:
return 'Apple'
- case OAuthAddressType.Discord:
+ case OAuthAccountType.Discord:
return 'Discord'
- case OAuthAddressType.GitHub:
+ case OAuthAccountType.GitHub:
return 'GitHub'
- case OAuthAddressType.Google:
+ case OAuthAccountType.Google:
return 'Google'
- case OAuthAddressType.Microsoft:
+ case OAuthAccountType.Microsoft:
return 'Microsoft'
- case OAuthAddressType.Twitter:
+ case OAuthAccountType.Twitter:
return 'Twitter'
default:
return ''
diff --git a/apps/profile/app/root.tsx b/apps/profile/app/root.tsx
index 963ced5daa..2441522816 100644
--- a/apps/profile/app/root.tsx
+++ b/apps/profile/app/root.tsx
@@ -71,7 +71,9 @@ export const links: LinksFunction = () => [
{ rel: 'shortcut icon', type: 'image/svg+xml', href: faviconSvg },
]
-export const loader: LoaderFunction = async ({ request }) => {
+export const loader: LoaderFunction = async ({ request, context }) => {
+ const { INTERNAL_GOOGLE_ANALYTICS_TAG, PASSPORT_URL, PROFILE_CLIENT_ID } =
+ context.env
return json({
ENV: {
INTERNAL_GOOGLE_ANALYTICS_TAG,
diff --git a/apps/profile/app/routes/$type.$address.tsx b/apps/profile/app/routes/$type.$address.tsx
index bdf16fe72e..2556ea3a87 100644
--- a/apps/profile/app/routes/$type.$address.tsx
+++ b/apps/profile/app/routes/$type.$address.tsx
@@ -20,10 +20,9 @@ import { JsonError, getErrorCause } from '@proofzero/utils/errors'
import { getAccessToken, parseJwt } from '~/utils/session.server'
import { getGalaxyClient } from '~/helpers/clients'
import { ogImageFromProfile } from '~/helpers/ogImage'
-import { getAccountProfile } from '~/helpers/profile'
+import { getIdentityProfile } from '~/helpers/profile'
import { Avatar } from '@proofzero/design-system/src/atoms/profile/avatar/Avatar'
-import { Text } from '@proofzero/design-system/src/atoms/text/Text'
import { gatewayFromIpfs } from '@proofzero/utils'
import ProfileTabs from '~/components/profile/tabs/tabs'
@@ -31,11 +30,11 @@ import ProfileLayout from '~/components/profile/layout'
import defaultOG from '~/assets/social.png'
import subtractLogo from '~/assets/subtract-logo.svg'
-import { CryptoAddressType, OAuthAddressType } from '@proofzero/types/address'
-import type { AccountURN } from '@proofzero/urns/account'
-import { AccountURNSpace } from '@proofzero/urns/account'
-import { Button } from '@proofzero/design-system/src/atoms/buttons/Button'
-import { imageFromAddressType } from '~/helpers'
+import { CryptoAccountType, OAuthAccountType } from '@proofzero/types/account'
+import type { IdentityURN } from '@proofzero/urns/identity'
+import { IdentityURNSpace } from '@proofzero/urns/identity'
+import { Button, Text } from '@proofzero/design-system'
+import { imageFromAccountType } from '~/helpers'
import type { FullProfile } from '~/types'
import { generateTraceContextHeaders } from '@proofzero/platform-middleware/trace'
@@ -54,38 +53,43 @@ export const loader: LoaderFunction = async ({ request, params, context }) => {
}
const galaxyClient = await getGalaxyClient(
- generateTraceContextHeaders(context.traceSpan)
+ generateTraceContextHeaders(context.traceSpan),
+ context.env
)
if (!address) throw new Error('No address provided in URL')
if (!type) throw new Error('No provider specified in URL')
- // redirect from any addressURN to its addressURNs
- let accountURN: AccountURN
+ // redirect from any accountURN to its accountURNs
+ let identityURN: IdentityURN
if (type !== 'p') {
try {
- const { accountFromAlias } = await galaxyClient.getAccountUrnFromAlias({
+ const { identityFromAlias } = await galaxyClient.getIdentityURNFromAlias({
provider: type,
alias: address,
})
- if (!accountFromAlias) {
+ if (!identityFromAlias) {
throw json({ message: 'Not Found' }, { status: 404 })
}
- accountURN = accountFromAlias
+ identityURN = identityFromAlias as IdentityURN
} catch (error) {
throw JsonError(error, context.traceSpan.getTraceParent())
}
} else {
- accountURN = AccountURNSpace.urn(address) as AccountURN
+ identityURN = IdentityURNSpace.urn(address) as IdentityURN
}
// if not handle is this let's assume this is an idref
let profile, jwt
try {
- jwt = await getAccessToken(request)
+ jwt = await getAccessToken(request, context.env)
- profile = await getAccountProfile({ jwt, accountURN }, context.traceSpan)
+ profile = await getIdentityProfile(
+ { jwt, identityURN },
+ context.env,
+ context.traceSpan
+ )
if (!profile) {
throw json({ message: 'Profile could not be resolved' }, { status: 404 })
@@ -95,6 +99,7 @@ export const loader: LoaderFunction = async ({ request, params, context }) => {
if (request.cf.botManagement.score < 30) {
ogImage = await ogImageFromProfile(
profile.pfp?.image as string,
+ context.env,
context.traceSpan
)
} else {
@@ -107,20 +112,20 @@ export const loader: LoaderFunction = async ({ request, params, context }) => {
const splittedUrl = request.url.split('/')
const path = splittedUrl[splittedUrl.length - 1]
- // Check if the accountURN in jwt matches with accountURN in URL
- const isOwner = jwt ? parseJwt(jwt).sub === accountURN : false
+ // Check if the identityURN in jwt matches with identityURN in URL
+ const isOwner = jwt ? parseJwt(jwt).sub === identityURN : false
return json({
uname: profile.displayName || address,
ogImage: ogImage || defaultOG,
profile,
- accountURN,
+ identityURN,
path,
isOwner,
})
} catch (error) {
console.log(
- `Galaxy did not return a profile for address ${accountURN}. Moving on.`
+ `Galaxy did not return a profile for address ${identityURN}. Moving on.`
)
console.error(getErrorCause(error))
throw new Response('No address found', { status: 404 })
@@ -166,11 +171,11 @@ export const meta: MetaFunction = ({
const UserAddressLayout = () => {
//TODO: this needs to be optimized so profile isn't fetched from the loader
//but used from context alone.
- const { profile, path, isOwner, accountURN } = useLoaderData<{
+ const { profile, path, isOwner, identityURN } = useLoaderData<{
profile: FullProfile
path: string
isOwner: boolean
- accountURN: string
+ identityURN: string
}>()
const finalProfile = profile
@@ -251,7 +256,7 @@ const UserAddressLayout = () => {
>
{
const { profile, isOwner } = useOutletContext<{
profile: FullProfile
isOwner: boolean
- accountURN: AccountURN
+ identityURN: IdentityURN
}>()
const { displayName, pfp } = profile
diff --git a/apps/profile/app/routes/account.tsx b/apps/profile/app/routes/account.tsx
index 004b5d7599..cd620ab1bd 100644
--- a/apps/profile/app/routes/account.tsx
+++ b/apps/profile/app/routes/account.tsx
@@ -11,22 +11,22 @@ import { parseJwt, requireJWT } from '~/utils/session.server'
import styles from '~/styles/account.css'
-import type { AccountURN } from '@proofzero/urns/account'
-import { AccountURNSpace } from '@proofzero/urns/account'
+import type { IdentityURN } from '@proofzero/urns/identity'
+import { IdentityURNSpace } from '@proofzero/urns/identity'
import HeadNav, { links as headNavLink } from '~/components/head-nav'
import { DesktopSideNav } from '~/components/side-nav'
import {
- getAccountAddresses,
- getAccountProfile,
- getAddressProfiles,
+ getIdentityAccounts,
+ getIdentityProfile,
+ getAccountProfiles,
} from '~/helpers/profile'
import type {
- GetAddressProfilesQuery,
+ GetAccountProfilesQuery,
Node,
Profile,
} from '@proofzero/galaxy-client'
-import type { AddressURN } from '@proofzero/urns/address'
+import type { AccountURN } from '@proofzero/urns/account'
import type { FullProfile } from '~/types'
import {
toast,
@@ -51,53 +51,54 @@ export const loader: LoaderFunction = async ({ request, context }) => {
if (url.pathname === '/account') {
return redirect('/account/dashboard')
}
- const jwt = await requireJWT(request)
+ const jwt = await requireJWT(request, context.env)
// We go through this because
// the context had connected addresses
// but don't have the profiles
// and it's complex to send them to a loader / action
- const accountURN = parseJwt(jwt).sub as AccountURN
+ const identityURN = parseJwt(jwt).sub as IdentityURN
try {
- const [loggedInUserProfile, addresses] = await Promise.all([
- getAccountProfile({ jwt, accountURN }, context.traceSpan),
- getAccountAddresses({ jwt, traceSpan: context.traceSpan }),
+ const [loggedInUserProfile, accounts] = await Promise.all([
+ getIdentityProfile({ jwt, identityURN }, context.env, context.traceSpan),
+ getIdentityAccounts({ jwt }, context.env, context.traceSpan),
])
- const addressTypeUrns = addresses.map((a) => ({
+ const accountTypeUrns = accounts.map((a) => ({
urn: a.baseUrn,
nodeType: a.rc.node_type,
}))
- let connectedProfiles: GetAddressProfilesQuery['addressProfiles'] = []
+ let connectedProfiles: GetAccountProfilesQuery['accountProfiles'] = []
// We get the full profiles
connectedProfiles =
- (await getAddressProfiles(
+ (await getAccountProfiles(
jwt,
- addressTypeUrns.map((atu) => atu.urn as AddressURN),
+ accountTypeUrns.map((atu) => atu.urn as AccountURN),
+ context.env,
context.traceSpan
)) ?? []
// This mapps to a new structure that contains urn also;
- // useful for list keys as well as for address context actions as param
+ // useful for list keys as well as for account context actions as param
const normalizedConnectedProfiles = connectedProfiles.map((p, i) => ({
- ...addressTypeUrns[i],
+ ...accountTypeUrns[i],
...p,
}))
- const cryptoAddresses =
- addresses?.filter((e) => {
+ const cryptoAccounts =
+ accounts?.filter((e) => {
if (!e.rc) return false
return e?.rc?.node_type === 'crypto'
}) || []
return json({
connectedProfiles: normalizedConnectedProfiles,
- cryptoAddresses,
- accountURN,
+ cryptoAccounts,
+ identityURN,
profile: loggedInUserProfile,
})
} catch (error) {
@@ -124,12 +125,12 @@ const notify = (success: boolean = true) => {
}
export default function AccountLayout() {
- const { profile, accountURN, connectedProfiles, cryptoAddresses } =
+ const { profile, identityURN, connectedProfiles, cryptoAccounts } =
useLoaderData<{
profile: FullProfile
- accountURN: AccountURN
+ identityURN: IdentityURN
connectedProfiles: Node & Profile[]
- cryptoAddresses: Node[]
+ cryptoAccounts: Node[]
}>()
return (
@@ -144,7 +145,7 @@ export default function AccountLayout() {
>
{
rounded-lg transition-opacity ${
activeId ? 'opacity-0' : 'opacity-100'
}`}
- disabled={!cryptoAddresses?.length}
- onClick={() => setIsOpen(!!cryptoAddresses?.length)}
+ disabled={!cryptoAccounts?.length}
+ onClick={() => setIsOpen(!!cryptoAccounts?.length)}
>
- {/* Links that are already in account DO */}
+ {/* Links that are already in identity DO */}
({
@@ -454,9 +467,8 @@ export default function AccountSettingsLinks() {
itemRenderer={(item) => {
return (
- {}} />
+ { }} />
diff --git a/apps/profile/app/routes/account/profile.tsx b/apps/profile/app/routes/account/profile.tsx
index b05207d4c0..739c29178a 100644
--- a/apps/profile/app/routes/account/profile.tsx
+++ b/apps/profile/app/routes/account/profile.tsx
@@ -5,11 +5,10 @@ import {
useTransition,
useFetcher,
} from '@remix-run/react'
-import { Button } from '@proofzero/design-system/src/atoms/buttons/Button'
+import { Button, Text } from '@proofzero/design-system'
import { FaBriefcase, FaMapMarkerAlt } from 'react-icons/fa'
import InputText from '~/components/inputs/InputText'
import { getAccessToken, parseJwt } from '~/utils/session.server'
-import { Text } from '@proofzero/design-system/src/atoms/text/Text'
import { Avatar } from '@proofzero/design-system/src/atoms/profile/avatar/Avatar'
import { Spinner } from '@proofzero/design-system/src/atoms/spinner/Spinner'
@@ -27,7 +26,9 @@ import { FullProfileSchema } from '~/validation'
import InputTextarea from '@proofzero/design-system/src/atoms/form/InputTextarea'
export const action: ActionFunction = async ({ request, context }) => {
- const { sub: accountURN } = parseJwt(await getAccessToken(request))
+ const { sub: identityURN } = parseJwt(
+ await getAccessToken(request, context.env)
+ )
const formData = await request.formData()
@@ -39,7 +40,10 @@ export const action: ActionFunction = async ({ request, context }) => {
let computedIsToken =
formData.get('pfp_isToken')?.toString() === '1' ? true : false
- const currentProfile = await ProfileKV.get
(accountURN!, 'json')
+ const currentProfile = await context.env.ProfileKV.get(
+ identityURN!,
+ 'json'
+ )
const updatedProfile = Object.assign(currentProfile || {}, {
displayName,
pfp: {
@@ -60,20 +64,25 @@ export const action: ActionFunction = async ({ request, context }) => {
}
}
- await ProfileKV.put(accountURN!, JSON.stringify(zodValidation.data))
+ await context.env.ProfileKV.put(
+ identityURN!,
+ JSON.stringify(zodValidation.data)
+ )
return null
}
export default function AccountSettingsProfile() {
- const { notify, profile, accountURN } = useOutletContext<{
+ const { notify, profile, identityURN } = useOutletContext<{
profile: FullProfile
notify: (success: boolean) => void
- accountURN: string
+ identityURN: string
}>()
const { displayName, pfp, bio, job, location } = profile
+ const [bioInput, setBioInput] = useState(bio || '')
+
const [pfpUrl, setPfpUrl] = useState(pfp?.image || undefined)
const [isToken, setIsToken] = useState(false)
@@ -162,7 +171,7 @@ export default function AccountSettingsProfile() {
(nft: NFT) => nft.contract.address === collection
)[0].chain.chain
: null
- getMoreNftsModal(modalFetcher, accountURN, collection, chain)
+ getMoreNftsModal(modalFetcher, identityURN, collection, chain)
}, [collection])
// --------------------- END OF MODAL PART ---------------------- //
@@ -317,7 +326,8 @@ export default function AccountSettingsProfile() {
heading="Bio"
charLimit={256}
rows={3}
- defaultValue={bio || ''}
+ value={bioInput}
+ onChange={setBioInput}
error={actionData?.errors.bio}
/>
diff --git a/apps/profile/app/routes/account/profile/image-upload-url.tsx b/apps/profile/app/routes/account/profile/image-upload-url.tsx
index b11ab6a11b..d38a3a6bc7 100644
--- a/apps/profile/app/routes/account/profile/image-upload-url.tsx
+++ b/apps/profile/app/routes/account/profile/image-upload-url.tsx
@@ -5,15 +5,15 @@ import { requireJWT } from '~/utils/session.server'
import createImageClient from '@proofzero/platform-clients/image'
import { generateTraceContextHeaders } from '@proofzero/platform-middleware/trace'
-export const loader: LoaderFunction = async ({ request }) => {
- await requireJWT(request)
+export const loader: LoaderFunction = async ({ request, context }) => {
+ await requireJWT(request, context.env)
return redirect('/account/profile')
}
export const action: ActionFunction = async ({ request, context }) => {
- await requireJWT(request)
+ await requireJWT(request, context.env)
- const imageClient = createImageClient(Images, {
+ const imageClient = createImageClient(context.env.Images, {
headers: generateTraceContextHeaders(context.traceSpan),
})
const { uploadURL } = await imageClient.upload.mutate()
diff --git a/apps/profile/app/routes/api/nfts/collection.tsx b/apps/profile/app/routes/api/nfts/collection.tsx
index 0b89a2175c..be8c2f36b1 100644
--- a/apps/profile/app/routes/api/nfts/collection.tsx
+++ b/apps/profile/app/routes/api/nfts/collection.tsx
@@ -1,19 +1,19 @@
-import type { AccountURN } from '@proofzero/urns/account'
+import type { IdentityURN } from '@proofzero/urns/identity'
import type { LoaderFunction } from '@remix-run/cloudflare'
import { json } from '@remix-run/cloudflare'
import { getAccessToken } from '~/utils/session.server'
import { getNfts } from '~/helpers/alchemy'
-import { getAccountCryptoAddresses } from '~/helpers/profile'
+import { getIdentityCryptoAddresses } from '~/helpers/profile'
import type { AlchemyChain } from '@proofzero/packages/alchemy-client'
import { JsonError } from '@proofzero/utils/errors'
export const loader: LoaderFunction = async ({ request, context }) => {
const srcUrl = new URL(request.url)
- const jwt = await getAccessToken(request)
+ const jwt = await getAccessToken(request, context.env)
- const owner = srcUrl.searchParams.get('owner') as AccountURN
+ const owner = srcUrl.searchParams.get('owner') as IdentityURN
if (!owner) {
throw new Error('Owner is required')
}
@@ -29,19 +29,25 @@ export const loader: LoaderFunction = async ({ request, context }) => {
}
try {
- const addresses = await getAccountCryptoAddresses({
- jwt,
- traceSpan: context.traceSpan,
- })
-
- const nftsForAccount = await getNfts({
- addresses,
- contractAddresses: [collection],
- chain,
- })
+ const addresses = await getIdentityCryptoAddresses(
+ {
+ jwt,
+ },
+ context.env,
+ context.traceSpan
+ )
+
+ const nftsForIdentity = await getNfts(
+ {
+ addresses,
+ contractAddresses: [collection],
+ chain,
+ },
+ context.env
+ )
return json({
- ownedNfts: nftsForAccount,
+ ownedNfts: nftsForIdentity,
})
} catch (error) {
throw JsonError(error, context.traceSpan.getTraceParent())
diff --git a/apps/profile/app/routes/api/nfts/index.tsx b/apps/profile/app/routes/api/nfts/index.tsx
index 050eb4334b..b103977f01 100644
--- a/apps/profile/app/routes/api/nfts/index.tsx
+++ b/apps/profile/app/routes/api/nfts/index.tsx
@@ -1,4 +1,4 @@
-import type { AccountURN } from '@proofzero/urns/account'
+import type { IdentityURN } from '@proofzero/urns/identity'
import type { LoaderFunction } from '@remix-run/cloudflare'
import { json } from '@remix-run/cloudflare'
@@ -6,31 +6,37 @@ import { JsonError } from '@proofzero/utils/errors'
import { getAccessToken } from '~/utils/session.server'
import { getContractsForAllChains } from '~/helpers/alchemy'
-import { getAccountCryptoAddresses } from '~/helpers/profile'
+import { getIdentityCryptoAddresses } from '~/helpers/profile'
export const loader: LoaderFunction = async ({ request, context }) => {
const srcUrl = new URL(request.url)
- const jwt = await getAccessToken(request)
+ const jwt = await getAccessToken(request, context.env)
- const owner = srcUrl.searchParams.get('owner') as AccountURN
+ const owner = srcUrl.searchParams.get('owner') as IdentityURN
if (!owner) {
throw new Error('Owner is required')
}
try {
- const addresses = await getAccountCryptoAddresses({
- jwt,
- traceSpan: context.traceSpan,
- })
-
- const nftsForAccount = await getContractsForAllChains({
- addresses,
- excludeFilters: ['SPAM'],
- })
+ const addresses = await getIdentityCryptoAddresses(
+ {
+ jwt,
+ },
+ context.env,
+ context.traceSpan
+ )
+
+ const nftsForIdentity = await getContractsForAllChains(
+ {
+ addresses,
+ excludeFilters: ['SPAM'],
+ },
+ context.env
+ )
return json({
- ...nftsForAccount,
+ ...nftsForIdentity,
})
} catch (error) {
throw JsonError(error, context.traceSpan.getTraceParent())
diff --git a/apps/profile/app/routes/auth/callback.tsx b/apps/profile/app/routes/auth/callback.tsx
index 2e557f4a4b..dec1814f2f 100644
--- a/apps/profile/app/routes/auth/callback.tsx
+++ b/apps/profile/app/routes/auth/callback.tsx
@@ -1,8 +1,8 @@
import type { LoaderFunction } from '@remix-run/cloudflare'
import { getRollupAuthenticator } from '~/utils/session.server'
-export const loader: LoaderFunction = async ({ request }) => {
- const authenticator = getRollupAuthenticator()
+export const loader: LoaderFunction = async ({ request, context }) => {
+ const authenticator = getRollupAuthenticator(context.env)
await authenticator.authenticate('rollup', request, {
successRedirect: '/account',
})
diff --git a/apps/profile/app/routes/auth/index.tsx b/apps/profile/app/routes/auth/index.tsx
index c1dc1ded84..cbe9b94cfd 100644
--- a/apps/profile/app/routes/auth/index.tsx
+++ b/apps/profile/app/routes/auth/index.tsx
@@ -1,8 +1,8 @@
import type { LoaderFunction } from '@remix-run/cloudflare'
import { getRollupAuthenticator } from '~/utils/session.server'
-export const loader: LoaderFunction = async ({ request }) => {
- const authenticator = getRollupAuthenticator()
+export const loader: LoaderFunction = async ({ request, context }) => {
+ const authenticator = getRollupAuthenticator(context.env)
await authenticator.isAuthenticated(request, {
successRedirect: '/account',
// failureRedirect: 'https://rollup.id/profiles',
diff --git a/apps/profile/app/routes/signout.tsx b/apps/profile/app/routes/signout.tsx
index 3c5f5194fa..24d49c9b7a 100644
--- a/apps/profile/app/routes/signout.tsx
+++ b/apps/profile/app/routes/signout.tsx
@@ -1,17 +1,17 @@
import type { ActionFunction, LoaderFunction } from '@remix-run/cloudflare'
import { initAuthenticator } from '~/utils/session.server'
-const signOut = (request: Request) => {
- const authenticator = initAuthenticator()
+const signOut = (request: Request, env: Env) => {
+ const authenticator = initAuthenticator(env)
return authenticator.logout(request, {
redirectTo: `/auth`,
})
}
-export const loader: LoaderFunction = async ({ request }) => {
- return signOut(request)
+export const loader: LoaderFunction = async ({ request, context }) => {
+ return signOut(request, context.env)
}
-export const action: ActionFunction = async ({ request }) => {
- return signOut(request)
+export const action: ActionFunction = async ({ request, context }) => {
+ return signOut(request, context.env)
}
diff --git a/apps/profile/app/utils/session.server.tsx b/apps/profile/app/utils/session.server.tsx
index e998f939d6..93ad479ece 100644
--- a/apps/profile/app/utils/session.server.tsx
+++ b/apps/profile/app/utils/session.server.tsx
@@ -22,32 +22,22 @@ import {
import type { FullProfile, RollupAuth } from '~/types'
-const sessionKey = SECRET_SESSION_KEY
-if (!sessionKey) {
- throw new Error('SECRET_SESSION_KEY must be set')
-}
-
-const sessionSecret = SECRET_SESSION_SALT
-if (!sessionSecret) {
- throw new Error('SECRET_SESSION_SALT must be set')
-}
-
-const getProfileSessionStorage = () =>
+const getProfileSessionStorage = (env: Env) =>
createCookieSessionStorage({
cookie: {
- domain: COOKIE_DOMAIN,
+ domain: env.COOKIE_DOMAIN,
httpOnly: true,
name: '_rollup_profile_oauth',
path: '/',
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
maxAge: 7776000 /*60 * 60 * 24 * 90*/,
- secrets: [sessionSecret],
+ secrets: [env.SECRET_SESSION_SALT],
},
})
-export const initAuthenticator = () => {
- const oauthStorage = getProfileSessionStorage()
+export const initAuthenticator = (env: Env) => {
+ const oauthStorage = getProfileSessionStorage(env)
return new Authenticator(oauthStorage)
}
@@ -63,21 +53,21 @@ export class RollupAuthStrategy extends OAuth2Strategy<
}
}
-export const getRollupAuthenticator = () => {
- const authenticator = initAuthenticator()
+export const getRollupAuthenticator = (env: Env) => {
+ const authenticator = initAuthenticator(env)
const rollupStrategy = new RollupAuthStrategy(
{
- authorizationURL: PASSPORT_AUTH_URL,
- tokenURL: PASSPORT_TOKEN_URL,
- clientID: PROFILE_CLIENT_ID,
- clientSecret: PROFILE_CLIENT_SECRET,
- callbackURL: REDIRECT_URI,
+ authorizationURL: env.PASSPORT_AUTH_URL,
+ tokenURL: env.PASSPORT_TOKEN_URL,
+ clientID: env.PROFILE_CLIENT_ID,
+ clientSecret: env.PROFILE_CLIENT_SECRET,
+ callbackURL: env.REDIRECT_URI,
},
async ({ accessToken, refreshToken, extraParams }) => {
const parsedId = parseJwt(extraParams.id_token)
const { sub, name, picture } = parsedId
- const profile = await ProfileKV.get(sub!, 'json')
+ const profile = await env.ProfileKV.get(sub!, 'json')
if (!profile) {
const newProfile = {
displayName: name,
@@ -85,14 +75,14 @@ export const getRollupAuthenticator = () => {
image: picture,
},
} as FullProfile
- await ProfileKV.put(sub!, JSON.stringify(newProfile))
+ await env.ProfileKV.put(sub!, JSON.stringify(newProfile))
}
return {
accessToken: JSON.stringify(
- await encryptSession(sessionKey, accessToken)
+ await encryptSession(env.SECRET_SESSION_KEY, accessToken)
),
refreshToken: JSON.stringify(
- await encryptSession(sessionKey, refreshToken)
+ await encryptSession(env.SECRET_SESSION_KEY, refreshToken)
),
extraParams: {},
}
@@ -105,15 +95,15 @@ export const getRollupAuthenticator = () => {
// our authenticate function receives the Request, the Session and a Headers
// we make the headers optional so loaders don't need to pass one
// https://sergiodxa.com/articles/working-with-refresh-tokens-in-remix
-export async function requireJWT(request: Request, headers = new Headers()) {
- const session = await getProfileSession(request)
+export async function requireJWT(request: Request, env: Env, headers = new Headers()) {
+ const session = await getProfileSession(request, env)
const { user } = session.data
if (!user) throw redirect('/auth')
try {
const { cipher, iv } = JSON.parse(user.accessToken)
if (!cipher || !iv) throw redirect('/auth')
- const accessToken = await decryptSession(sessionKey, cipher, iv)
+ const accessToken = await decryptSession(env.SECRET_SESSION_KEY, cipher, iv)
checkToken(accessToken)
return accessToken
} catch (error) {
@@ -124,16 +114,16 @@ export async function requireJWT(request: Request, headers = new Headers()) {
const { cipher, iv } = JSON.parse(user.refreshToken)
if (!cipher || !iv) throw redirect('/auth')
const accessToken = await refreshAccessToken({
- tokenURL: PASSPORT_TOKEN_URL,
- refreshToken: await decryptSession(sessionKey, cipher, iv),
- clientId: PROFILE_CLIENT_ID,
- clientSecret: PROFILE_CLIENT_SECRET,
+ tokenURL: env.PASSPORT_TOKEN_URL,
+ refreshToken: await decryptSession(env.SECRET_SESSION_KEY, cipher, iv),
+ clientId: env.PROFILE_CLIENT_ID,
+ clientSecret: env.PROFILE_CLIENT_SECRET,
})
user.accessToken = JSON.stringify(
- await encryptSession(sessionKey, accessToken)
+ await encryptSession(env.SECRET_SESSION_KEY, accessToken)
)
session.set('user', user)
- const cookie = await getProfileSessionStorage().commitSession(session)
+ const cookie = await getProfileSessionStorage(env).commitSession(session)
headers.append('Set-Cookie', cookie)
if (request.method === 'GET') throw redirect(request.url, { headers })
@@ -155,12 +145,12 @@ export async function requireJWT(request: Request, headers = new Headers()) {
}
}
-export async function isValidJWT(request: Request): Promise {
- const session = await getProfileSession(request)
+export async function isValidJWT(request: Request, env: Env): Promise {
+ const session = await getProfileSession(request, env)
const user = session.get('user')
const { cipher, iv } = JSON.parse(user.accessToken)
- const jwt = await decryptSession(sessionKey, cipher, iv)
+ const jwt = await decryptSession(env.SECRET_SESSION_KEY, cipher, iv)
if (!jwt || typeof jwt !== 'string') {
return false
@@ -183,18 +173,18 @@ export function parseJwt(token: string): JWTPayload {
}
// TODO: reset cookie maxAge if valid
-export async function getProfileSession(request: Request) {
- const storage = getProfileSessionStorage()
+export async function getProfileSession(request: Request, env: Env) {
+ const storage = getProfileSessionStorage(env)
return storage.getSession(request.headers.get('Cookie'))
}
-export async function commitProfileSession(session: Session) {
- const storage = getProfileSessionStorage()
+export async function commitProfileSession(session: Session, env: Env) {
+ const storage = getProfileSessionStorage(env)
return storage.commitSession(session)
}
-export const getAccessToken = async (request: Request): Promise => {
- const session = await getProfileSession(request)
+export const getAccessToken = async (request: Request, env: Env): Promise => {
+ const session = await getProfileSession(request, env)
const { cipher, iv } = JSON.parse(session.get('user').accessToken)
- return decryptSession(sessionKey, cipher, iv)
+ return decryptSession(env.SECRET_SESSION_KEY, cipher, iv)
}
diff --git a/apps/profile/app/validation.ts b/apps/profile/app/validation.ts
index ebab2d837f..4b57f1b33f 100644
--- a/apps/profile/app/validation.ts
+++ b/apps/profile/app/validation.ts
@@ -54,7 +54,7 @@ export const NFTDetailSchema = z.object({
export const NFTPropertySchema = z.object({
display: z.string(),
name: z.string(),
- value: z.string(),
+ value: z.string().or(z.number()),
})
export const NFTSchema = z.object({
diff --git a/apps/profile/bindings.d.ts b/apps/profile/bindings.d.ts
new file mode 100644
index 0000000000..54328a6408
--- /dev/null
+++ b/apps/profile/bindings.d.ts
@@ -0,0 +1,28 @@
+interface Env {
+ NODE_ENV: string
+ COOKIE_DOMAIN: string
+ SECRET_SESSION_KEY: string
+ SECRET_SESSION_SALT: string
+ PASSPORT_URL: string
+ IMAGES_URL: string
+ PASSPORT_AUTH_URL: string
+ PASSPORT_TOKEN_URL: string
+ INTERNAL_GOOGLE_ANALYTICS_TAG: string
+
+ PROFILE_CLIENT_ID: string
+ PROFILE_CLIENT_SECRET: string
+ REDIRECT_URI: string
+
+ Galaxy: Fetcher
+ Address: Fetcher
+ Images: Fetcher
+
+ ProfileKV: KVNamespace
+ PROFILE_VERSION: number
+ ALCHEMY_ETH_NETWORK: 'mainnet' | 'goerli'
+ APIKEY_ALCHEMY_ETH: string
+ ALCHEMY_POLYGON_NETWORK: 'mainnet' | 'mumbai'
+ APIKEY_ALCHEMY_POLYGON: string
+ //Needed to make Remix work with Cloudflare module workers
+ __STATIC_CONTENT: string
+}
diff --git a/apps/profile/global.d.ts b/apps/profile/global.d.ts
deleted file mode 100644
index f73ecccaed..0000000000
--- a/apps/profile/global.d.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-export const seviceBindings = true
-
-declare global {
- const NODE_ENV: string
- const COOKIE_DOMAIN: string
- const SECRET_SESSION_KEY: string
- const SECRET_SESSION_SALT: string
- const PASSPORT_URL: string
- const IMAGES_URL: string
- const PASSPORT_AUTH_URL: string
- const PASSPORT_TOKEN_URL: string
- const INTERNAL_GOOGLE_ANALYTICS_TAG: string
-
- const PROFILE_CLIENT_ID: string
- const PROFILE_CLIENT_SECRET: string
- const REDIRECT_URI: string
-
- const Galaxy: Fetcher
- const Address: Fetcher
- const Images: Fetcher
-
- const ProfileKV: KVNamespace
- const PROFILE_VERSION: number
- const ALCHEMY_ETH_NETWORK: 'mainnet' | 'goerli'
- const APIKEY_ALCHEMY_ETH: string
- const ALCHEMY_POLYGON_NETWORK: 'mainnet' | 'mumbai'
- const APIKEY_ALCHEMY_POLYGON: string
-}
diff --git a/apps/profile/globals.d.ts b/apps/profile/globals.d.ts
new file mode 100644
index 0000000000..5b61595d7f
--- /dev/null
+++ b/apps/profile/globals.d.ts
@@ -0,0 +1,4 @@
+declare module '__STATIC_CONTENT_MANIFEST' {
+ const value: string
+ export default value
+}
diff --git a/apps/profile/package.json b/apps/profile/package.json
index 11ea44ce23..8941f46888 100644
--- a/apps/profile/package.json
+++ b/apps/profile/package.json
@@ -13,7 +13,7 @@
"dev:css": "npx tailwindcss -o ./app/styles/tailwind.css --watch",
"dev:sass": "sass --watch app/:app/",
"dev:remix": "remix watch",
- "dev:wrangler": "wrangler dev --local --persist",
+ "dev:wrangler": "wrangler dev ",
"dev": "echo \"No dev for profile. Please use start.\"",
"start": "env-cmd --file .dev.env run-p 'dev:*'",
"e2e": "cross-env NODE_ENV=test npx playwright test",
@@ -41,14 +41,14 @@
"ethers": "5.7.2",
"flowbite": "1.6.5",
"flowbite-react": "0.4.3",
- "graphql": "16.6.0",
+ "graphql": "16.8.1",
"graphql-request": "5.0.0",
"graphql-tag": "2.12.6",
"history": "5.3.0",
"qs": "6.11.0",
"react": "18.2.0",
"react-dom": "18.2.0",
- "react-icons": "4.8.0",
+ "react-icons": "4.10.1",
"react-infinite-scroll-component": "6.1.0",
"react-masonry-css": "1.0.16",
"react-popper": "2.3.0",
@@ -57,13 +57,13 @@
"remix-auth-oauth2": "1.5.0",
"tw-elements": "1.0.0-alpha12",
"urns": "0.6.0",
- "wagmi": "1.0.9"
+ "wagmi": "1.1.1"
},
"devDependencies": {
"@babel/core": "7.20.2",
"@cloudflare/workers-types": "4.20221111.1",
"@mdx-js/react": "2.1.5",
- "@playwright/test": "1.32.3",
+ "@playwright/test": "1.35.1",
"@remix-run/dev": "1.14.0",
"@remix-run/eslint-config": "1.14.0",
"@remix-run/serve": "1.14.0",
@@ -109,10 +109,10 @@
"sass-loader": "13.2.0",
"storybook-addon-sass-postcss": "0.1.3",
"style-loader": "3.3.1",
- "tailwindcss": "3.2.4",
+ "tailwindcss": "3.3.3",
"typescript": "5.0.4",
"webpack": "5.75.0",
- "wrangler": "2.14.0"
+ "wrangler": "3.2.0"
},
"engines": {
"node": ">=16.13"
diff --git a/apps/profile/remix.config.js b/apps/profile/remix.config.js
index 5dda68be85..88e95e88b3 100644
--- a/apps/profile/remix.config.js
+++ b/apps/profile/remix.config.js
@@ -1,11 +1,16 @@
/** @type {import('@remix-run/dev').AppConfig} */
module.exports = {
- serverBuildTarget: 'cloudflare-workers',
server: './server.ts',
devServerBroadcastDelay: 1000,
ignoredRouteFiles: ['**/.*'],
- // appDirectory: "app",
- // assetsBuildDirectory: "public/build",
- // serverBuildPath: "build/index.js",
- // publicPath: "/build/",
+ // Bundles everything _except_ `__STATIC_CONTENT_MANIFEST`. Be careful if editing.
+ serverDependenciesToBundle: [/^(?!(__STATIC_CONTENT_MANIFEST)$).*$/u],
+ // Optional, but highly recommended for fitting within the Worker bundle size
+ serverMinify: true,
+ serverModuleFormat: 'esm',
+ // YMMV with setting to `node`, most likely anything but simple code won’t work with the CF node compat layer
+ serverPlatform: 'neutral',
+ serverMainFields: ['browser', 'module', 'main'],
+ // Try the `workerd` condition first (this is new and slowly standardising), then `worker`, then `browser` (equivalent of `serverPlatform: browser` but without extra behaviour.
+ serverConditions: ['workerd', 'worker', 'browser'],
}
diff --git a/apps/profile/server.ts b/apps/profile/server.ts
index 5d9ded210d..cbdeb106af 100644
--- a/apps/profile/server.ts
+++ b/apps/profile/server.ts
@@ -8,26 +8,21 @@ import {
handleAsset,
} from '@remix-run/cloudflare-workers'
import * as build from '@remix-run/dev/server-build'
+import manifestJSON from '__STATIC_CONTENT_MANIFEST'
+let manifest = JSON.parse(manifestJSON)
declare module '@remix-run/server-runtime' {
interface AppLoadContext {
traceSpan: TraceSpan
+ env: Env
}
}
-const requestHandler = createRequestHandler({
- build,
- mode: process.env.NODE_ENV,
- getLoadContext: (event) => {
- const traceSpan = (event as TraceableFetchEvent).traceSpan
- return {
- traceSpan,
- }
- },
-})
-
-const handleEvent = async (event: FetchEvent) => {
- let response = await handleAsset(event, build)
+const handleEvent = async (event: FetchEvent, env: Env) => {
+ let response = await handleAsset(event, build, {
+ ASSET_NAMESPACE: env.__STATIC_CONTENT,
+ ASSET_MANIFEST: manifest,
+ })
if (!response) {
//Create a new trace span with no parent
@@ -40,6 +35,19 @@ const handleEvent = async (event: FetchEvent) => {
`Started HTTP handler for ${reqURL.pathname}/${reqURL.searchParams}`,
newTraceSpan.toString()
)
+
+ const requestHandler = createRequestHandler({
+ build,
+ mode: process.env.NODE_ENV,
+ getLoadContext: (event) => {
+ const traceSpan = (event as TraceableFetchEvent).traceSpan
+ return {
+ traceSpan,
+ env,
+ }
+ },
+ })
+
try {
response = await requestHandler(newEvent)
} finally {
@@ -53,6 +61,13 @@ const handleEvent = async (event: FetchEvent) => {
return response
}
-addEventListener('fetch', async (event: FetchEvent) => {
- event.respondWith(handleEvent(event))
-})
+export default {
+ async fetch(req: Request, env: Env, ctx: ExecutionContext) {
+ //This is the smallest set of event props Remix needs to handle assets correctly
+ const event = {
+ request: req,
+ waitUntil: ctx.waitUntil.bind(ctx),
+ } as FetchEvent
+ return await handleEvent(event, env)
+ },
+}
diff --git a/apps/profile/tests/helpers.ts b/apps/profile/tests/helpers.ts
index f8606dda60..c50599243e 100644
--- a/apps/profile/tests/helpers.ts
+++ b/apps/profile/tests/helpers.ts
@@ -1,42 +1,45 @@
-import fs from "fs";
-import path from "path";
+import fs from 'fs'
+import path from 'path'
-import { ethers } from "ethers";
+import { ethers } from 'ethers'
-import { test as baseTest } from "@playwright/test";
+import { test as baseTest } from '@playwright/test'
+import { AuthenticationScreenDefaults } from '@proofzero/design-system/src/templates/authentication/Authentication'
-import { signMessageTemplate } from "../app/utils/constants";
-
-export const dappUrl = `${process.env.DAPP_SCHEMA}://${process.env.DAPP_HOST}:${process.env.DAPP_PORT}`;
+export const dappUrl = `${process.env.DAPP_SCHEMA}://${process.env.DAPP_HOST}:${process.env.DAPP_PORT}`
export const users = {
- invited: new ethers.Wallet(process.env.ETH_GOERLI_PK || ""),
+ invited: new ethers.Wallet(process.env.ETH_GOERLI_PK || ''),
uninvited: ethers.Wallet.createRandom(),
-};
+}
export const invitedUsertest = baseTest.extend({
storageState: async ({ browser }, use, testInfo) => {
// Override storage state, use worker index to look up logged-in info and generate it lazily.
const fileName = path.join(
testInfo.project.outputDir,
- "storage-invitedUser.json"
- );
+ 'storage-invitedUser.json'
+ )
if (!fs.existsSync(fileName)) {
// Make sure we are not using any other storage state.
- const address = users.invited.address;
- const nonceUrl = `${dappUrl}/auth/nonce/${address}?isTest=true`;
+ const address = users.invited.address
+ const nonceUrl = `${dappUrl}/auth/nonce/${address}?isTest=true`
- const page = await browser.newPage({ storageState: undefined });
- await page.goto(nonceUrl);
+ const page = await browser.newPage({ storageState: undefined })
+ await page.goto(nonceUrl)
// get nonce
- const signUrl = page.url();
- const url = new URL(signUrl);
- const nonce = url.searchParams.get("nonce") || "";
+ const signUrl = page.url()
+ const url = new URL(signUrl)
+ const nonce = url.searchParams.get('nonce') || ''
// sign a login message
- const nonceMessage = signMessageTemplate.replace("{{nonce}}", nonce);
- const signature = await users.invited.signMessage(nonceMessage);
+ const nonceMessage =
+ AuthenticationScreenDefaults.defaultSignMessage.replace(
+ '{{nonce}}',
+ nonce
+ )
+ const signature = await users.invited.signMessage(nonceMessage)
// get jwt
await page.request.post(signUrl, {
@@ -44,12 +47,12 @@ export const invitedUsertest = baseTest.extend({
nonce,
signature,
},
- });
+ })
// Save signed-in state to 'storageState.json'.
- await page.context().storageState({ path: fileName });
- await page.close();
+ await page.context().storageState({ path: fileName })
+ await page.close()
}
- await use(fileName);
+ await use(fileName)
},
-});
+})
diff --git a/apps/profile/wrangler.current.toml b/apps/profile/wrangler.current.toml
new file mode 100644
index 0000000000..6a9b55e864
--- /dev/null
+++ b/apps/profile/wrangler.current.toml
@@ -0,0 +1,42 @@
+name = "profile"
+main = "./build/index.js"
+compatibility_date = "2022-04-05"
+compatibility_flags = ["no_minimal_subrequests"]
+workers_dev = false
+logpush = true
+
+[build]
+command = "yarn build"
+
+[site]
+bucket = "./public"
+
+[env.current]
+kv_namespaces = [
+ { binding = "ProfileKV", id = "4aae166942744cce9569daefbbeea7f2" },
+]
+
+routes = [
+ { pattern = "my.rollup.id", custom_domain = true, zone_name = "rollup.id" },
+]
+
+services = [
+ { binding = "Galaxy", service = "galaxy-current" },
+ { binding = "Images", service = "images-current" },
+]
+
+[env.current.vars]
+PASSPORT_URL = "https://passport.rollup.id"
+PASSPORT_AUTH_URL = "https://passport.rollup.id/authorize"
+IMAGES_URL = "https://images.rollup.id/trpc"
+PASSPORT_TOKEN_URL = "https://passport.rollup.id/token"
+CLIENT_ID = ""
+REDIRECT_URI = "https://my.rollup.id/auth/callback"
+COOKIE_DOMAIN = "rollup.id"
+TWITTER_URL = "https://twitter.com/rollupid"
+DISCORD_URL = "https://discord.gg/rollupid"
+MINTPFP_CONTRACT_ADDRESS = "0x3ebfaFE60F3Ac34f476B2f696Fc2779ff1B03193"
+INTERNAL_GOOGLE_ANALYTICS_TAG = "G-675VJMWSRY"
+PROFILE_VERSION = 1
+ALCHEMY_ETH_NETWORK = "mainnet"
+ALCHEMY_POLYGON_NETWORK = "mainnet"
diff --git a/apps/profile/wrangler.dev.toml b/apps/profile/wrangler.dev.toml
new file mode 100644
index 0000000000..61d3ef265e
--- /dev/null
+++ b/apps/profile/wrangler.dev.toml
@@ -0,0 +1,42 @@
+name = "profile"
+main = "./build/index.js"
+compatibility_date = "2022-04-05"
+compatibility_flags = ["no_minimal_subrequests"]
+workers_dev = false
+logpush = true
+
+[build]
+command = "yarn build"
+
+[site]
+bucket = "./public"
+
+[env.dev]
+kv_namespaces = [
+ { binding = "ProfileKV", id = "d72ecc0884434a8591761c6cb15fedd5" },
+]
+
+routes = [
+ { pattern = "my-dev.rollup.id", custom_domain = true, zone_name = "rollup.id" },
+]
+
+services = [
+ { binding = "Galaxy", service = "galaxy-dev" },
+ { binding = "Images", service = "images-dev" },
+]
+
+[env.dev.vars]
+PASSPORT_URL = "https://passport-dev.rollup.id"
+PASSPORT_AUTH_URL = "https://passport-dev.rollup.id/authorize"
+IMAGES_URL = "https://images-dev.rollup.id/trpc"
+PASSPORT_TOKEN_URL = "https://passport-dev.rollup.id/token"
+CLIENT_ID = ""
+REDIRECT_URI = "https://my-dev.rollup.id/auth/callback"
+COOKIE_DOMAIN = "rollup.id"
+TWITTER_URL = "https://twitter.com/rollupid"
+DISCORD_URL = "https://discord.gg/rollupid"
+MINTPFP_CONTRACT_ADDRESS = "0x028aE75Bb01eef2A581172607b93af8D24F50643"
+INTERNAL_GOOGLE_ANALYTICS_TAG = "G-NHNH4KRWC3"
+PROFILE_VERSION = 1
+ALCHEMY_ETH_NETWORK = "mainnet"
+ALCHEMY_POLYGON_NETWORK = "mainnet"
diff --git a/apps/profile/wrangler.next.toml b/apps/profile/wrangler.next.toml
new file mode 100644
index 0000000000..3675069bfb
--- /dev/null
+++ b/apps/profile/wrangler.next.toml
@@ -0,0 +1,42 @@
+name = "profile"
+main = "./build/index.js"
+compatibility_date = "2022-04-05"
+compatibility_flags = ["no_minimal_subrequests"]
+workers_dev = false
+logpush = true
+
+[build]
+command = "yarn build"
+
+[site]
+bucket = "./public"
+
+[env.next]
+kv_namespaces = [
+ { binding = "ProfileKV", id = "d4190ed6b22849bebba393fabb2feb9b" },
+]
+
+routes = [
+ { pattern = "my-next.rollup.id", custom_domain = true, zone_name = "rollup.id" },
+]
+
+services = [
+ { binding = "Galaxy", service = "galaxy-next" },
+ { binding = "Images", service = "images-next" },
+]
+
+[env.next.vars]
+PASSPORT_URL = "https://passport-next.rollup.id"
+PASSPORT_AUTH_URL = "https://passport-next.rollup.id/authorize"
+IMAGES_URL = "https://images-next.rollup.id/trpc"
+PASSPORT_TOKEN_URL = "https://passport-next.rollup.id/token"
+CLIENT_ID = ""
+REDIRECT_URI = "https://my-next.rollup.id/auth/callback"
+COOKIE_DOMAIN = "rollup.id"
+TWITTER_URL = "https://twitter.com/rollupid"
+DISCORD_URL = "https://discord.gg/rollupid"
+MINTPFP_CONTRACT_ADDRESS = "0x028aE75Bb01eef2A581172607b93af8D24F50643"
+INTERNAL_GOOGLE_ANALYTICS_TAG = "G-X7ZN16M4NB"
+PROFILE_VERSION = 1
+ALCHEMY_ETH_NETWORK = "mainnet"
+ALCHEMY_POLYGON_NETWORK = "mainnet"
diff --git a/apps/profile/wrangler.toml b/apps/profile/wrangler.toml
index 24eef5d024..13de4f6364 100644
--- a/apps/profile/wrangler.toml
+++ b/apps/profile/wrangler.toml
@@ -1,84 +1,37 @@
-# https://developers.cloudflare.com/workers/platform/compatibility-dates
-compatibility_date = "2022-04-05"
-
main = "./build/index.js"
-
name = "profile"
-
+compatibility_date = "2022-04-05"
+compatibility_flags = ["no_minimal_subrequests"]
workers_dev = false
logpush = true
-# The default environment
-# ------------------------------------------------------------------------------
-# name = "rollup-id-dev"
-# # Whether or not the worker should be deployed to *.workers.dev. Can use
-# # either route or workers_dev, but not both.
-# workers_dev = true
-# routes = [
-# "rollupid-dev.rollup.id/*",
-# ]
-
-# Services binding for local development
services = [
{ binding = "Galaxy", service = "galaxy" },
- { binding = "Address", service = "address" },
{ binding = "Images", service = "images" },
]
kv_namespaces = [
- { binding = "ProfileKV", id = "6510b0e703694f0388ced3de37df869d", preview_id = "b827cd51f02e4a569ed7b9ff2c680a75" },
+ { binding = "ProfileKV", id = "ProfileKV", preview_id = "ProfileKV" },
]
-[vars]
-PASSPORT_URL = "http://localhost:10001"
-PASSPORT_AUTH_URL = "http://localhost:10001/authorize"
-PASSPORT_TOKEN_URL = "http://127.0.0.1:10001/token"
-IMAGES_URL = "http://localhost/trpc"
-REDIRECT_URI = "http://localhost:10003/auth/callback"
-COOKIE_DOMAIN = "localhost"
-TWITTER_URL = "https://twitter.com/rollupid"
-DISCORD_URL = "https://discord.gg/rollupid"
-MINTPFP_CONTRACT_ADDRESS = "0x028aE75Bb01eef2A581172607b93af8D24F50643"
-INTERNAL_GOOGLE_ANALYTICS_TAG = "G-NHNH4KRWC3"
-PROFILE_VERSION = 1
-ALCHEMY_ETH_NETWORK = "mainnet"
-ALCHEMY_POLYGON_NETWORK = "mainnet"
+[build]
+command = "yarn build -- --sourcemap"
[site]
bucket = "./public"
-[build]
-command = "yarn build -- --sourcemap"
-
[dev]
port = 10003
inspector_port = 11003
local_protocol = "http"
-# dev (development)
-# ------------------------------------------------------------------------------
-[env.dev]
-routes = [
- { pattern = "my-dev.rollup.id", custom_domain = true, zone_name = "rollup.id" },
-]
-services = [
- { binding = "Galaxy", service = "galaxy-dev" },
- { binding = "Address", service = "address-dev" },
- { binding = "Images", service = "images-dev" },
-
-]
-kv_namespaces = [
- { binding = "ProfileKV", id = "6510b0e703694f0388ced3de37df869d" },
-]
-[env.dev.vars]
-
-PASSPORT_URL = "https://passport-dev.rollup.id"
-PASSPORT_AUTH_URL = "https://passport-dev.rollup.id/authorize"
-IMAGES_URL = "https://images-dev.rollup.id/trpc"
-PASSPORT_TOKEN_URL = "https://passport-dev.pz3r0.com/token"
-CLIENT_ID = ""
-REDIRECT_URI = "https://my-dev.rollup.id/auth/callback"
-COOKIE_DOMAIN = "rollup.id"
+[vars]
+PASSPORT_URL = "http://localhost:10001"
+PASSPORT_AUTH_URL = "http://localhost:10001/authorize"
+PASSPORT_TOKEN_URL = "http://127.0.0.1:10001/token"
+IMAGES_URL = "http://localhost/trpc"
+REDIRECT_URI = "http://localhost:10003/auth/callback"
+COOKIE_DOMAIN = "localhost"
TWITTER_URL = "https://twitter.com/rollupid"
DISCORD_URL = "https://discord.gg/rollupid"
MINTPFP_CONTRACT_ADDRESS = "0x028aE75Bb01eef2A581172607b93af8D24F50643"
@@ -86,71 +39,3 @@ INTERNAL_GOOGLE_ANALYTICS_TAG = "G-NHNH4KRWC3"
PROFILE_VERSION = 1
ALCHEMY_ETH_NETWORK = "mainnet"
ALCHEMY_POLYGON_NETWORK = "mainnet"
-
-# next (staging)
-# ------------------------------------------------------------------------------
-[env.next]
-routes = [
- { pattern = "my-next.rollup.id", custom_domain = true, zone_name = "rollup.id" },
-]
-services = [
- { binding = "Galaxy", service = "galaxy-next" },
- { binding = "Address", service = "address-next" },
- { binding = "Images", service = "images-next" },
-
-]
-kv_namespaces = [
- { binding = "ProfileKV", id = "451e189131d74dca87b41701555a839d" },
-]
-[env.next.build]
-command = "yarn build"
-
-[env.next.vars]
-
-PASSPORT_URL = "https://passport-next.rollup.id"
-PASSPORT_AUTH_URL = "https://passport-next.rollup.id/authorize"
-IMAGES_URL = "https://images-next.rollup.id/trpc"
-PASSPORT_TOKEN_URL = "https://passport-next.pz3r0.com/token"
-CLIENT_ID = ""
-REDIRECT_URI = "https://my-next.rollup.id/auth/callback"
-COOKIE_DOMAIN = "rollup.id"
-TWITTER_URL = "https://twitter.com/rollupid"
-DISCORD_URL = "https://discord.gg/rollupid"
-MINTPFP_CONTRACT_ADDRESS = "0x028aE75Bb01eef2A581172607b93af8D24F50643"
-INTERNAL_GOOGLE_ANALYTICS_TAG = "G-X7ZN16M4NB"
-PROFILE_VERSION = 1
-ALCHEMY_ETH_NETWORK = "mainnet"
-ALCHEMY_POLYGON_NETWORK = "mainnet"
-
-# current (production)
-# ------------------------------------------------------------------------------
-[env.current]
-routes = [
- { pattern = "my.rollup.id", custom_domain = true, zone_name = "rollup.id" },
-]
-services = [
- { binding = "Galaxy", service = "galaxy-current" },
- { binding = "Address", service = "address-current" },
- { binding = "Images", service = "images-current" },
-]
-kv_namespaces = [
- { binding = "ProfileKV", id = "3a5214b7dab24414a1cb65733a6143b2" },
-]
-[env.current.build]
-command = "yarn build"
-
-[env.current.vars]
-PASSPORT_URL = "https://passport.rollup.id"
-PASSPORT_AUTH_URL = "https://passport.rollup.id/authorize"
-IMAGES_URL = "https://images.rollup.id/trpc"
-PASSPORT_TOKEN_URL = "https://passport.pz3r0.com/token"
-CLIENT_ID = ""
-REDIRECT_URI = "https://my.rollup.id/auth/callback"
-COOKIE_DOMAIN = "rollup.id"
-TWITTER_URL = "https://twitter.com/rollupid"
-DISCORD_URL = "https://discord.gg/rollupid"
-MINTPFP_CONTRACT_ADDRESS = "0x3ebfaFE60F3Ac34f476B2f696Fc2779ff1B03193"
-INTERNAL_GOOGLE_ANALYTICS_TAG = "G-675VJMWSRY"
-PROFILE_VERSION = 1
-ALCHEMY_ETH_NETWORK = "mainnet"
-ALCHEMY_POLYGON_NETWORK = "mainnet"
diff --git a/docs/.gitbook/assets/CleanShot 2023-06-28 at 13.30.13.png b/docs/.gitbook/assets/CleanShot 2023-06-28 at 13.30.13.png
new file mode 100644
index 0000000000..c005667f6c
Binary files /dev/null and b/docs/.gitbook/assets/CleanShot 2023-06-28 at 13.30.13.png differ
diff --git a/docs/.gitbook/assets/CleanShot 2023-06-28 at 13.44.27.png b/docs/.gitbook/assets/CleanShot 2023-06-28 at 13.44.27.png
new file mode 100644
index 0000000000..f7731e1ca5
Binary files /dev/null and b/docs/.gitbook/assets/CleanShot 2023-06-28 at 13.44.27.png differ
diff --git a/docs/.gitbook/assets/Connect New Account (1).png b/docs/.gitbook/assets/Connect New Account (1).png
new file mode 100644
index 0000000000..2f2d182052
Binary files /dev/null and b/docs/.gitbook/assets/Connect New Account (1).png differ
diff --git a/docs/.gitbook/assets/Dropdown.png b/docs/.gitbook/assets/Dropdown.png
new file mode 100644
index 0000000000..959659f067
Binary files /dev/null and b/docs/.gitbook/assets/Dropdown.png differ
diff --git a/docs/.gitbook/assets/No Data (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (1).png b/docs/.gitbook/assets/No Data (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (1).png
new file mode 100644
index 0000000000..ddc22800d4
Binary files /dev/null and b/docs/.gitbook/assets/No Data (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (1).png differ
diff --git a/docs/.gitbook/assets/No Data (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (2).png b/docs/.gitbook/assets/No Data (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (2).png
new file mode 100644
index 0000000000..ddc22800d4
Binary files /dev/null and b/docs/.gitbook/assets/No Data (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (2).png differ
diff --git a/docs/.gitbook/assets/ezgif.com-video-to-gif.gif b/docs/.gitbook/assets/ezgif.com-video-to-gif.gif
new file mode 100644
index 0000000000..e1bd13583b
Binary files /dev/null and b/docs/.gitbook/assets/ezgif.com-video-to-gif.gif differ
diff --git a/docs/.gitbook/assets/image (1) (1) (1).png b/docs/.gitbook/assets/image (1) (1) (1).png
new file mode 100644
index 0000000000..e435c42bbb
Binary files /dev/null and b/docs/.gitbook/assets/image (1) (1) (1).png differ
diff --git a/docs/.gitbook/assets/image (1) (1).png b/docs/.gitbook/assets/image (1) (1).png
new file mode 100644
index 0000000000..e59edb03c0
Binary files /dev/null and b/docs/.gitbook/assets/image (1) (1).png differ
diff --git a/docs/.gitbook/assets/image (1).png b/docs/.gitbook/assets/image (1).png
index e435c42bbb..4a82129016 100644
Binary files a/docs/.gitbook/assets/image (1).png and b/docs/.gitbook/assets/image (1).png differ
diff --git a/docs/.gitbook/assets/image (6).png b/docs/.gitbook/assets/image (6).png
new file mode 100644
index 0000000000..71dfd9c115
Binary files /dev/null and b/docs/.gitbook/assets/image (6).png differ
diff --git a/docs/.gitbook/assets/image (7).png b/docs/.gitbook/assets/image (7).png
new file mode 100644
index 0000000000..71dfd9c115
Binary files /dev/null and b/docs/.gitbook/assets/image (7).png differ
diff --git a/docs/.gitbook/assets/image (8).png b/docs/.gitbook/assets/image (8).png
new file mode 100644
index 0000000000..811f1e9176
Binary files /dev/null and b/docs/.gitbook/assets/image (8).png differ
diff --git a/docs/.gitbook/assets/image (9).png b/docs/.gitbook/assets/image (9).png
new file mode 100644
index 0000000000..811f1e9176
Binary files /dev/null and b/docs/.gitbook/assets/image (9).png differ
diff --git a/docs/.gitbook/assets/image.png b/docs/.gitbook/assets/image.png
index e59edb03c0..edb1677c62 100644
Binary files a/docs/.gitbook/assets/image.png and b/docs/.gitbook/assets/image.png differ
diff --git a/docs/3id/overview/index.md b/docs/3id/overview/index.md
index 3a52c2b6f4..45eae42a7b 100644
--- a/docs/3id/overview/index.md
+++ b/docs/3id/overview/index.md
@@ -1,14 +1,14 @@
---
-description: Identity management for the private web.
+description: Simple & Secure Auth the private web.
---
-# Introduction to Rollup
+# Welcome to Rollup
-Rollup ID is a user management platform designed to prioritize privacy, security, and ease of use for both you and your users. By providing a user-centric and privacy-focused digital identity solution, we empower users to take control of their online data and identities, while also offering your business enhanced security, control, and lifecycle management.
+Rollup ID is a cutting-edge user management platform, meticulously crafted to prioritize privacy, security, and user-friendliness. Our primary goal is to empower users by providing a digital identity solution that places them at the center, allowing them to take control of their online data and identities.
-Our open-source platform utilizes established open standards and protocols, ensuring seamless compatibility with your existing infrastructure and services. This makes it effortless to integrate Rollup ID and collaborate across various systems. No matter if you're a Web3 or SaaS company, our flexible and scalable infrastructure can help reduce costs, improve user experiences, and guarantee interoperability.
+At the same time, we understand the needs of applications. That's why Rollup ID is designed to offer enhanced security, control, and lifecycle management for your application. With Rollup ID, you can ensure a secure and seamless user experience while maintaining the highest standards of data privacy and protection.
-We're thrilled to have you on board and are committed to collaborating with you in creating a safer and more connected web. Join us in building a secure and unified internet experience with Rollup ID, your user access bridge to a more private web, everywhere.
+Join us on this journey towards a more secure and privacy-focused digital world. Welcome to Rollup ID.
#### Get Started
@@ -16,6 +16,6 @@ We're thrilled to have you on board and are committed to collaborating with you
#### Learn More
-1. [Understanding Passport](../../platform/passport.md)
+1. [An Introduction to OIDC](../../an-introduction-to-openid-connect-oidc.md)
2. [Explore the Galaxy API](../../reference/galaxy-api.md)
3. [Learn about the Profile Graph](../../platform/profile-graph.md)
diff --git a/docs/CONTENTS.md b/docs/CONTENTS.md
index cff7d7a2fa..d50c93a84a 100644
--- a/docs/CONTENTS.md
+++ b/docs/CONTENTS.md
@@ -1,21 +1,27 @@
# Table of contents
-* [Introduction to Rollup](3id/overview/index.md)
-* [How it works](how-it-works.md)
+* [Welcome to Rollup](3id/overview/index.md)
+* [An Introduction to OpenID Connect (OIDC)](an-introduction-to-openid-connect-oidc.md)
+* [The Rollup Identity Graph](how-it-works.md)
## Getting Started
-* [Overview](getting-started/overview.md)
-* [Create an application](getting-started/create-an-application.md)
-* [Logging in Users](getting-started/auth-flow.md)
-* [Accessing Profile API](getting-started/accessing-profile-api.md)
+* [Create an Application](getting-started/create-an-application.md)
+* [Setup Auth Flow](getting-started/auth-flow.md)
+* [API Access](getting-started/accessing-profile-api.md)
## Guides
-* [Adding Users to your Database](guides/connecting-to-your-database.md)
-* [Authenticating Users in your App](guides/authenticating-users-in-your-app.md)
-* [Setup with Supabase](guides/setup-with-supabase.md)
-* [Using Smart Contract Wallets](guides/using-smart-contract-wallets.md)
+* [Storing Tokens](guides/connecting-to-your-database.md)
+* [Session Management](guides/authenticating-users-in-your-app.md)
+* [Using Scopes](guides/using-scopes/README.md)
+ * [Requesting Email](guides/using-scopes/requesting-email.md)
+ * [Requesting Connected Accounts](guides/using-scopes/connected-accounts.md)
+ * [Requesting Smart Contract Wallets](guides/using-scopes/using-smart-contract-wallets.md)
+* [Third Party Auth Tools](guides/third-party-auth-tools/README.md)
+ * [Setup with Auth0](guides/third-party-auth-tools/setup-with-auth0.md)
+ * [Setup with Supabase](guides/third-party-auth-tools/setup-with-supabase.md)
+ * [Setup with NextAuth.js](guides/third-party-auth-tools/using-nextauth.js.md)
## Platform
diff --git a/docs/advanced/tokens.md b/docs/advanced/tokens.md
index 4b01496bbc..2bd738cc0f 100644
--- a/docs/advanced/tokens.md
+++ b/docs/advanced/tokens.md
@@ -15,7 +15,7 @@ The decoded contents of an ID token looks like the following:
```json
{
"iss": "https://passport.rollup.id/",
- "sub": "{accountUrn}",
+ "sub": "{identityUrn}",
"aud": ["{yourClientId}"],
"exp": 1311281970,
"iat": 1311280970,
@@ -42,7 +42,7 @@ Here is an example of an access token:
```json
{
"iss": "https://passport.rollup.id/",
- "sub": "{accountUrn}",
+ "sub": "{identityUrn}",
"aud": ["{yourClientId}"],
"azp": "{yourClientId}",
"exp": 1489179954,
@@ -58,3 +58,28 @@ In the Pro version of Rollup you can set the issuer (iss) to your own custom dom
Access tokens do not contain any user information other than their ID (sub claim) and authorization information about the actions the application is allowed to perform via the API (scope claim). This makes them useful for securing an API but not for authenticating a user.
In some situations, it may be desirable to include additional information about the user or other custom claims in the access token to save the API from having to fetch details about the user. If you choose to do this, be aware that these extra claims will be readable in the access token. Non-access claims, however, will be readable in the ID token. To learn more, read [Create Custom Claims](custom-claims.md).
+
+## Refresh Tokens
+
+In addition to ID tokens and access tokens, OAuth 2.0 also introduces the concept of refresh tokens. A refresh token is a special kind of token that can be used to obtain a renewed access token. This is useful in situations where the access token has expired or has been invalidated, and the application needs to gain access to the user's resources without having to re-authenticate the user.
+
+Refresh tokens are typically long-lived and can be used to request new access tokens from the authorization server. This is done by sending a request to the token endpoint of the authorization server, including the refresh token along with the client's ID and secret.
+
+Here is an example of a refresh token:
+
+```json
+{
+ "iss": "https://passport.rollup.id/",
+ "aud": ["{yourClientId}"],
+ "sub": "{userId}",
+ "exp": "{expiryTimestamp}",
+ "iat": "{issuedAtTimestamp}",
+ "jti": "{uniqueTokenIdentifier}"
+}
+```
+
+In the Pro version of Rollup, you can set the issuer (iss) to your own custom domain. See the custom domain feature in your Console app.
+
+Refresh tokens provide a more seamless user experience by reducing the need for the user to re-authenticate and grant permissions, while also maintaining the security and privacy of the user's data. However, because refresh tokens can be used to obtain new access tokens, they must be stored securely and treated with the same level of care as the user's credentials.
+
+To learn more about using refresh tokens with Rollup ID, refer to our [API documentation](../reference/passport-api.md#exchange-token) and resources.
diff --git a/docs/an-introduction-to-openid-connect-oidc.md b/docs/an-introduction-to-openid-connect-oidc.md
new file mode 100644
index 0000000000..3f1cb37d1d
--- /dev/null
+++ b/docs/an-introduction-to-openid-connect-oidc.md
@@ -0,0 +1,69 @@
+---
+description: How Rollup ID works with OIDC / OAuth 2.0
+---
+
+# An Introduction to OpenID Connect (OIDC)
+
+Rollup ID is an innovative Identity Provider (IDP) that leverages a unique, decentralized approach to identity management. By adhering to established protocols such as [OAuth 2.0](https://oauth.net/2/) and [OpenID](https://openid.net/developers/how-connect-works/) Connect (OIDC), Rollup ID offers a secure, consent-based authorization mechanism, enabling users to interact safely with online applications. This article will explore OIDC and its application within the Rollup ID platform.
+
+Auth Flow
+
+### **Understanding OIDC**
+
+OpenID Connect (OIDC) is a straightforward identity layer built on top of the OAuth 2.0 protocol. It enables clients to verify an end-user's identity based on the authentication performed by an authorization server. Additionally, OIDC provides basic profile information about the end-user in an interoperable and REST-like manner.
+
+OIDC plays a crucial role in standardizing user authentication, simplifying the process for various online services to share this responsibility securely.
+
+### **The Application of OIDC in Rollup ID**
+
+Rollup ID not only embraces the OIDC and OAuth 2.0 standards but also introduces a layer of decentralized control. This approach effectively returns the control of privacy and data security to the users. Here's how it works:
+
+* **Decentralized Control:** Unlike traditional OIDC setups where authorization tokens are issued by the IDP, in the Rollup ID ecosystem, users issue these tokens themselves. This user-centric approach provides full control and transparency to the users over their authorizations.
+* **Consent-based Authorizations:** Rollup ID operates on consent-based authorizations to services and data, giving users complete control over which applications can access their data and the extent of this access.
+* **Flexible and Extensible Claims:** Authorization claims can include access to profile data, linked accounts, email addresses, Personally Identifiable Information (PII) / Know Your Customer (KYC) data, smart contract wallets, and more. The platform is also designed to allow third-parties to publish custom claims in the future.
+* **Easy Onboarding and Offboarding:** Rollup ID enables businesses to create simple, user-friendly onboarding and offboarding experiences that meet regulatory and compliance requirements while aligning with user needs and expectations.
+* **Security and Fraud Prevention:** Rollup ID safeguards users and developers from "fake accounts" through its robust identity verification features, including passkeys and other multifactor authentication (MFA) methods.
+
+By incorporating these features, Rollup ID achieves "logical" or "sufficient" decentralization. It provides a secure, private, and user-controlled identity platform that adheres to the standards set by OIDC, ensuring a seamless and secure online experience for all users.
+
+### **Registering Applications with Rollup ID**
+
+Before OIDC authentication can take place, developers must register their applications with Rollup ID. This can be done by create a new application on the [Developer Portal](https://console.rollup.id) and following the [Getting Started guide](broken-reference).
+
+### **Authentication Flow**
+
+Rollup ID supports the authorization code from the OIDC spec. Although applications without backend servers (that may be running purely client-side) are more suited to implicit authentication flows, they are no longer recommended as they are not secure.
+
+Authentication begins with a request to the /authorize endpoint. When using the native Sign In with Rollup ID page, most of the OIDC process is handled for you. You can begin the authentication cycle by redirecting your users to the /authorize endpoint with the appropriate parameters.
+
+```
+https://passport.rollup.id/authorize?client_id={client_id}&response_type={code}&redirect_uri={encoded_redirect_url}&state={state_value}&scope=oidc+profile+email
+```
+
+### **Redirect Responses**
+
+Once successfully authorized, the user is redirected back to your application. The redirect URL will contain a number of values, depending on the flow you are using.
+
+The authorization code flow, the redirect URL will contain the `code` (an authorization code that can be exchanged for an ID token) and the `state` (the optional state value passed to the /authorize endpoint).
+
+### **ID Token Verification**
+
+ID tokens must always be verified and should not be blindly accepted. To verify an ID token, fetch the public key from the [/jwks](reference/passport-api.md#jwks) endpoint. More details about this process can be found in the API documentation.
+
+```javascript
+import * as jose from 'jose'
+
+const verifyJwt = (token: string) => {
+ const JWKS = jose.createRemoteJWKSet(new URL('https://passport.rollup.id/jwks'))
+
+ const { payload, header } = await jose.jwtVerify(token, JWKS, {
+ issuer: 'https://rollup.id',
+ aud: '',
+ })
+
+ return payload
+}
+
+verifyJwt('eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp.eyAs.XVCJ9...')
+
+```
diff --git a/docs/getting-started/accessing-profile-api.md b/docs/getting-started/accessing-profile-api.md
index e2f70447ce..44249e8ee3 100644
--- a/docs/getting-started/accessing-profile-api.md
+++ b/docs/getting-started/accessing-profile-api.md
@@ -1,13 +1,11 @@
---
-description: Using Galaxy to access the user profile graph.
+description: Using Rollup ID APIs to manage tokens and access the user profile graph.
---
-# Accessing Profile API
+# API Access
-Now that you have the access token from the previous step, you can make authorized requests to the [Galaxy API](../reference/galaxy-api.md) and [Passport API](../reference/passport-api.md).
+Once you've successfully integrated Rollup ID and authenticated your users you can now make authorized requests to the [Galaxy API](../reference/galaxy-api.md) and [Passport API](../reference/passport-api.md).
-The Galaxy API is a GraphQL API accessible at [https://galaxy.rollup.id](https://galaxy.rollup.id/). To incorporate our GQL schemas into your GraphQL client, you can use GraphQL codegen tools.
+The Galaxy API is both a [REST API ](https://galaxy.rollup.id/swagger)and [GraphQL](https://galaxy.rollup.id/graphql) API.
-When calling the API, you'll need to include the [Galaxy API key](create-an-application.md) from your app and, in some cases, the signed JWT/access token.
-
-For a comprehensive overview of the API, please refer to the full API reference documentation available [here](../reference/galaxy-api.md).
+When calling the API, **you'll need to include the** [**Galaxy API key**](create-an-application.md) from your app and, in some cases, the signed JWT/access token.
diff --git a/docs/getting-started/auth-flow.md b/docs/getting-started/auth-flow.md
index 5291a878bd..3e6698590b 100644
--- a/docs/getting-started/auth-flow.md
+++ b/docs/getting-started/auth-flow.md
@@ -1,42 +1,52 @@
---
-description: Authenticating and authorizing users into your application.
+description: Logging in users into your application.
---
-# Logging in Users
+# Setup Auth Flow
{% hint style="warning" %}
For this step you will need the **Client ID** and the **Client Secret** from the [previous step](create-an-application.md).
{% endhint %}
-Since Rollup ID is **standards-compliant**, integrating it into your application is similar to integrating other OAuth-based authentication services like [Auth0](https://auth0.com/) or [Firebase](https://firebase.google.com/) / [Supabase](https://supabase.com/). You can use off-the-shelf [open-source libraries](https://oauth.net/code/) to build your OAuth flow.
+The Rollup ID authentication flow is built upon the [OpenID Connect (OIDC) protocol](https://openid.net/developers/how-connect-works/), ensuring a secure and standardized process. This guide will walk you through the steps involved in this flow.
-We recommend setting up two routes in your application called `/auth/login` and `/auth/callback` to manage the authorization flow.
+For this step, since Rollup ID is **standards-compliant,** you can use off-the-shelf [open-source libraries](https://oauth.net/code/) to build your OAuth flow.
{% hint style="info" %}
+Many open source OAuth client libraries like [Auth.js](https://authjs.dev/) will automatically create the necessary routes and manage client state and requests for your application that you wil see below.
+
We have created a reference implementation using [Remix](https://remix.run/) and the [Remix OAuth](https://github.com/sergiodxa/remix-auth) library [here](https://github.com/proofzero/rollupid/tree/main/apps/profile/app/routes/auth) which we will refer to several times in this step.
{% endhint %}
-### Step 1: Initiate Authentication
+### Step 1: Initiate the Auth Request
+
+To log in or register to a user your application the first step is to send an authorization request to your Rollup ID application. This is typically handled by a redirect route (e.g.`/auth`) in your application.
+
+This request contains essential parameters, including the `client_id` (your application's ID), `response_type`, `scope`, `state` and `redirect_url`.
-To begin the authentication flow, redirect users to the Passport authorization endpoint with the Client ID and a randomly generated state parameter included in the query string. The URL should look like this: `https://passport.rollup.id/authorize?client_id=&state=&scope=email`
+The `state` parameter is a CSRF token used to protect against potential attacks and to maintain any state that might be useful after the authentication process (more on this later). Before redirecting users to the Rollup ID auth flow, it is important that the state parameter is persisted in a cookie or other storage method for reference in a later step.
-You can achieve this by redirecting users to a route in your application that subsequently redirects them to the URL above.
+For example, your authorization URL should look like this: `https://passport.rollup.id/authorize?client_id=&state=&scope=email`.
{% hint style="info" %}
-For PRO accounts, custom hostnames of Passport are allowed.
+For PRO apps, [custom hostnames](../platform/console/custom-domain.md) are allowed and would replace `passport.rollup.id` with your domain name and more customization features would be available in the [designer](../platform/console/designer.md).
{% endhint %}
-Persist the state parameter in a cookie or other storage method for reference in a later step. In our reference implementation, the remix-oauth library handles this automatically. In your application, your chosen library will typically handle this for you.
+### Step 2: User Authentication & Authorization
+
+Rollup ID will use the provided `client_id` to look up your application details, displaying your application name and branding information.
+
+If the user is not signed in they will be displayed the authentication page where they can choose from various authentication methods. After successful authentication, the user will be shown the authorization screen to provide consent for the requested scopes, allowing your application to access specific data.
+
+Upon completion, users will be redirected back to your app using the `redirect_url` set in the previous step.
-Rollup ID will use the provided Client ID to look up your application details, displaying your application name and branding information. If your application requires specific authorization scopes, users will be presented with an authorization screen.
-
-Upon completion, users will be redirected back to your app using the "Redirect URL" set in the previous step.
+
-### Step 2: Handle Callback
+### Step 3: **Authorization Code Exchange**
-Your Redirect URL should be ready to accept an exchange token and state parameters in the following format:
+Your `redirect_url` should be ready to accept an exchange token and state parameters in the following format:
```
// https://?code=&state=
@@ -46,74 +56,22 @@ Your Redirect URL should be ready to accept an exchange token and state paramete
* **State:** this state should match the state you created for the user/client in Step 1. _Typically your chosen OAuth library will manage this for you._
* **Redirect URL**: the redirect url set in your app in the [previous step](create-an-application.md). _For development, "localhost" is an accepted redirect url host._
-Ensure the state parameter matches the state you sent when initiating the auth flow in Step 1. This security measure helps prevent replay attacks. Send the exchange code along with the Client Secret and **grant type** to Passport's token endpoint (see below) to receive the access token, refresh token, and minimal user profile (encoded in an ID token) as base64-encoded signed JWTs, completing the flow.
-
-{% swagger method="post" path="" baseUrl="https://passport.rollup.id/token" summary="Exchange access code for access token" %}
-{% swagger-description %}
-Exchange access code for access token, refresh token and ID token.
-
-_(For more details visit the_ [_Passport API_](../platform/passport.md) _page)_
-{% endswagger-description %}
-
-{% swagger-parameter in="body" name="code" type="String" required="true" %}
-Exchange code
-{% endswagger-parameter %}
-
-{% swagger-parameter in="body" name="client_id" required="true" type="String" %}
-Application client id
-{% endswagger-parameter %}
-
-{% swagger-parameter in="body" name="client_secret" type="String" required="true" %}
-Appication client secret
-{% endswagger-parameter %}
+To exchange the code for an access token use this information to call the exchange token endpoint using the "authorization\_code" value for "grant\_type"
-{% swagger-parameter in="body" name="grant_type" type="String" required="true" %}
-"authorization\_code" or
+### Step 4: Access Token & ID Token
-"refresh\_token"
-{% endswagger-parameter %}
-
-{% swagger-response status="201: Created" description="" %}
-```javascript
-{
- access_token: "ey....",
- refresh_token: "ey....",
- token_type: 'Bearer',
- id_token: "ey....",
-}
-```
-{% endswagger-response %}
-{% endswagger %}
-
-#### Access Token
-
-Access tokens are valid for 1 hour, with the expiry time stored in the "exp" property in the JWT. Refresh tokens, on the other hand, are valid for 90 days and can be used to request another access token using the same exchange code endpoint with the "refresh\_token" grant type.
+With the obtained access token, your application can now fetch the [id token](../advanced/tokens.md#id-tokens) from the Rollup ID `/userinfo`[endpoint](../reference/passport-api.md#user-info) to fetch or any authorized endpoint from the [Galaxy API](../reference/galaxy-api.md).
{% hint style="info" %}
-There are multiple ways to manage this refresh flow, [here](../../apps/profile/app/utils/session.server.tsx#L52) is our reference implementation. In summary, we store the tokens encrypted in a user cookie that is valid for 90 days and refresh when needed.\
-\
-If you ever find yourself with an expired refresh token you can consider this as the user being "logged out" and redirect them back to passport for login to repeat this flow.
+Inside the ID token object you will find a unique claim called `sub` which will be consistent across all logins. This will match the value of the `sub` claim in access and refresh tokens also.
{% endhint %}
-#### ID Token
-
-ID tokens are only supplied when the initial set of tokens is retrieved, and are not provided again during usage of refresh tokens. Use the `/userinfo` [endpoint](../reference/passport-api.md#user-info) to retrieve fresh user details. The response, as well as what is encoded in the ID token, is shaped as follows:
+Ensure you handle this data with care, respecting user privacy and adhering to data protection regulations.
-```typescript
-{
- name: string,
- picture: string,
- email: string,
- //...ID token encodes other claims as well
-}
-```
-
-{% hint style="info" %}
-Inside the ID token object you will find a unique claim called `sub` which will be consistent across all logins. This will match the value of the `sub` claim in access and refresh tokens also.
-{% endhint %}
+### Step 5: Refresh Tokens (optional)
-For more information on tokens and how to decode them please check out the [Tokens](../advanced/tokens.md) page.
+Access tokens are valid for 1 hour, with the expiry time stored in the "exp" property in the JWT. If your application requires prolonged access to user data without prompting the user for re-authentication, consider implementing refresh tokens using the [exchange token endpoint](../reference/passport-api.md#exchange-token).
-Here is a [link](../../apps/profile/app/routes/auth/callback.tsx) to the reference implementation doing just this. With the access token you can make authorized requests to the [Profile Graph](../platform/profile-graph.md) for this user.
+Refresh tokens allow your application to obtain new access tokens, ensuring uninterrupted access to user data.
-If you are coming from an existing provider please check out the [Migration Guide](../advanced/migration-guide.md).
+If you ever find yourself with an expired refresh token you can consider this as the user being "logged out" and redirect them to repeat the auth flow.
diff --git a/docs/getting-started/create-an-application.md b/docs/getting-started/create-an-application.md
index a70bfd489b..7b8b5085c2 100644
--- a/docs/getting-started/create-an-application.md
+++ b/docs/getting-started/create-an-application.md
@@ -2,67 +2,38 @@
description: Step-by-step guide to creating a Rollup application.
---
-# Create an application
-
-Follow these steps to create and configure your Rollup ID application:
+# Create an Application
### Step 1: Connect with Rollup Passport
-First, visit [https://console.rollup.id](https://passport.rollup.id/) and log in to the [Console](../platform/console/) using Rollup Passport. You'll see a screen that offers various authentication methods to register or log in.
-
-Rollup Passport Authentication
-
-### Step 2: Create an App
-
-Once you've logged in, you'll be redirected to the Console dashboard. Here, you can register and configure your Rollup ID application.
-
-Click on the "Create Application" button in the center of the screen. You'll be prompted to enter a name for your application and then redirected to the application's configuration screen.
-
-
-
-
-
-
+* Begin by visiting the [Rollup Console](https://console.rollup.id/) and logging in.
+* Once logged in, you'll land on the [Console](../platform/console/) dashboard. If this is your first time logging in you will be presented with an quick onboarding flow where you will be directed to create an application.
-
+### Step 2: Register Your Application
-
+* In your personal or group dashboard click on the "Create Application" button.
+* Provide a name for your application. After this, you'll be redirected to the application's configuration screen.
### Step 3: Configure your Application
-Upon creating your app, you'll land on the application dashboard. This is where you can obtain your [Galaxy API](../reference/galaxy-api.md) key and application keys.
+On the application dashboard, you can obtain your Galaxy API key and application keys.
{% hint style="warning" %}
The Client Secret is only shared once so, if you missed it you can click the "roll keys" link to regenerate the keys.
{% endhint %}
-
+Navigate to the "OAuth" section for a comprehensive application configuration. Here, you'll find a standard [OAuth 2.0](https://oauth.net/2/) configuration form. Ensure you fill in the required fields to customize your auth flow:
-
-
-
-
-
-
-
-
-Next, click the "OAuth" link in the left navigation bar to access the full application configuration screen. Here, you'll find a standard [OAuth 2.0](https://oauth.net/2/) configuration form.
-
-
-
-Fill in the following required fields:
-
-* **Redirect URL**: The address where Rollup will redirect your users to after they have completed the auth flow ([more on next page](auth-flow.md)).
-* **App Icon**: Your application's logo, which will be displayed to users during the auth flow ([see passport for more](../platform/passport.md)).
+* **Application Name:** The name of your application. This will be displayed to users during the auth flow.
+* **Redirect URL**: Where Rollup redirects users post-authentication to exchange tokens (more details on this on next page).
+* **App Icon**: Your application's logo, which will be displayed to users during the auth flow.
+* **Allowed scope**: The superset of [scope](../reference/scopes.md) values that the application can request from users.
* **Terms of Service URL**: A link to your application's TOS
-* **Website**: A link to your application's website
-* **Allowed scope**: The superset of scope values that the application can request
-* **Domains**: \[coming soon]
+* **Privacy Policy URL**: A link to your application's Privacy Policy
+* **Website URL**: A link to your application's website
{% hint style="warning" %}
-In most cases, you'll need to set up an app for each environment and provide the appropriate **redirect URL** for each. For instance, you might use a "**localhost**" redirect URL for local development and a "staging" redirect URL for test environments.
+For different environments (like development or testing), set up distinct apps with the appropriate redirect URLs (e.g. "localhost").
{% endhint %}
-Fill in any additional optional fields as needed. When you're done, click the "Published" toggle and then the "Save" button.
-
-With your application fully configured, you're now ready to complete the integration.
+Once all details are filled in, activate the "Published" toggle and hit the "Save" button. With your application fully configured, you're now ready to complete integrate the auth flow into your app.
diff --git a/docs/getting-started/overview.md b/docs/getting-started/overview.md
deleted file mode 100644
index 738a2884b6..0000000000
--- a/docs/getting-started/overview.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-description: User management for the private web
----
-
-# Overview
-
-Welcome to the Rollup ID Getting Started Guide! This overview will provide you with a brief introduction to our authorization infrastructure, which is designed to simplify and secure user logins while offering fine-grained access to user profiles.
-
-Rollup ID utilizes an OAuth-like authorization scope system, ensuring controlled access to user information. Our implementation extends the standard OAuth protocol to accommodate authorizations specific to user profiles. This allows you to effectively manage user data access and create a secure, customized experience for your users.
-
-You can checkout our [example apps](https://github.com/proofzero/example-apps) to get started quickly with your favorite stack. To learn more about what you can do with OAuth authorizations, check out the [Scopes Reference](../reference/scopes.md).
-
-
-
-By following this guide, you'll be well-equipped to take full advantage of Rollup ID's secure and user-friendly authorization infrastructure. Stay tuned for more detailed instructions and best practices in the upcoming sections.
diff --git a/docs/guides/connecting-to-your-database.md b/docs/guides/connecting-to-your-database.md
index decc1ee1ae..e994f9cdc3 100644
--- a/docs/guides/connecting-to-your-database.md
+++ b/docs/guides/connecting-to-your-database.md
@@ -2,7 +2,7 @@
description: How to connect users to your database
---
-# Adding Users to your Database
+# Storing Tokens
After completing the [auth flow](../getting-started/auth-flow.md) and obtaining a user's ID token and access token, you can choose from several strategies to connect users to your app's database. For more information on the ID token, refer to the [Tokens](../advanced/tokens.md) documentation.
@@ -15,3 +15,22 @@ You can create a user in your database using the ID token. The ID token contains
The access token also contains the user's unique identifier in the subject (sub) field. You can use this to identify the user in your database. Additionally, the access token includes the user's consented scopes, which help you determine the information you can access from the user's profile using the [Galaxy API](../reference/galaxy-api.md).
Make sure to store the access token securely in your database or another safe storage mechanism accessible to your application.
+
+## Refresh Token
+
+In addition to storing ID tokens, you may also need to store refresh tokens in your database. Refresh tokens are used to obtain new access tokens without requiring the user to re-authenticate. This can be particularly useful for providing a seamless user experience, especially for applications that require long-lived sessions.
+
+When you receive a refresh token, it should be stored securely in your database, associated with the user. Here's an example of how you might store a refresh token:
+
+```json
+{
+ "userId": "{userId}",
+ "refreshToken": "{refreshToken}"
+}
+```
+
+When the access token expires, your application can use the stored refresh token to request a new access token from the Rollup ID authorization server. This request would include the refresh token along with your application's client ID and secret.
+
+It's important to handle refresh tokens securely because they can be used to obtain new access tokens. If a refresh token is leaked, it could potentially allow unauthorized access to the user's resources. Therefore, refresh tokens should be stored securely and treated with the same level of care as the user's credentials.
+
+To learn more about using refresh tokens with Rollup ID, refer to our [API documentation](../reference/passport-api.md) and resources.
diff --git a/docs/guides/third-party-auth-tools/README.md b/docs/guides/third-party-auth-tools/README.md
new file mode 100644
index 0000000000..7c8a0a5d84
--- /dev/null
+++ b/docs/guides/third-party-auth-tools/README.md
@@ -0,0 +1,13 @@
+---
+description: Configuring Rollup ID with other auth tools
+---
+
+# Third Party Auth Tools
+
+{% content-ref url="setup-with-auth0.md" %}
+[setup-with-auth0.md](setup-with-auth0.md)
+{% endcontent-ref %}
+
+{% content-ref url="setup-with-supabase.md" %}
+[setup-with-supabase.md](setup-with-supabase.md)
+{% endcontent-ref %}
diff --git a/docs/guides/third-party-auth-tools/setup-with-auth0.md b/docs/guides/third-party-auth-tools/setup-with-auth0.md
new file mode 100644
index 0000000000..909e7af6c7
--- /dev/null
+++ b/docs/guides/third-party-auth-tools/setup-with-auth0.md
@@ -0,0 +1,7 @@
+---
+description: Configure Rollup ID with Auth0.
+---
+
+# Setup with Auth0
+
+You can configure Rollup ID as a generic OAuth2 authorization server by following [this guide](https://auth0.com/docs/authenticate/identity-providers/social-identity-providers/oauth2).
diff --git a/docs/guides/setup-with-supabase.md b/docs/guides/third-party-auth-tools/setup-with-supabase.md
similarity index 78%
rename from docs/guides/setup-with-supabase.md
rename to docs/guides/third-party-auth-tools/setup-with-supabase.md
index 4d30fb07ad..ec71f48285 100644
--- a/docs/guides/setup-with-supabase.md
+++ b/docs/guides/third-party-auth-tools/setup-with-supabase.md
@@ -24,7 +24,7 @@ In order to implement these hops, configure Rollup, Supabase, and your app as fo
Request authorization for the `Email` scope and set the Redirect URL to the Supabase Keycloak provider's redirect URL. You can get the redirect URL from the Keycloak provider configuration (see below).
-Rollup Console Configuration for the Supabase callback and required scopes.
+Rollup Console Configuration for the Supabase callback and required scopes.
Required scope values are:
@@ -39,7 +39,7 @@ Save and publish your application.
Within Supabase, select "Authentication" a nd then under "Configuration" select "Providers" and under the "Email" settings disable "Confirm Email" and save.
-
+
Next go to the Keycloak settings and enable Keycloak.
@@ -49,7 +49,7 @@ Set your Keycloak Realm to `https://passport.rollup.id` (the screenshot below sh
Copy your callback URL here and use it in your Rollup Console Application configuration (see above).
-Keycloak provider configuration for Supabase.
+Keycloak provider configuration for Supabase.
Save your settings.
diff --git a/docs/guides/third-party-auth-tools/using-nextauth.js.md b/docs/guides/third-party-auth-tools/using-nextauth.js.md
new file mode 100644
index 0000000000..658de9801c
--- /dev/null
+++ b/docs/guides/third-party-auth-tools/using-nextauth.js.md
@@ -0,0 +1,19 @@
+---
+description: How to configure NextAuth.js with Rollup ID
+---
+
+# Using NextAuth.js
+
+For this guide we will be setting up an OAuth provider as [documented](https://next-auth.js.org/configuration/providers/oauth) by NextAuth.js.
+
+### Step 1
+
+Follow the getting started guide for initial setup instructions.
+
+{% embed url="https://next-auth.js.org/getting-started/example" %}
+
+### Step 2
+
+Configure a custom provider using the guide linked below. You can also refer to our example configuration in our example apps repo [here](https://github.com/proofzero/example-apps/blob/main/nextjs/pages/api/auth/\[...nextauth].ts).
+
+{% embed url="https://next-auth.js.org/configuration/providers/oauth#using-a-custom-provider" %}
diff --git a/docs/guides/using-scopes/README.md b/docs/guides/using-scopes/README.md
new file mode 100644
index 0000000000..0cc365506f
--- /dev/null
+++ b/docs/guides/using-scopes/README.md
@@ -0,0 +1,13 @@
+---
+description: Requesting information from your users.
+---
+
+# Using Scopes
+
+{% content-ref url="requesting-email.md" %}
+[requesting-email.md](requesting-email.md)
+{% endcontent-ref %}
+
+{% content-ref url="connected-accounts.md" %}
+[connected-accounts.md](connected-accounts.md)
+{% endcontent-ref %}
diff --git a/docs/guides/using-scopes/connected-accounts.md b/docs/guides/using-scopes/connected-accounts.md
new file mode 100644
index 0000000000..9c19bd383f
--- /dev/null
+++ b/docs/guides/using-scopes/connected-accounts.md
@@ -0,0 +1,29 @@
+---
+description: Using the connected accounts scope
+---
+
+# Requesting Connected Accounts
+
+This guide will walk you through how to request email addresses from your user using the Connected Accounts [Scope](../../reference/scopes.md).
+
+### Step 1
+
+In your application OAuth configuration make sure that the "connected accounts" scope is selected in the "allowed scopes" dropdown. Make sure to save this configuration before moving to step 2.
+
+Connected Accounts Scope
+
+### Step 2
+
+When [logging in users](../../getting-started/auth-flow.md) make sure you add `connected_accounts` to the `scope` query param. Your auth url should look something like this `https:///authorize?client_id=xxx&state=xxx&scope=connected_accounts`
+
+### Step 3
+
+After authenticating into your custom Rollup application, your users will we be presented with an authorization screen containing the connected accounts scope authorization request.
+
+By default "All Connected Accounts" will be selected. Users will have the option to filter down connected accounts.
+
+Connected Accounts Prompt
+
+### Step 4
+
+When redirected back to your application, the [access token](../../advanced/tokens.md) will now contain an authorization for connected accounts. The connected accounts will also be available returned in the user token via the [user info endpoint](../../reference/passport-api.md#user-info).
diff --git a/docs/guides/using-scopes/requesting-email.md b/docs/guides/using-scopes/requesting-email.md
new file mode 100644
index 0000000000..a4defbf8aa
--- /dev/null
+++ b/docs/guides/using-scopes/requesting-email.md
@@ -0,0 +1,29 @@
+---
+description: Using the connected email scope
+---
+
+# Requesting Email
+
+This guide will walk you through how to request email addresses from your user using the Email [Scope](../../reference/scopes.md).
+
+### Step 1
+
+In your application OAuth configuration make sure that the "email" scope is selected in the "allowed scopes" dropdown. Make sure to save this configuration before moving to step 2.
+
+Email Scope Dropdown
+
+### Step 2
+
+When [logging in users](../../getting-started/auth-flow.md) make sure you add `email` to the `scope` query param. Your auth url should look something like this `https:///authorize?client_id=xxx&state=xxx&scope=email`
+
+### Step 3
+
+After authenticating into your custom Rollup application, your users will we be presented with an authorization screen containing the email scope authorization request.
+
+If the user already as a connected email, this email will be automatically selected. If no email address is connected to their identity they can connect an new email address and continue.
+
+Email Authorization
+
+### Step 4
+
+When redirected back to your application, the [access token](../../advanced/tokens.md) will now contain an authorization for email address. The email address will also be available returned in the user token via the [user info endpoint](../../reference/passport-api.md#user-info).
diff --git a/docs/guides/using-smart-contract-wallets.md b/docs/guides/using-scopes/using-smart-contract-wallets.md
similarity index 55%
rename from docs/guides/using-smart-contract-wallets.md
rename to docs/guides/using-scopes/using-smart-contract-wallets.md
index db99f3fca3..761e417934 100644
--- a/docs/guides/using-smart-contract-wallets.md
+++ b/docs/guides/using-scopes/using-smart-contract-wallets.md
@@ -2,31 +2,33 @@
description: Rollup Account Abstraction Claims and API
---
-# Using Smart Contract Wallets
+# Requesting Smart Contract Wallets
## Accessing You User's Smart Contract Wallets
With Rollup your can request access to your users [ERC 4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract wallets. If they don't have a smart contract wallet, no sweat, we will help them create one when they onboard to your application. The following will guide you through setting up this flow.
+**Currently for this feature we only support ethereum and polygon with their testnets.** [**Contact us**](https://discord.com/invite/rollupid) **if you'd like to add something else.**
+
### Prerequisites
-* Setup an application with one of our supported[ paymaster providers](../platform/console/blockchain.md#preferred-paymasters).
+* Setup an application with one of our supported[ paymaster providers](../../platform/console/blockchain.md#preferred-paymasters).
### Setup
1. Login into [Console](https://console.rollup.id)
-2. Go to your app (if you don't have one, [set one up](../getting-started/create-an-application.md))
+2. Go to your app (if you don't have one, [set one up](../../getting-started/create-an-application.md))
3. Go to the Blockchain setting tab
4. In the paymaster section, enter your paymaster credential and save.
5. Go to the OAuth settings tab
6. In the scopes dropdown select `erc_4337` scope
-7. Update your application to include the `erc_4337` scope in the [authorization request](../getting-started/auth-flow.md)
+7. Update your application to include the `erc_4337` scope in the [authorization request](../../getting-started/auth-flow.md)
-
+
### Registering Session Keys
-When your users login to your application you will now be presented with an access [token](../advanced/tokens.md) that contains the `erc_4337` claim and a list of smart contract wallet addresses and a nickname. For example:
+When your users login to your application you will now be presented with an access [token](../../advanced/tokens.md) that contains the `erc_4337` claim and a list of smart contract wallet addresses and a nickname. For example:
```json
{
@@ -40,16 +42,16 @@ When your users login to your application you will now be presented with an acce
}
```
-
+
-With this access token you can now make requests to the [galaxy-api.md](../reference/galaxy-api.md "mention") to register your [session key](https://twitter.com/chainlink/status/1636781219848372235). To register a session key you will always need to generate a ethers wallet and send the public address along with the specified smart contract wallet to register. For example:
+With this access token you can now make requests to the [galaxy-api.md](../../reference/galaxy-api.md "mention") to register your [session key](https://twitter.com/chainlink/status/1636781219848372235). To register a session key you will always need to generate a ethers wallet and send the public address along with the specified smart contract wallet to register. For example:
````typescript
import { Wallet } from 'ethers'
-// if using more than once we reccomend that you store the private key somewhere safe.
-// you will need the privateSigner to submit transactions using your session key.
-const privateSigner = Wallet.createRandom()
+// if using more than once we recommend that you store the private key somewhere safe.
+// you will need the privateSigner to submit transactions using your session key.
+const privateSigner = Wallet.createRandom()
const address = await privateSigner.address()
const sessionDataRes = await fetch("https://galaxy.rollup.id/rest/register-session-key", {
@@ -61,7 +63,7 @@ const sessionDataRes = await fetch("https://galaxy.rollup.id/rest/register-sessi
},
body: JSON.stringify({
smartContractWalletAddress: session.erc_4337[0].address, //users' smart contract wallet address
- sessionPublicKey: address, //public key for which to issue session key
+ sessionPublicKey: address, //ethereum address for which to issue session key
},
}),
})
@@ -77,6 +79,23 @@ Once a session key has been registered you should receive session key data that
When registering a session key we will use your configured paymaster provider and their tools to fulfill the registration. **Please ensure the API keys saved in your paymaster settings are the same you use in your application's transactions.**
{% endhint %}
+### Revocation of Session Key
+
+Once user authorized app to create a session key for provided wallet, user easily can revoke this session key at any time.
+
+Here's how to do that:
+
+1. Go to [Passport](https://passport.rollup.id)
+2. Navigate to `Applications` tab in side-menu
+3. Select app for which to revoke session key and hit `Edit Access` button
+4. Then push `Revoke Access` button
+
+This action will revoke all scopes user authorized app to use, including the revocation of session keys for all authorized smart contract wallets.
+
+{% hint style="warning" %}
+Revocation of Smart Contract wallets results in a blockchain transaction. Be aware that any revocation of smart contract wallet will cost gas fees.
+{% endhint %}
+
## Accessing Your App's Smart Contract Wallet
Save on fees with your applications personal L2 for batching transactions across multiple users. Coming soon: [https://github.com/proofzero/rollupid/issues/2252](https://github.com/proofzero/rollupid/issues/2252)
diff --git a/docs/platform/console/blockchain.md b/docs/platform/console/blockchain.md
index de105d8a31..e01d0bd0c5 100644
--- a/docs/platform/console/blockchain.md
+++ b/docs/platform/console/blockchain.md
@@ -12,7 +12,7 @@ This feature is in Early Access. [Login](https://console.rollup.id) to console a
Ethereum account abstraction and user deposit vault accounts provide secure and flexible management of funds on the Ethereum blockchain. With this feature, apps can sponsor gas fees can interact with a smart contract wallet using the Galaxy API, while each user's funds are kept in a separate deposit vault account, reducing the risk of unauthorized access or loss of funds.
-For more information read our guide: [using-smart-contract-wallets.md](../../guides/using-smart-contract-wallets.md "mention")
+For more information read our guide: [using-smart-contract-wallets.md](../../guides/using-scopes/using-smart-contract-wallets.md "mention")
## Preferred Account Abstraction Providers
diff --git a/docs/platform/console/oauth.md b/docs/platform/console/oauth.md
index 406edc387a..9e1c8d8278 100644
--- a/docs/platform/console/oauth.md
+++ b/docs/platform/console/oauth.md
@@ -24,7 +24,7 @@ Here you can roll your your app's client secret but make sure to update this val
### Details Panel
-OAuth App Details Panel
+OAuth App Details Panel
The details panel is where you configure your application name, picture, [authorization scopes](../../reference/scopes.md), [custom domain](custom-domain.md), and other links.
diff --git a/docs/platform/passport.md b/docs/platform/passport.md
index 9c6b7922e7..fcb197cdec 100644
--- a/docs/platform/passport.md
+++ b/docs/platform/passport.md
@@ -18,15 +18,15 @@ Once authenticated the user will the be redirected to the authorization screen.
Passport's authentication flow currently supports the following authentication methods and are configurable for your app [Console](console/):
-* Connect with Wallet
-* Connect with Email (coming soon)
-* Connect with WebAuthN (coming soon)
-* Connect with Google
-* Connect with Apple
-* Connect with Twitter
-* Connect with Github
-* Connect with Microsoft
-* Connect with Discord
+- Connect with Wallet
+- Connect with Email
+- Connect with Passkeys (aka. WebAuthn)
+- Connect with Google
+- Connect with Apple
+- Connect with Twitter
+- Connect with Github
+- Connect with Microsoft
+- Connect with Discord
#### Authorization
diff --git a/docs/reference/passport-api.md b/docs/reference/passport-api.md
index 6962ab5d46..92b0787f6e 100644
--- a/docs/reference/passport-api.md
+++ b/docs/reference/passport-api.md
@@ -26,11 +26,10 @@ Application client secret
{% endswagger-parameter %}
{% swagger-parameter in="body" name="grant_type" type="String" required="true" %}
-"authorization_code" or "refresh_token"
+"authorization\_code" or "refresh\_token"
{% endswagger-parameter %}
{% swagger-response status="201: Created" description="Exchange token response" %}
-
```javascript
{
access_token: string,
@@ -39,7 +38,6 @@ Application client secret
id_token: string
}
```
-
{% endswagger-response %}
{% endswagger %}
@@ -47,10 +45,9 @@ Application client secret
{% tabs %}
{% tab title="Javascript" %}
-
```typescript
const tokenForm = new Form()
-tokenForm.append('exchange_code', exchangeCode)
+tokenForm.append('code', exchangeCode)
tokenForm.append('grant_type', grantType)
tokenForm.append('client_id', clientId)
tokenForm.append('client_secret', clientSecret)
@@ -63,20 +60,17 @@ const { access_code, refresh_token } = await fetch(
}
)
```
-
{% endtab %}
{% tab title="Curl" %}
-
```bash
curl https://passport.rollup.id/token -X POST \
--header "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "client_id={clientId}" \
--data-urlencode "client_secret={clientSecret}"
- --data-urlencode "exchange_code={exchangeCode}"
+ --data-urlencode "code={exchangeCode}"
--data-urlencode "grant_type=authorization_code"
```
-
{% endtab %}
{% endtabs %}
@@ -98,7 +92,6 @@ Call this method to retrieve basic identity information for the user.
{% endswagger-parameter %}
{% swagger-response status="200: OK" description="User Info response" %}
-
```javascript
{
name: '(some name here)',
@@ -122,7 +115,6 @@ Call this method to retrieve basic identity information for the user.
]
}
```
-
{% endswagger-response %}
{% endswagger %}
@@ -130,7 +122,6 @@ Call this method to retrieve basic identity information for the user.
{% tabs %}
{% tab title="Javascript" %}
-
```typescript
const access_token = '(some access token value)'
@@ -141,17 +132,14 @@ const response = await fetch('https://passport.rollup.id/userinfo', {
})
const { name, picture } = await response.json()
```
-
{% endtab %}
{% tab title="Curl" %}
-
```bash
export token="(some token value)"
curl https://passport.rollup.id/userinfo \
--header "Authorization: Bearer $token"
```
-
{% endtab %}
{% endtabs %}
@@ -160,9 +148,11 @@ curl https://passport.rollup.id/userinfo \
The OpenID provider metadata can be accessed in the endpoint described below.
{% swagger method="get" path="" baseUrl="https://passport.rollup.id/.well-known/openid-configuration" summary="OpenID Configuration" %}
+{% swagger-description %}
-{% swagger-response status="200: OK" description="OpenID Configuration" %}
+{% endswagger-description %}
+{% swagger-response status="200: OK" description="OpenID Configuration" %}
```json
{
"issuer": "https://passport.rollup.id",
@@ -182,7 +172,6 @@ The OpenID provider metadata can be accessed in the endpoint described below.
"service_documentation": "https://docs.rollup.id/"
}
```
-
{% endswagger-response %}
{% endswagger %}
@@ -191,9 +180,11 @@ The OpenID provider metadata can be accessed in the endpoint described below.
The JWKS is the list of public keys to be used to validate token signatures.
{% swagger method="get" path="" baseUrl="https://passport.rollup.id/.well-known/jwks.json" summary="JWKS" %}
+{% swagger-description %}
-{% swagger-response status="200: OK" description="JWKS" %}
+{% endswagger-description %}
+{% swagger-response status="200: OK" description="JWKS" %}
```json
{
"keys": [
@@ -216,6 +207,5 @@ The JWKS is the list of public keys to be used to validate token signatures.
]
}
```
-
{% endswagger-response %}
{% endswagger %}
diff --git a/docs/reference/scopes.md b/docs/reference/scopes.md
index d000f4a2f7..33586afd73 100644
--- a/docs/reference/scopes.md
+++ b/docs/reference/scopes.md
@@ -6,12 +6,12 @@ This is a listing of scope values Rollup supports and plans to support for Rollu
# Scopes
-| Scope Name | Scope Description | Availability |
-| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
-| `openid` | Standard scope value indicating the authorization request to be an OIDC request. This provides an ID token as part of the token exchange. | ✅ |
-| `profile` | Standard scope value indicating that basic profile claims will be included in the ID token (see `openid`) as well as in the responses of calls to `/userinfo` endpoint. Currently, this includes the `name` and `picture` claims. | ✅ |
+| Scope Name | Scope Description | Availability |
+| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
+| `openid` | Standard scope value indicating the authorization request to be an OIDC request. This provides an ID token as part of the token exchange. | ✅ |
+| `profile` | Standard scope value indicating that basic profile claims will be included in the ID token (see `openid`) as well as in the responses of calls to `/userinfo` endpoint. Currently, this includes the `name` and `picture` claims. | ✅ |
| `email` | Standard scope value indicating that a configured email address will be included in the ID token as well as the `/userinfo` endpoint response. The value of this claim will come from the connected account the authorizing user selects in the authorization screen. | ✅ |
-| `connected_accounts` | Scope value that indicates that the type and address of each connected account will be included in the ID token as well as the `/userinfo` endpoint response. | ✅ |
-| `smart contract wallet` | | ⏳ |
-| `storage` | | ⏳ |
-| `kyc` | | 📅 |
+| `connected_accounts` | Scope value that indicates that the type and address of each connected account will be included in the ID token as well as the `/userinfo` endpoint response. | ✅ |
+| `smart contract wallet` | Scope value which indicates that the blockchain address and name of smart contract wallet will be included in the ID token as well as the `/userinfo` endpoint response. | ✅ |
+| `storage` | | ⏳ |
+| `kyc` | | 📅 |
diff --git a/docs/troubleshooting/faq.md b/docs/troubleshooting/faq.md
index 078d221856..34cc478b4b 100644
--- a/docs/troubleshooting/faq.md
+++ b/docs/troubleshooting/faq.md
@@ -8,16 +8,24 @@ description: Frequently Asked Questions
You can get support from the team by joining our [Discord](https://discord.gg/rollupid) or documenting any issues at our [Github](https://github.com/proofzero/rollupid/issues).
-## When will email authentication be supported?
+## When will WebAuthn/Passkeys be supported?
-We take user privacy very seriously. Email login requires more investment than third-party and wallet login services. You can track the work on this [here](https://github.com/proofzero/rollupid/milestone/6).
-
-## When will WebAuthn be supported?
-
-We're working on that now. Check out progress [here](https://github.com/proofzero/rollupid/milestone/6).
+Passkeys/WebAuthn has now been implemented and is now one of the available authentication methods in Rollup Passport.
## What is a Vault and when will it be available?
Vault is an ETH "burner wallet" linked to a user identity and can be created by developers using progressive authorizations. Access to these wallet is governed by advanced OAuth scopes and available over API and is perfect for onboarding non-technical users to blockchain powered applications.
You can check out progress on this feature [here](https://github.com/proofzero/rollupid/milestones).
+
+## How do I claim my promotional credits?
+
+1. Login to [https://console.rollup.id](https://console.rollup.id)
+2. Go to the "Billing and Invoicing" section on the bottom left navigation.
+3. Follow the prompt to select a billing email address.
+4. Email [promo@rollup.id](mailto:promo@rollup.id?subject=Claim%20my%20credits) with the email address selected in step 3. If you are claiming the additional $200 existing app credit and $200 1000 MAU credit let us know in the email by providing your app domain name and a screen shot of your total users.
+5. Someone will reply when credits have been applied
+
+{% hint style="info" %}
+We are working on automating this at purchase time.
+{% endhint %}
diff --git a/packages/design-system/.storybook/main.js b/packages/design-system/.storybook/main.js
index 8679c3806d..041ebc1b75 100644
--- a/packages/design-system/.storybook/main.js
+++ b/packages/design-system/.storybook/main.js
@@ -50,7 +50,6 @@ module.exports = {
Buffer: ['buffer', 'Buffer'],
})
)
-
config.resolve.fallback = {
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
@@ -69,6 +68,7 @@ module.exports = {
child_process: false,
}
+
// Return the altered config
return config
},
diff --git a/packages/design-system/package.json b/packages/design-system/package.json
index d5ea0e06bf..5984e10926 100644
--- a/packages/design-system/package.json
+++ b/packages/design-system/package.json
@@ -8,11 +8,12 @@
"dependencies": {
"@headlessui/react": "1.7.5",
"@radix-ui/react-popover": "1.0.5",
+ "classnames": "2.3.2",
"cra-template-typescript": "1.2.0",
"react": "18.2.0",
"react-countdown-circle-timer": "3.2.1",
"react-dom": "18.2.0",
- "react-icons": "4.8.0",
+ "react-icons": "4.10.1",
"react-json-pretty": "2.2.0",
"react-scripts": "5.0.1",
"styled-components": "5.3.6"
@@ -100,7 +101,7 @@
"storybook-addon-sass-postcss": "0.1.3",
"storybook-css-modules-preset": "1.1.1",
"style-loader": "3.3.1",
- "tailwindcss": "latest",
+ "tailwindcss": "3.3.3",
"webpack": "5.75.0"
}
}
diff --git a/packages/design-system/src/assets/social_icons/microsoft.svg b/packages/design-system/src/assets/social_icons/microsoft.svg
deleted file mode 100644
index 0370b3495b..0000000000
--- a/packages/design-system/src/assets/social_icons/microsoft.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/packages/design-system/src/atoms/accounts/ConnectNewButton.stories.tsx b/packages/design-system/src/atoms/accounts/ConnectNewButton.stories.tsx
deleted file mode 100644
index edaf026611..0000000000
--- a/packages/design-system/src/atoms/accounts/ConnectNewButton.stories.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import React from 'react'
-import { ConnectNewAccountButton } from './ConnectNewButton'
-
-export default {
- title: 'Atoms/Account/Button',
- component: ConnectNewAccountButton,
-}
-
-const Template = () => (
-
- {
- return null
- }}
- />
-
-)
-
-export const ConnectNewAccountButtonExample = Template.bind({}) as any
diff --git a/packages/design-system/src/atoms/accounts/ConnectNewButton.tsx b/packages/design-system/src/atoms/accounts/ConnectNewButton.tsx
deleted file mode 100644
index 6e3e81d801..0000000000
--- a/packages/design-system/src/atoms/accounts/ConnectNewButton.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react'
-import { Button } from '../buttons/Button'
-import { TbCirclePlus } from 'react-icons/tb'
-
-export const ConnectNewAccountButton = ({
- phrase,
- onConnectNew,
- className,
-}: {
- phrase: string
- onConnectNew: () => void
- className?: string
-}) => {
- return (
-
-
- {phrase}
-
- )
-}
diff --git a/packages/design-system/src/atoms/accounts/ConnectedAccountSelect.stories.tsx b/packages/design-system/src/atoms/accounts/ConnectedAccountSelect.stories.tsx
deleted file mode 100644
index f5aeec8d71..0000000000
--- a/packages/design-system/src/atoms/accounts/ConnectedAccountSelect.stories.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react'
-import { ConnectedAccountSelect } from './ConnectedAccountSelect'
-
-export default {
- title: 'Atoms/Account/Select',
- component: ConnectedAccountSelect,
-}
-
-const accounts = Array.from({ length: 10 }, (_, i) => ({
- addressURN: `urn:proofzero:address:${i}`,
- title: `Account ${i}`,
- provider: `Provider ${i}`,
- address: `Address ${i}`,
-}))
-
-const Template = (args: any) => (
-
- {
- return null
- }}
- {...args}
- />
-
-)
-
-export const ConnectedAccountSelectExample = Template.bind({}) as any
diff --git a/packages/design-system/src/atoms/accounts/ConnectedAccountSelect.tsx b/packages/design-system/src/atoms/accounts/ConnectedAccountSelect.tsx
deleted file mode 100644
index ec42d39b98..0000000000
--- a/packages/design-system/src/atoms/accounts/ConnectedAccountSelect.tsx
+++ /dev/null
@@ -1,213 +0,0 @@
-import React, { Fragment, useEffect, useRef, useState } from 'react'
-import { Listbox, Transition } from '@headlessui/react'
-import { Text } from '@proofzero/design-system/src/atoms/text/Text'
-import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid'
-import { AddressURN } from '@proofzero/urns/address'
-import { ConnectNewAccountButton } from './ConnectNewButton'
-
-type ConnectedAccountSelectListItem = {
- addressURN: AddressURN
- title: string
- provider: string
- address: string
-}
-
-type ConnectedAccountSelectProps = {
- accounts: Array
- onConnectNew: () => void
- onSelect?: (selected: Array) => void
- onSelectAll?: () => void
-}
-
-export const ConnectedAccountSelect = ({
- accounts,
- onConnectNew,
- onSelect,
- onSelectAll,
-}: ConnectedAccountSelectProps) => {
- const isFirstRender = useRef(true)
-
- const [selectedAccounts, setSelectedAccounts] = useState<
- Array
- >([])
-
- const [allConnectedAccountsSelected, setAllConnectedAccountsSelected] =
- useState(false)
-
- const capitalizeFirstLetter = (string: string) => {
- return string.charAt(0).toUpperCase() + string.slice(1)
- }
-
- useEffect(() => {
- if (allConnectedAccountsSelected) {
- setSelectedAccounts([])
-
- if (onSelectAll) {
- onSelectAll()
- }
- }
- }, [allConnectedAccountsSelected])
-
- useEffect(() => {
- if (isFirstRender.current) {
- isFirstRender.current = false
- return
- }
-
- if (!allConnectedAccountsSelected && onSelect) {
- onSelect(selectedAccounts)
- }
- }, [selectedAccounts, allConnectedAccountsSelected])
-
- return (
-
- {({ open }) => (
-
-
- {(!selectedAccounts || selectedAccounts.length === 0) &&
- !allConnectedAccountsSelected && (
-
- No connected account(s)
-
- )}
-
- {selectedAccounts?.length === 1 && !allConnectedAccountsSelected && (
-
- {selectedAccounts[0].title}
-
- )}
- {selectedAccounts?.length > 1 && !allConnectedAccountsSelected && (
-
- {selectedAccounts.length} accounts selected
-
- )}
-
- {allConnectedAccountsSelected && (
-
- All connected accounts
-
- )}
-
- {open ? (
-
- ) : (
-
- )}
-
-
-
-
-
- setAllConnectedAccountsSelected(!allConnectedAccountsSelected)
- }
- >
-
-
-
-
-
- All connected accounts
-
-
-
-
-
-
-
- {accounts?.map((account) => (
-
-
- sa.addressURN)
- .includes(account.addressURN)
- }
- />
-
-
-
- {account.title}
-
-
- {capitalizeFirstLetter(account.provider)} -{' '}
- {account.address}
-
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
- )}
-
- )
-}
diff --git a/packages/design-system/src/atoms/breadcrumbs/Breadcrumbs.tsx b/packages/design-system/src/atoms/breadcrumbs/Breadcrumbs.tsx
new file mode 100644
index 0000000000..782bd7fc63
--- /dev/null
+++ b/packages/design-system/src/atoms/breadcrumbs/Breadcrumbs.tsx
@@ -0,0 +1,40 @@
+import React from 'react'
+import { Text } from '../text/Text'
+import { HiChevronRight } from 'react-icons/hi'
+
+type BreadcrumbsProps = {
+ trail: {
+ label: string
+ href?: string
+ }[]
+ LinkType: any & {
+ to: string
+ }
+}
+
+export default ({ trail, LinkType }: BreadcrumbsProps) => (
+ <>
+
+ {trail.map(({ label, href }, index) => (
+
+ {href && (
+
+
+ {label}
+
+
+ )}
+ {!href && (
+
+ {label}
+
+ )}
+
+ {index < trail.length - 1 && (
+
+ )}
+
+ ))}
+
+ >
+)
diff --git a/packages/design-system/src/atoms/buttons/Button.tsx b/packages/design-system/src/atoms/buttons/Button.tsx
index bd2baf920a..78090259ad 100644
--- a/packages/design-system/src/atoms/buttons/Button.tsx
+++ b/packages/design-system/src/atoms/buttons/Button.tsx
@@ -7,12 +7,17 @@ import {
sizeToSizesDict,
typeToColorsDict,
} from './common'
-import { ThemeContext } from '../../contexts/theme'
-export type ButtonProps = ButtonHTMLAttributes & {
+export type ButtonProps = React.DetailedHTMLProps<
+ React.ButtonHTMLAttributes,
+ HTMLButtonElement
+> & {
btnSize?: ButtonSize
btnType?: ButtonType
isSubmit?: boolean
+ onClick?: (e?: any) => unknown
+ disabled?: boolean
+ className?: string
}
export function Button({
@@ -21,6 +26,7 @@ export function Button({
btnType = 'primary',
isSubmit,
className,
+ onClick,
children,
...rest
}: ButtonProps) {
@@ -31,6 +37,7 @@ export function Button({
# Connect Button
-
+ < Canvas >
{
const config = createConfig(
getDefaultConfig({
appName: 'Rollup',
+ walletConnectProjectId: "foo",
})
)
return (
-
+
+
+
)
},
@@ -43,4 +46,4 @@ export const Template = (args) =>
>
{Template.bind({})}
-
+
diff --git a/packages/design-system/src/atoms/buttons/connect-button/ConnectButton.tsx b/packages/design-system/src/atoms/buttons/connect-button/ConnectButton.tsx
index 1517a77b8a..1cb6abf39c 100644
--- a/packages/design-system/src/atoms/buttons/connect-button/ConnectButton.tsx
+++ b/packages/design-system/src/atoms/buttons/connect-button/ConnectButton.tsx
@@ -1,29 +1,25 @@
-import React, { useContext } from 'react'
+import React, { useContext, useEffect } from 'react'
-import { useEffect } from 'react'
import type { ButtonProps } from '@proofzero/design-system/src/atoms/buttons/Button'
import walletsSvg from './wallets.png'
-import { Avatar } from 'connectkit'
import { Spinner } from '@proofzero/design-system/src/atoms/spinner/Spinner'
-import { useAccount, useDisconnect, useSignMessage } from 'wagmi'
-import { ConnectKitProvider, ConnectKitButton } from 'connectkit'
+import { Avatar, ConnectKitButton } from 'connectkit'
+
+import { useDisconnect, useSignMessage } from 'wagmi'
import { Text } from '@proofzero/design-system/src/atoms/text/Text'
import { Popover } from '@headlessui/react'
import { HiChevronDown, HiChevronUp } from 'react-icons/hi'
import { ThemeContext } from '../../../contexts/theme'
-export const signMessageTemplate = `Welcome to Rollup!
-
-Sign this message to accept the Rollup Terms of Service (https://rollup.id/tos), no password needed!
-
-This will not trigger a blockchain transaction or cost any gas fees.
-
-{{nonce}}
-`
+import {
+ AuthenticationScreenDefaults,
+ appendNonceTemplate,
+} from '../../../templates/authentication/Authentication'
export type ConnectButtonProps = {
+ signMessageTemplate?: string
connectCallback: (address: string) => void
signCallback: (
address: string,
@@ -46,6 +42,7 @@ export type ConnectButtonProps = {
} & ButtonProps
export function ConnectButton({
+ signMessageTemplate = AuthenticationScreenDefaults.defaultSignMessage,
connectCallback,
connectErrorCallback,
signCallback,
@@ -54,16 +51,9 @@ export function ConnectButton({
fullSize = true,
displayContinueWith = false,
}: ConnectButtonProps) {
- const { connector, isConnected, isReconnecting } = useAccount()
const { disconnect } = useDisconnect()
- const {
- isLoading: isSigning,
- error,
- status,
- signMessage,
- reset,
- } = useSignMessage({
- onSuccess(data, variables) {
+ const { isLoading: isSigning, signMessage } = useSignMessage({
+ onSuccess(data) {
console.debug('message signed')
if (!signData?.nonce || !signData?.state || !signData?.address) {
connectErrorCallback(new Error('No signature data present.'))
@@ -79,11 +69,10 @@ export function ConnectButton({
}
},
})
-
useEffect(() => {
if (!signData?.signature && signData?.nonce) {
console.debug('signing...')
- const nonceMessage = signMessageTemplate.replace(
+ const nonceMessage = appendNonceTemplate(signMessageTemplate).replace(
'{{nonce}}',
signData.nonce
)
@@ -92,7 +81,7 @@ export function ConnectButton({
} else {
console.debug('no sign data')
}
- }, [signData, isReconnecting, isConnected, connector, signMessage])
+ }, [signData])
const { theme, dark } = useContext(ThemeContext)
@@ -100,76 +89,102 @@ export function ConnectButton({
-
-
- {({
- isConnected,
- isConnecting,
- show,
- hide,
- address,
- truncatedAddress,
- ensName,
- }) => {
- if (isConnected) {
- hide!()
- }
- return (
-
-
address && connectCallback(address)
- : show
- }
- className={`flex-1 button hover:bg-gray-100 flex flex-row items-center space-x-3 px-[17px] rounded-l-md ${
- isConnected ? '' : 'rounded-r-md'
- } ${
- fullSize ? 'justify-start' : 'justify-center'
- } bg-white dark:bg-[#374151] dark:border-gray-600 hover:bg-gray-100 focus:bg-white focus:ring-inset focus:ring-2 focus:ring-skin-primary truncate`}
- >
- {(isSigning || isLoading) && isConnected ? (
-
- ) : (
-
- {!ensName &&
}
- {ensName &&
}
-
- )}
+
+ {({
+ isConnected,
+ isConnecting,
+ show,
+ hide,
+ address,
+ truncatedAddress,
+ ensName,
+ }) => {
+ if (isConnected) {
+ hide!()
+ }
+ return (
+
+
{
+ return address && connectCallback(address)
+ }
+ : show
+ }
+ className={`flex-1 button hover:bg-gray-100 flex flex-row
+ items-center space-x-3 px-[17px] rounded-l-md ${
+ isConnected ? '' : 'rounded-r-md'
+ } ${
+ fullSize ? 'justify-start' : 'justify-center'
+ } bg-white dark:bg-[#374151] dark:border-gray-600 dark:hover:bg-gray-600 focus:bg-white dark:focus:bg-gray-600 focus:ring-inset focus:ring-2 focus:ring-skin-primary truncate`}
+ >
+ {(isSigning || isLoading) && isConnected ? (
+
+ ) : (
+
+ {!ensName &&
}
+ {ensName &&
}
+
+ )}
- {fullSize && (
-
- {(isSigning || isLoading) && isConnected
- ? isSigning
- ? 'Signing... (please check wallet)'
- : 'Continuing...'
- : isConnected && address
- ? `${displayContinueWith ? `Continue with ` : ''}${
- ensName ?? truncatedAddress
- }`
- : !isConnecting
- ? `${displayContinueWith ? `Continue with ` : ''}Wallet`
- : 'Connecting'}
-
- )}
-
+ {fullSize && (
+
+ {(isSigning || isLoading) && isConnected
+ ? isSigning
+ ? 'Signing... (please check wallet)'
+ : 'Continuing...'
+ : isConnected && address
+ ? `${displayContinueWith ? `Continue with ` : ''}${
+ ensName ?? truncatedAddress
+ }`
+ : !isConnecting
+ ? `${displayContinueWith ? `Continue with ` : ''}Wallet`
+ : 'Connecting'}
+
+ )}
+
- {isConnected && (
-
- {({ open }) => (
+ {isConnected && (
+
+ {({ open }) => {
+ const disabled = isConnecting || isSigning || isLoading
+ return (
<>
-
- {!open && }
+
+ {!open && (
+
+ )}
{open && (
-
+
)}
-
+
{
@@ -179,21 +194,21 @@ export function ConnectButton({
{`Disconnect ${
ensName ?? truncatedAddress
}`}
>
- )}
-
- )}
-
- )
- }}
-
-
+ )
+ }}
+
+ )}
+
+ )
+ }}
+
)
}
diff --git a/packages/design-system/src/atoms/buttons/connect-oauth-button/index.tsx b/packages/design-system/src/atoms/buttons/connect-oauth-button/index.tsx
index a88109d9f7..e89c2f4d10 100644
--- a/packages/design-system/src/atoms/buttons/connect-oauth-button/index.tsx
+++ b/packages/design-system/src/atoms/buttons/connect-oauth-button/index.tsx
@@ -1,11 +1,11 @@
import React from 'react'
import discordIcon from '../../../assets/social_icons/discord.svg'
-import googleIcon from '../../../assets/social_icons/google.svg'
-import microsoftIcon from '../../../assets/social_icons/microsoft.svg'
import twitterIcon from '../../../assets/social_icons/twitter.svg'
import { Button } from '../Button'
import { Text } from '../../text/Text'
+import { WrappedSVG as GoogleSVG } from '../../providers/Google'
+import { WrappedSVG as MicrosoftSVG } from '../../providers/Microsoft'
import { WrappedSVG as AppleSVG } from '../../providers/Apple'
import { WrappedSVG as GitHubSVG } from '../../providers/Github'
@@ -25,8 +25,8 @@ const providerIconDict: { [key in OAuthProvider]: JSX.Element } = {
apple: AppleSVG,
discord: providerImgBuildHelper('discord', discordIcon),
github: GitHubSVG,
- google: providerImgBuildHelper('google', googleIcon),
- microsoft: providerImgBuildHelper('microsoft', microsoftIcon),
+ google: GoogleSVG,
+ microsoft: MicrosoftSVG,
twitter: providerImgBuildHelper('twitter', twitterIcon),
}
@@ -35,10 +35,12 @@ type ConnectOAuthButtonProps = {
fullSize?: boolean
displayContinueWith?: boolean
submit?: boolean
+ onClick?: () => unknown
}
const ConnectOAuthButton = ({
provider,
+ onClick,
fullSize = true,
displayContinueWith = false,
submit = false,
@@ -46,16 +48,16 @@ const ConnectOAuthButton = ({
return (
{providerIconDict[provider]}
diff --git a/packages/design-system/src/atoms/dropdown/ConnectedAccountsDropdown.stories.tsx b/packages/design-system/src/atoms/dropdown/ConnectedAccountsDropdown.stories.tsx
index e0bb0eed53..21dd93bb16 100644
--- a/packages/design-system/src/atoms/dropdown/ConnectedAccountsDropdown.stories.tsx
+++ b/packages/design-system/src/atoms/dropdown/ConnectedAccountsDropdown.stories.tsx
@@ -1,35 +1,39 @@
import React from 'react'
-import { Dropdown, SelectListItem } from './DropdownSelectList'
+import { Dropdown, DropdownSelectListItem } from './DropdownSelectList'
-import { OAuthAddressType, EmailAddressType, CryptoAddressType } from '@proofzero/types/address'
+import {
+ OAuthAccountType,
+ EmailAccountType,
+ CryptoAccountType,
+} from '@proofzero/types/account'
+import { adjustAccountTypeToDisplay } from '@proofzero/utils/getNormalisedConnectedAccounts'
export default {
title: 'Atoms/Dropdown/ConnectedAccounts',
component: Dropdown,
}
-const pickRandomType = (i: number) => {
- const types = [OAuthAddressType.Google,
- OAuthAddressType.Microsoft,
- EmailAddressType.Email,
- CryptoAddressType.ETH]
+const pickRandomAccountType = (i: number) => {
+ const types = [
+ OAuthAccountType.Google,
+ OAuthAccountType.Microsoft,
+ EmailAccountType.Email,
+ CryptoAccountType.ETH,
+ ]
- return types[i % types.length]
+ return types[i % types.length]
}
-const modifyType = (string: string) => {
- if (string === CryptoAddressType.Wallet) {
- return "SC Wallet"
- }
- return string.charAt(0).toUpperCase() + string.slice(1)
-}
-
-const accounts: SelectListItem[] = Array.from({ length: 10 }, (_, i) => ({
- value: `urn:proofzero:address:${i}`,
+const accounts: DropdownSelectListItem[] = Array.from(
+ { length: 10 },
+ (_, i) => ({
+ value: `urn:proofzero:account:${i}`,
title: `Account ${i}`,
- subtitle: `${modifyType(pickRandomType(i) as string)} - Address ${i}`
-
-}))
+ subtitle: `${adjustAccountTypeToDisplay(
+ pickRandomAccountType(i)
+ )} - Account ${i}`,
+ })
+)
const Template = () => (
@@ -46,4 +50,4 @@ const Template = () => (
)
-export const ConnectedAccountsSelectExample = Template.bind({}) as any
\ No newline at end of file
+export const ConnectedAccountsSelectExample = Template.bind({}) as any
diff --git a/packages/design-system/src/atoms/dropdown/DropdownSelectList.tsx b/packages/design-system/src/atoms/dropdown/DropdownSelectList.tsx
index 3c8f7db2b3..4025203c0a 100644
--- a/packages/design-system/src/atoms/dropdown/DropdownSelectList.tsx
+++ b/packages/design-system/src/atoms/dropdown/DropdownSelectList.tsx
@@ -1,321 +1,424 @@
-import React, { Fragment, useState } from "react"
-import { Listbox, Transition } from "@headlessui/react"
-import { Text } from "../text/Text"
+import React, { Fragment, useState } from 'react'
+import { Listbox, Transition } from '@headlessui/react'
+import { Text } from '../text/Text'
import {
- ChevronDownIcon,
- ChevronUpIcon,
- CheckIcon,
+ ChevronDownIcon,
+ ChevronUpIcon,
+ CheckIcon,
} from '@heroicons/react/20/solid'
-import { Button } from "../buttons/Button"
-import { TbCirclePlus } from "react-icons/tb"
-import { BadRequestError } from "@proofzero/errors"
+import { Button } from '../buttons/Button'
+import { TbCirclePlus } from 'react-icons/tb'
+import { BadRequestError } from '@proofzero/errors'
+import { AuthorizationControlSelection } from '@proofzero/types/application'
-
-export type SelectListItem = {
- title: string
- value?: string
- icon?: JSX.Element
- selected?: boolean
- subtitle?: string
+export type DropdownSelectListItem = {
+ title: string
+ value?: string
+ icon?: JSX.Element
+ selected?: boolean
+ subtitle?: string
}
-export const Dropdown = ({
- items,
- placeholder,
- ConnectButtonPhrase,
- ConnectButtonCallback,
- onSelect,
- multiple = false,
- onSelectAll,
- selectAllCheckboxTitle,
- selectAllCheckboxDescription,
-}: {
- items: SelectListItem[],
- placeholder: string,
- onSelect: (selected: Array
| SelectListItem) => void,
- multiple?: boolean
- ConnectButtonPhrase: string,
- ConnectButtonCallback: () => void,
- onSelectAll?: () => void,
- selectAllCheckboxTitle?: string
- selectAllCheckboxDescription?: string
-}) => {
-
- const defaultItems = items.filter(
- (item) => item.selected
- )
+export type DropdownListboxButtonType = {
+ selectedItem?: DropdownSelectListItem
+ selectedItems?: Array
+ allItemsSelected?: boolean
+ placeholder?: string
+ selectAllCheckboxTitle?: string
+ open: boolean
+}
- if (defaultItems.length > 1 && !multiple) {
- throw new BadRequestError(
- { message: "You can't have multiple items selected in a single select dropdown" }
- )
- }
+const DropdownListboxButtonDefault = ({
+ selectedItem,
+ selectedItems,
+ allItemsSelected,
+ placeholder,
+ selectAllCheckboxTitle,
+ open,
+}: DropdownListboxButtonType) => {
+ return (
+
+ {!selectedItem && !selectedItems?.length && !allItemsSelected && (
+
+ {placeholder}
+
+ )}
- /**
- * For single select
- */
- const [selectedItem, setSelectedItem] = useState
(() => {
- return multiple ? null : defaultItems[0]
- })
+ {selectedItem?.title?.length && (
+
+ {selectedItem.title}
+
+ )}
- /**
- * For multi select
- */
- const [selectedItems, setSelectedItems] = useState<
- Array
- >(() => { return multiple ? defaultItems : [] })
+ {selectedItems && selectedItems?.length > 1 && !allItemsSelected && (
+
+ {selectedItems?.length} items selected
+
+ )}
- const [allItemsSelected, setAllItemsSelected] =
- useState(false)
+ {selectedItems && selectedItems?.length === 1 && !allItemsSelected && (
+
+ {selectedItems?.[0].title} selected
+
+ )}
- return (
- {
- if (multiple) {
- setSelectedItems(input as SelectListItem[])
- if (!allItemsSelected) {
- onSelect(input)
- }
- } else {
- setSelectedItem(input as SelectListItem)
- onSelect(input)
- }
- }}
- multiple={multiple}
+ {allItemsSelected && (
+
+ {selectAllCheckboxTitle}
+
+ )}
- {({ open }) => (
-
-
- {!selectedItem && !selectedItems.length && !allItemsSelected && (
-
- {placeholder}
-
- )}
+ {open ? (
+
+ ) : (
+
+ )}
+
+ )
+}
- {selectedItem?.title?.length && (
-
- {selectedItem.title}
-
- )}
+export const Dropdown = ({
+ items,
+ defaultItems,
+ placeholder,
+ ConnectButtonPhrase,
+ ConnectButtonCallback,
+ onSelect,
+ multiple = false,
+ onSelectAll,
+ selectAllCheckboxTitle,
+ selectAllCheckboxDescription,
+ DropdownListboxButton = DropdownListboxButtonDefault,
+ disabled = false,
+}: {
+ items: Array
+ placeholder: string
+ onSelect: (
+ selected: Array | DropdownSelectListItem
+ ) => void
+ defaultItems?:
+ | Array
+ | Array
+ multiple?: boolean
+ ConnectButtonPhrase?: string
+ ConnectButtonCallback?: () => void
+ onSelectAll?: (val: Array) => void
+ selectAllCheckboxTitle?: string
+ selectAllCheckboxDescription?: string
+ DropdownListboxButton?: ({
+ selectedItem,
+ selectedItems,
+ allItemsSelected,
+ placeholder,
+ selectAllCheckboxTitle,
+ open,
+ }: DropdownListboxButtonType) => JSX.Element
+ disabled?: boolean
+}) => {
+ if (defaultItems?.length && defaultItems?.length > 1 && !multiple) {
+ throw new BadRequestError({
+ message:
+ "You can't have multiple items selected in a single select dropdown",
+ })
+ }
- {selectedItems.length > 1 && !allItemsSelected && (
-
- {selectedItems.length} items selected
-
- )}
+ /**
+ * For single select
+ */
+ const [selectedItem, setSelectedItem] = useState<
+ DropdownSelectListItem | undefined
+ >(() => {
+ if (!multiple) return defaultItems?.[0] as DropdownSelectListItem
+ })
- {selectedItems.length === 1 && !allItemsSelected && (
-
- {selectedItems[0].title} selected
-
- )}
+ /**
+ * For multi select
+ */
+ const [selectedItems, setSelectedItems] = useState<
+ Array
+ >(() => {
+ if (multiple) {
+ if (defaultItems?.length) {
+ return defaultItems[0] !== AuthorizationControlSelection.ALL
+ ? (defaultItems as Array)
+ : ([] as Array)
+ }
+ }
+ return [] as Array
+ })
- {allItemsSelected && (
-
- {selectAllCheckboxTitle}
-
- )}
+ const [allItemsSelected, setAllItemsSelected] = useState(() => {
+ if (multiple) {
+ return defaultItems?.[0] === AuthorizationControlSelection.ALL
+ }
+ })
- {open ? (
-
- ) : (
-
- )}
-
- {
+ if (multiple) {
+ setSelectedItems(input as DropdownSelectListItem[])
+ if (!allItemsSelected) {
+ onSelect(input)
+ }
+ } else {
+ setSelectedItem(input as DropdownSelectListItem)
+ onSelect(input)
+ }
+ }}
+ multiple={multiple}
+ disabled={disabled}
+ >
+ {({ open }) => (
+
+
+
+
+
+
+ {items?.length ? (
+ multiple ? (
+ /**
+ * Multi select
+ */
+ <>
+ {
+ // We check if the value falsy because by default it's false
+ // in this method we flip it to true
+ if (!allItemsSelected) {
+ setSelectedItems([])
+ if (onSelectAll) {
+ onSelectAll([AuthorizationControlSelection.ALL])
+ }
+ } else {
+ if (onSelectAll) {
+ onSelectAll([])
+ }
+ }
+ setAllItemsSelected(!allItemsSelected)
+ }}
>
-
- {items?.length
- ? multiple
- /**
- * Multi select
- */
- ? <>
- {
- // We check if the value falsy because by default it's false
- // in this method we flip it to true
- if (!allItemsSelected) {
- setSelectedItems([])
-
- if (onSelectAll) {
- onSelectAll()
- }
- }
- setAllItemsSelected(!allItemsSelected)
- }}
- >
-
-
-
-
-
- {selectAllCheckboxTitle}
-
-
- {selectAllCheckboxDescription}
-
-
-
+
+
+
+
+
+ {selectAllCheckboxTitle}
+
+
+ {selectAllCheckboxDescription}
+
+
+
-
+
-
- {items?.map((item) => {
+
+ {items?.map((item) => {
+ const checked =
+ !allItemsSelected &&
+ selectedItems
+ .map((si) => si.value)
+ .includes(item.value)
- const checked = !allItemsSelected &&
- selectedItems
- .map((si) => si.value)
- .includes(item.value)
-
- return
-
-
+
+
-
-
-
- {item.title}
-
- {item.subtitle
- ?
- {item.subtitle}
-
- : null}
-
-
- })}
-
- >
- /**
- * Single select
- */
- :
- {items.map((item) => (
-
{({ selected }) => (
-
+
+
+
+ {item.title}
+
+ {item.subtitle ? (
+
+ {item.subtitle}
+
+ ) : null}
+
+
+ )
+ })}
+
+ >
+ ) : (
+ /**
+ * Single select
+ */
+
+ {items.map((item) => {
+ const preselected = selectedItem?.value === item.value
+ return (
+
+ {({ selected }) => (
+
-
- {
- item.icon ?
- item.icon
- : null
- }
-
-
- {item.title}
-
- {
- item.subtitle
- ? (
- {item.subtitle}
- )
- : null
- }
-
-
- {selected ? (
-
-
-
- ) : null}
-
- )}
-
- ))}
-
: null}
- {items?.length
- ?
- : null
- }
-
-
-
- {ConnectButtonPhrase}
-
+ >
+
+ {item.icon ? item.icon : null}
+
+
+ {item.title}
+
+ {item.subtitle ? (
+
+ {item.subtitle}
+
+ ) : null}
+
+
+ {selected || preselected ? (
+
+
+
+ ) : null}
-
-
-
- )}
-
- )
+ )}
+
+ )
+ })}
+
+ )
+ ) : null}
+ {ConnectButtonPhrase && ConnectButtonCallback ? (
+ <>
+ {items?.length ? (
+
+ ) : null}
+
+
+
+
+ {ConnectButtonPhrase}
+
+
+
+ >
+ ) : null}
+
+
+
+ )}
+
+ )
}
diff --git a/packages/design-system/src/atoms/dropdown/EmailDropdown.stories.tsx b/packages/design-system/src/atoms/dropdown/EmailDropdown.stories.tsx
index acb1258565..8659c06666 100644
--- a/packages/design-system/src/atoms/dropdown/EmailDropdown.stories.tsx
+++ b/packages/design-system/src/atoms/dropdown/EmailDropdown.stories.tsx
@@ -1,81 +1,81 @@
import React from 'react'
-import { Dropdown, SelectListItem } from './DropdownSelectList'
+import { Dropdown, DropdownSelectListItem } from './DropdownSelectList'
-import { OAuthAddressType, EmailAddressType, CryptoAddressType } from '@proofzero/types/address'
-import { OptionType } from '@proofzero/utils/getNormalisedConnectedAccounts'
+import {
+ OAuthAccountType,
+ EmailAccountType,
+ CryptoAccountType,
+} from '@proofzero/types/account'
import { HiOutlineEnvelope } from 'react-icons/hi2'
-import googleIcon from '@proofzero/design-system/src/assets/social_icons/google.svg'
-import microsoftIcon from '@proofzero/design-system/src/assets/social_icons/microsoft.svg'
+import googleIcon from '@proofzero/design-system/src/atoms/providers/Google'
+import microsoftIcon from '@proofzero/design-system/src/atoms/providers/Microsoft'
import appleIcon from '@proofzero/design-system/src/atoms/providers/Apple'
export default {
- title: 'Atoms/Dropdown/Email',
- component: Dropdown,
+ title: 'Atoms/Dropdown/Email',
+ component: Dropdown,
}
-
-
const getIcon = (
- type?: OAuthAddressType | EmailAddressType | OptionType | CryptoAddressType
+ type?: OAuthAccountType | EmailAccountType | CryptoAccountType
): JSX.Element => {
- return type
- ? type === OAuthAddressType.Microsoft
- ?
- : type === OAuthAddressType.Apple
- ?
- : type === OAuthAddressType.Google
- ?
- : type === EmailAddressType.Email
- ?
- : null
- : null
+ return type ? (
+ type === OAuthAccountType.Microsoft ? (
+
+ ) : type === OAuthAccountType.Apple ? (
+
+ ) : type === OAuthAccountType.Google ? (
+
+ ) : type === EmailAccountType.Email ? (
+
+ ) : null
+ ) : null
}
-const listItems: SelectListItem[] = [
- {
- title: 'email@gmail.com',
- value: 'urn:rollupid:address/1',
- icon: getIcon(OAuthAddressType.Google),
-
- },
- {
- title: 'email@microsoft.com',
- value: 'urn:rollupid:address/2',
- icon: getIcon(OAuthAddressType.Microsoft),
-
- },
- {
- title: 'perez@apple.com',
- value: 'urn:rollupid:address/5',
- icon: getIcon(OAuthAddressType.Apple),
-
- },
- {
- title: 'email@yahoo.com',
- value: 'urn:rollupid:address/3',
- icon: getIcon(EmailAddressType.Email),
- selected: true,
-
- },
- {
- title: 'email@gmail.com',
- value: 'urn:rollupid:address/4',
- icon: getIcon(EmailAddressType.Email),
-
- },
+const listItems: Array = [
+ {
+ title: 'email@gmail.com',
+ value: 'urn:rollupid:account/1',
+ icon: getIcon(OAuthAccountType.Google),
+ },
+ {
+ title: 'email@microsoft.com',
+ value: 'urn:rollupid:account/2',
+ icon: getIcon(OAuthAccountType.Microsoft),
+ },
+ {
+ title: 'perez@apple.com',
+ value: 'urn:rollupid:account/5',
+ icon: getIcon(OAuthAccountType.Apple),
+ },
+ {
+ title: 'email@yahoo.com',
+ value: 'urn:rollupid:account/3',
+ icon: getIcon(EmailAccountType.Email),
+ selected: true,
+ },
+ {
+ title: 'email@gmail.com',
+ value: 'urn:rollupid:account/4',
+ icon: getIcon(EmailAccountType.Email),
+ },
]
const Template = () => (
-
- { console.log({ val }) }}
- placeholder='Select an Email Address'
- ConnectButtonPhrase="Connect New Email Address"
- ConnectButtonCallback={() => { console.log('Connect New Email Address') }}
- />
-
+
+ {
+ console.log({ val })
+ }}
+ placeholder="Select an Email Address"
+ ConnectButtonPhrase="Connect New Email Account"
+ ConnectButtonCallback={() => {
+ console.log('Connect New Email Account')
+ }}
+ />
+
)
-export const EmailSelectExample = Template.bind({}) as any
\ No newline at end of file
+export const EmailSelectExample = Template.bind({}) as any
diff --git a/packages/design-system/src/atoms/dropdown/SCWalletDropdown.stories.tsx b/packages/design-system/src/atoms/dropdown/SCWalletDropdown.stories.tsx
index c631aba7c2..dc6ffb4b61 100644
--- a/packages/design-system/src/atoms/dropdown/SCWalletDropdown.stories.tsx
+++ b/packages/design-system/src/atoms/dropdown/SCWalletDropdown.stories.tsx
@@ -1,25 +1,30 @@
import React from 'react'
-import { Dropdown, SelectListItem } from './DropdownSelectList'
+import { Dropdown, DropdownSelectListItem } from './DropdownSelectList'
-import { CryptoAddressType } from '@proofzero/types/address'
+import { CryptoAccountType } from '@proofzero/types/account'
export default {
- title: 'Atoms/Dropdown/SmartContractWallets',
- component: Dropdown,
+ title: 'Atoms/Dropdown/SmartContractWallets',
+ component: Dropdown,
}
const modifyType = (string: string) => {
- if (string === CryptoAddressType.Wallet) {
- return "SC Wallet"
- }
- return string.charAt(0).toUpperCase() + string.slice(1)
+ if (string === CryptoAccountType.Wallet) {
+ return 'SC Wallet'
+ }
+ return string.charAt(0).toUpperCase() + string.slice(1)
}
-const accounts: SelectListItem[] = Array.from({ length: 10 }, (_, i) => ({
- value: `urn:proofzero:address:${i}`,
+const accounts: DropdownSelectListItem[] = Array.from(
+ { length: 10 },
+ (_, i) => ({
+ value: `urn:rollupid:account:${i}`,
title: `Smart Contract Wallet ${i}`,
- subtitle: `${modifyType(CryptoAddressType.Wallet as string)} - SC Wallet: ${i}`
-}))
+ subtitle: `${modifyType(
+ CryptoAccountType.Wallet as string
+ )} - SC Wallet: ${i}`,
+ })
+)
const Template = () => (
@@ -36,4 +41,4 @@ const Template = () => (
)
-export const SCWalletSelectExample = Template.bind({}) as any
\ No newline at end of file
+export const SCWalletSelectExample = Template.bind({}) as any
diff --git a/packages/design-system/src/atoms/email/EmailSelect.stories.tsx b/packages/design-system/src/atoms/email/EmailSelect.stories.tsx
deleted file mode 100644
index 92a1828658..0000000000
--- a/packages/design-system/src/atoms/email/EmailSelect.stories.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from 'react'
-import { EmailSelect } from './EmailSelect'
-
-import { OAuthAddressType, EmailAddressType } from '@proofzero/types/address'
-import { EmailSelectListItem } from '@proofzero/utils/getNormalisedConnectedAccounts'
-
-export default {
- title: 'Atoms/Email/Select',
- component: EmailSelect,
-}
-
-const listItems: EmailSelectListItem[] = [
- {
- type: OAuthAddressType.Google,
- email: 'email@gmail.com',
- addressURN: 'urn:rollupid:address/1',
- },
- {
- type: OAuthAddressType.Microsoft,
- email: 'email@microsoft.com',
- addressURN: 'urn:rollupid:address/2',
- },
- {
- type: EmailAddressType.Email,
- email: 'email@yahoo.com',
- addressURN: 'urn:rollupid:address/3',
- },
- {
- type: EmailAddressType.Email,
- email: 'email@gmail.com',
- addressURN: 'urn:rollupid:address/4',
- },
-]
-
-const Template = (args: any) => (
-
-
-
-)
-
-export const EmailSelectExample = Template.bind({}) as any
-EmailSelectExample.args = {
- enableAddNew: true,
-}
diff --git a/packages/design-system/src/atoms/email/EmailSelect.tsx b/packages/design-system/src/atoms/email/EmailSelect.tsx
deleted file mode 100644
index f6e5dc5354..0000000000
--- a/packages/design-system/src/atoms/email/EmailSelect.tsx
+++ /dev/null
@@ -1,220 +0,0 @@
-import React, { useEffect } from 'react'
-import { Fragment, useState } from 'react'
-import { Listbox, Transition } from '@headlessui/react'
-import { ChevronDownIcon } from '@heroicons/react/20/solid'
-import { Text } from '@proofzero/design-system/src/atoms/text/Text'
-import { HiCheck } from 'react-icons/hi'
-import { TbCircleOff, TbCirclePlus } from 'react-icons/tb'
-import { HiOutlineEnvelope } from 'react-icons/hi2'
-
-import { EmailAddressType, OAuthAddressType } from '@proofzero/types/address'
-
-import googleIcon from '@proofzero/design-system/src/assets/social_icons/google.svg'
-import microsoftIcon from '@proofzero/design-system/src/assets/social_icons/microsoft.svg'
-import appleIcon from '@proofzero/design-system/src/atoms/providers/Apple'
-
-import { OptionType } from '@proofzero/utils/getNormalisedConnectedAccounts'
-
-import type { EmailSelectListItem } from '@proofzero/utils/getNormalisedConnectedAccounts'
-import type { AddressURN } from '@proofzero/urns/address'
-
-type EmailSelectProps = {
- items: EmailSelectListItem[]
- defaultAddress?: AddressURN
- enableAddNew?: boolean
- enableNone?: boolean
- onSelect?: (selected: EmailSelectListItem) => void
-}
-
-const getIconUrl = (
- type?: OAuthAddressType | EmailAddressType | OptionType
-) => {
- return type
- ? type === OAuthAddressType.Microsoft
- ? microsoftIcon
- : type === OAuthAddressType.Apple
- ? appleIcon
- : type === OAuthAddressType.Google
- ? googleIcon
- : null
- : null
-}
-
-export const EmailSelect = ({
- items,
- defaultAddress,
- enableAddNew = false,
- enableNone = false,
- onSelect,
-}: EmailSelectProps) => {
- const [selected, setSelected] = useState(() => {
- if (defaultAddress) {
- const defaultItem = items.find(
- (item) => item.addressURN === defaultAddress
- )
-
- return defaultItem
- }
- })
-
- useEffect(() => {
- if (selected && onSelect) {
- onSelect(selected)
- }
- }, [selected])
-
- const selectedIconURL = getIconUrl(selected?.type)
-
- return (
- {
- setSelected(selected)
- }}
- by="addressURN"
- >
- {({ open }) => (
-
-
- {!selected && (
-
- {enableAddNew ? 'Connect new email address' : 'None'}
-
- )}
- {selected && (
-
- {selected?.email}
-
- )}
- {open ? (
-
- ) : (
-
- )}
-
-
-
-
- {items.map((item, i) => {
- const iconURL = getIconUrl(item.type)
- return (
-
- {({ selected }) => (
-
- {iconURL ? (
-
- ) : (
-
- )}
-
-
- {item.email}
-
- {selected && (
-
- )}
-
- )}
-
- )
- })}
-
- {enableNone && (
-
- {({ selected }) => (
-
-
-
-
- None
-
- {selected && (
-
- )}
-
- )}
-
- )}
-
- {enableAddNew && (
-
- {({ selected }) => (
-
-
-
-
- Connect new email address
-
- {selected && (
-
- )}
-
- )}
-
- )}
-
-
-
- )}
-
- )
-}
diff --git a/packages/design-system/src/atoms/form/Input.tsx b/packages/design-system/src/atoms/form/Input.tsx
index 04af903fb9..3cd4263dd5 100644
--- a/packages/design-system/src/atoms/form/Input.tsx
+++ b/packages/design-system/src/atoms/form/Input.tsx
@@ -7,6 +7,7 @@ export type InputProps = InputHTMLAttributes & {
label?: string
error?: string
docsUrl?: string
+ skin?: boolean
}
export const Input = ({
@@ -16,18 +17,24 @@ export const Input = ({
className,
error,
docsUrl,
+ onSeeking,
+ skin,
...rest
}: InputProps) => {
const computedName = name ?? id
return (
-
+
{label && (
-
+
{label}
{rest.required ? '*' : ''}
@@ -45,16 +52,20 @@ export const Input = ({
)}
{
const computedName = name ?? id
- const [val, setVal] = useState(defaultValue)
const [computedError, setComputedError] = useState()
useEffect(() => {
- if (error || (val && charLimit && val.length > charLimit)) {
+ if (error || (value && charLimit && value.length > charLimit)) {
setComputedError(true)
} else {
setComputedError(false)
}
- }, [error, val])
+ }, [error, value])
return (
@@ -51,7 +50,7 @@ const InputTextarea = ({
{charLimit && (
- {val?.length || 0}/{charLimit}
+ {value?.length || 0}/{charLimit}
)}
@@ -66,12 +65,10 @@ const InputTextarea = ({
name={computedName}
id={id}
onChange={(e) => {
- setVal(e.target.value)
-
if (onChange) onChange(e.target.value)
}}
rows={rows}
- defaultValue={defaultValue}
+ value={value}
disabled={disabled ?? false}
className={`${
computedError ? 'border-red-500' : 'border-gray-300'
@@ -85,7 +82,7 @@ const InputTextarea = ({
: 'disabled:border-gray-200'
} ${computedError ? 'disabled:bg-red-50' : 'disabled:bg-gray-50'} ${
computedError ? 'placeholder-red-400' : 'placeholder-gray-400'
- } w-full rounded-md`}
+ } w-full rounded-md text-sm`}
placeholder={placeholder}
required={required}
/>
diff --git a/packages/design-system/src/atoms/form/MultiSelect.stories.tsx b/packages/design-system/src/atoms/form/MultiSelect.stories.tsx
index 612292b162..4d6c943b5d 100644
--- a/packages/design-system/src/atoms/form/MultiSelect.stories.tsx
+++ b/packages/design-system/src/atoms/form/MultiSelect.stories.tsx
@@ -24,6 +24,10 @@ export default {
},
}
-const Template = (args) =>
+const Template = (args) => (
+
+
+
+)
export const Default = Template.bind({})
diff --git a/packages/design-system/src/atoms/form/MultiSelect.tsx b/packages/design-system/src/atoms/form/MultiSelect.tsx
index e961da50f3..5212052be3 100644
--- a/packages/design-system/src/atoms/form/MultiSelect.tsx
+++ b/packages/design-system/src/atoms/form/MultiSelect.tsx
@@ -14,7 +14,7 @@
//https://headlessui.com/react/combobox
-import React, { useState } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import { ChevronUpDownIcon } from '@heroicons/react/20/solid'
import { IoCloseOutline } from 'react-icons/io5'
import { Combobox } from '@headlessui/react'
@@ -26,7 +26,7 @@ import { ExperimentalFeaturePill } from '../pills/ExperimentalFeaturePill'
type SelectItem = {
id: string
val: string
- desc: string
+ desc?: string
disabled?: boolean
section?: string
experimental?: boolean
@@ -35,23 +35,119 @@ export type MultiSelectProps = {
label: string
fieldName: string
items: SelectItem[]
- selectedItems?: SelectItem[]
+ width: number
+ preselectedItems?: SelectItem[]
disabled?: boolean
onChange?: () => void
learnMore?: string
}
+const ComboboxItem = ({ item, selected }) => {
+ return (
+
+
+
+
+
+ {item.val}
+
+
+ -
+
+
+ {item.id}
+
+ {item.disabled && (
+
+ Enable in "{item.section}" section
+
+ )}
+ {!item.disabled && item.experimental && (
+
+ )}
+
+
+ {item.desc}
+
+
+
+ )
+}
+
+const computeItemsToDisplay = (selectedItems, width) => {
+ let artificialWidth = 40
+ let itemsLeft = 0
+ const items = []
+ selectedItems
+ .sort((a, b) => a.val.length - b.val.length)
+ .forEach((item) => {
+ if (
+ // additional item + items left to display
+ artificialWidth + item.val.length * 10.5 + 10 + 42 <
+ width
+ ) {
+ artificialWidth += item.val.length * 10.5 + 42
+ items.push(item)
+ } else {
+ itemsLeft += 1
+ }
+ })
+
+ if (itemsLeft > 0) {
+ items.push({
+ id: 'items_left',
+ val: `${itemsLeft}`,
+ desc: '',
+ })
+ }
+ return items
+}
+
export function MultiSelect({
label,
fieldName,
items,
disabled = false,
- selectedItems = [],
+ preselectedItems = [],
onChange,
+ width,
learnMore,
}: MultiSelectProps) {
const [query, setQuery] = useState('')
- const [selectedValues, setSelectedValues] = useState(selectedItems)
+ const ref = useRef(null)
+ const [selectedItems, setSelectedItems] = useState(preselectedItems)
+ const [itemsToDisplay, setItemsToDisplay] = useState(() => {
+ return computeItemsToDisplay(preselectedItems, width)
+ })
+
+ useEffect(() => {
+ const items = computeItemsToDisplay(selectedItems, width)
+ setItemsToDisplay(items)
+ }, [selectedItems, width])
const filterItems =
query === ''
@@ -63,14 +159,24 @@ export function MultiSelect({
)
})
+ const [selectedFilteredItems, notSelectedFilteredItems] = filterItems.reduce(
+ ([selected, notSelected], item) => {
+ selectedItems.find((val) => val.id === item.id)
+ ? selected.push(item)
+ : notSelected.push(item)
+ return [selected, notSelected]
+ },
+ [[], []]
+ )
+
return (
{
- setSelectedValues(e)
+ setSelectedItems(e)
!!onChange && onChange()
}}
name={fieldName}
@@ -93,40 +199,69 @@ export function MultiSelect({
{
setQuery('')
}}
>
- {selectedValues.length > 0 ? (
- selectedValues.map((item, key) => (
- {
- event.stopPropagation()
- event.preventDefault()
- }}
- >
- {item.val}
- {
- setSelectedValues(
- selectedValues.filter((v) => v.id !== item.id)
- )
- if (onChange) onChange()
- }}
- />
-
- ))
+ {selectedItems.length > 0 ? (
+ itemsToDisplay.map((item, key) => {
+ return (
+
+ {item.id === 'items_left' ? (
+
+ {+item.val > 1 ? (
+
+ +
+
+ ) : null}
+
1 ? '-m-6' : 'm-1'
+ } rounded-md border border-indigo-300
+ z-998 flex flex-row items-center
+ justify-start gap-x-1`}
+ >
+ +{item.val}
+
+
+ ) : (
+
{
+ event.stopPropagation()
+ event.preventDefault()
+ }}
+ >
+ {item.val}
+
+ {
+ setSelectedItems(
+ selectedItems.filter((v) => v.id !== item.id)
+ )
+ if (onChange) onChange()
+ }}
+ />
+
+ )}
+
+ )
+ })
) : (
no select
)}
@@ -146,8 +281,7 @@ export function MultiSelect({
{
setQuery(event.target.value)
@@ -172,86 +306,59 @@ export function MultiSelect({
) : null}
- {filterItems.length ? (
- filterItems.map((item) => (
-
- classNames(
- 'relative cursor-default select-none m-2',
- active ? 'bg-gray-50' : ''
- )
- }
- disabled={item.disabled}
- >
- {({ selected }) => (
-
-
-
-
-
- {item.val}
-
-
- -
-
-
- {item.id}
-
- {item.disabled && (
-
- Enable in "{item.section}" section
-
- )}
- {!item.disabled && item.experimental && (
-
- )}
-
-
- {item.desc}
-
-
-
- )}
-
- ))
- ) : (
- No items found
- )}
-
-
-
+ {
+ filterItems ? (
+ <>
+ {selectedFilteredItems.length
+ ? selectedFilteredItems.map((item) => (
+
+ classNames(
+ 'relative cursor-default select-none m-2',
+ active ? 'bg-gray-50' : ''
+ )
+ }
+ disabled={item.disabled}
+ >
+ {({ selected }) => (
+
+ )}
+
+ ))
+ : null}
+
+ {notSelectedFilteredItems.length
+ ? notSelectedFilteredItems.map((item) => (
+
+ classNames(
+ 'relative cursor-default select-none m-2',
+ active ? 'bg-gray-50' : ''
+ )
+ }
+ disabled={item.disabled}
+ >
+ {({ selected }) => (
+
+ )}
+
+ ))
+ : null}
+ >
+ ) : (
+
No items found
+ )
+ }
+
+
+
>
- )}
-
+ )
+ }
+
)
}
diff --git a/packages/design-system/src/atoms/info/Info.tsx b/packages/design-system/src/atoms/info/Info.tsx
index 5a7ad7559f..cfb1f9740f 100644
--- a/packages/design-system/src/atoms/info/Info.tsx
+++ b/packages/design-system/src/atoms/info/Info.tsx
@@ -12,12 +12,15 @@ export default function Info({
placement?: 'top' | 'bottom' | 'left' | 'right'
}) {
return (
-
-
-
+
+
+
+
+
)
}
diff --git a/packages/design-system/src/atoms/info/TosAndPPol.tsx b/packages/design-system/src/atoms/info/TosAndPPol.tsx
index 87a9145269..1b2d7fd835 100644
--- a/packages/design-system/src/atoms/info/TosAndPPol.tsx
+++ b/packages/design-system/src/atoms/info/TosAndPPol.tsx
@@ -2,49 +2,46 @@ import React from 'react'
import iIcon from './i.svg'
import { Text } from '../text/Text'
-
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from '../popover/Popover'
-
+import { Popover, PopoverContent, PopoverTrigger } from '../popover/Popover'
export const TosAndPPol = () => {
- return (
-
-
-
-
-
- What is Rollup ID?
-
-
- Rollup ID is a user management platform designed to
- prioritize privacy, security, and ease of use .
-
-
-
- )
+ return (
+
+
+
+
+
+
+ What is Rollup ID?
+
+
+ Rollup ID is a user management platform designed to
+
+ {' '}
+ prioritize privacy, security, and ease of use
+
+ .
+
+
+
+
+ )
}
diff --git a/packages/design-system/src/atoms/lists/List.tsx b/packages/design-system/src/atoms/lists/List.tsx
index c0a0aa42b8..e66ae467b7 100644
--- a/packages/design-system/src/atoms/lists/List.tsx
+++ b/packages/design-system/src/atoms/lists/List.tsx
@@ -5,21 +5,35 @@ type ListItem = {
key: Key
val: any
disabled?: boolean
+ onClick?: (id: Key) => void
+ colorCode?: string
}
export type ListProps = {
items: T[]
itemRenderer: (item: T) => ReactNode
+ onItemClick?: (item: T) => void
}
export const List = ({
items,
itemRenderer,
+ onItemClick,
}: ListProps) => {
return (
{items.map((item) => (
-
+ onItemClick(items.find((i) => i.key === key))
+ : null
+ }
+ >
{itemRenderer(item)}
))}
diff --git a/packages/design-system/src/atoms/lists/ListItem.tsx b/packages/design-system/src/atoms/lists/ListItem.tsx
index 77ef50c975..0a248db1c1 100644
--- a/packages/design-system/src/atoms/lists/ListItem.tsx
+++ b/packages/design-system/src/atoms/lists/ListItem.tsx
@@ -1,26 +1,44 @@
+import classNames from 'classnames'
import React, { Key, ReactNode } from 'react'
export type ListItemProps = {
children: ReactNode
id: Key
+ onClick?: (id: Key) => void
disabled?: boolean
+ colorCode?: string
}
-export const ListItem = ({ id, children, disabled = false }: ListItemProps) => {
+export const ListItem = ({
+ id,
+ children,
+ disabled = false,
+ onClick,
+ colorCode,
+}: ListItemProps) => {
return (
-
-
-
{children}
+
onClick && onClick(id)}
+ >
+
{children}
+
)
}
diff --git a/packages/design-system/src/atoms/panels/Panel.tsx b/packages/design-system/src/atoms/panels/Panel.tsx
index 551969ea26..de5a5159d9 100644
--- a/packages/design-system/src/atoms/panels/Panel.tsx
+++ b/packages/design-system/src/atoms/panels/Panel.tsx
@@ -15,7 +15,7 @@ export const Panel = ({
children,
experimental,
}: PanelProps) => (
-
+
diff --git a/packages/design-system/src/atoms/pills/DangerPill.tsx b/packages/design-system/src/atoms/pills/DangerPill.tsx
new file mode 100644
index 0000000000..b43b33456f
--- /dev/null
+++ b/packages/design-system/src/atoms/pills/DangerPill.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { Text } from '../text/Text'
+import { Pill } from './Pill'
+import { HiOutlineExclamationTriangle } from 'react-icons/hi2'
+
+export type DangerPillProps = {
+ text: string
+}
+
+export const DangerPill = ({ text }: DangerPillProps) => (
+
+
+
+ {text}
+
+
+)
diff --git a/packages/design-system/src/atoms/pills/FeaturePill.tsx b/packages/design-system/src/atoms/pills/FeaturePill.tsx
index ebd5c991c9..e86810ca5b 100644
--- a/packages/design-system/src/atoms/pills/FeaturePill.tsx
+++ b/packages/design-system/src/atoms/pills/FeaturePill.tsx
@@ -1,13 +1,16 @@
import React from 'react'
import { Text } from '../text/Text'
import { Pill } from './Pill'
+import { IconType } from 'react-icons'
export type FeaturePillProps = {
+ Icon?: IconType
text: string
}
-export const FeaturePill = ({ text }: FeaturePillProps) => (
-
+export const FeaturePill = ({ Icon, text }: FeaturePillProps) => (
+
+ {Icon && }
{text}
diff --git a/packages/design-system/src/atoms/pills/Pill.tsx b/packages/design-system/src/atoms/pills/Pill.tsx
index 79795f1666..dff3c7a651 100644
--- a/packages/design-system/src/atoms/pills/Pill.tsx
+++ b/packages/design-system/src/atoms/pills/Pill.tsx
@@ -1,12 +1,14 @@
-import React, { ReactNode } from 'react'
+import React from 'react'
export type PillProps = React.DetailedHTMLProps<
React.HTMLAttributes,
HTMLDivElement
->
+> & {
+ className?: string
+}
export const Pill = ({ className, children }: PillProps) => (
-
+
{children}
)
diff --git a/packages/design-system/src/atoms/pills/StatusPill.tsx b/packages/design-system/src/atoms/pills/StatusPill.tsx
new file mode 100644
index 0000000000..8d64d7f8c2
--- /dev/null
+++ b/packages/design-system/src/atoms/pills/StatusPill.tsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import classNames from 'classnames'
+import { Pill } from './Pill'
+import { Text } from '../text/Text'
+
+export type StatusPillProps = {
+ text?: string
+ status: 'success' | 'warning' | 'danger'
+}
+
+export const StatusPill = ({ text, status }: StatusPillProps) => (
+
+
+ {text && (
+
+ {text}
+
+ )}
+
+)
diff --git a/packages/design-system/src/atoms/popover/Popover.tsx b/packages/design-system/src/atoms/popover/Popover.tsx
index adcf50687d..3e917a773f 100644
--- a/packages/design-system/src/atoms/popover/Popover.tsx
+++ b/packages/design-system/src/atoms/popover/Popover.tsx
@@ -1,42 +1,51 @@
-"use client"
+'use client'
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
+import * as React from 'react'
+import * as PopoverPrimitive from '@radix-ui/react-popover'
-import classNames from "classnames"
+import classNames from 'classnames'
+import { useContext } from 'react'
+import { ThemeContext } from '../../contexts/theme'
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverContent = React.forwardRef<
- React.ElementRef
,
- React.ComponentPropsWithoutRef
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => {
+ const { dark } = useContext(ThemeContext)
+
+ return (
+
-))
+ )
+})
PopoverContent.displayName = PopoverPrimitive.Content.displayName
const PopoverExample = () => {
- return
- Open
- Place content for the popover here.
+ return (
+
+ Open
+ Place content for the popover here.
+ )
}
-
-export { Popover, PopoverTrigger, PopoverContent, PopoverExample }
\ No newline at end of file
+export { Popover, PopoverTrigger, PopoverContent, PopoverExample }
diff --git a/packages/design-system/src/assets/social_icons/google.svg b/packages/design-system/src/atoms/providers/Google.tsx
similarity index 66%
rename from packages/design-system/src/assets/social_icons/google.svg
rename to packages/design-system/src/atoms/providers/Google.tsx
index f58bbb8431..7ce874b859 100644
--- a/packages/design-system/src/assets/social_icons/google.svg
+++ b/packages/design-system/src/atoms/providers/Google.tsx
@@ -1,6 +1,22 @@
-
+import React from 'react'
+
+const svgString = `
-
+ `
+
+export const WrappedSVG = (
+
+)
+
+export default `data:image/svg+xml;base64,${btoa(svgString)}`
+
+
+
diff --git a/packages/design-system/src/atoms/providers/Microsoft.tsx b/packages/design-system/src/atoms/providers/Microsoft.tsx
new file mode 100644
index 0000000000..9557051a90
--- /dev/null
+++ b/packages/design-system/src/atoms/providers/Microsoft.tsx
@@ -0,0 +1,22 @@
+import React from 'react'
+
+const svgString = `
+
+
+
+
+ `
+
+export const WrappedSVG = (
+
+)
+
+export default `data:image/svg+xml;base64,${btoa(svgString)}`
+
+
+
diff --git a/packages/design-system/src/atoms/providers/Webauthn.tsx b/packages/design-system/src/atoms/providers/Webauthn.tsx
new file mode 100644
index 0000000000..62e748cc54
--- /dev/null
+++ b/packages/design-system/src/atoms/providers/Webauthn.tsx
@@ -0,0 +1,20 @@
+import React from 'react'
+
+const svgString = `
+
+
+
+
+
+`
+
+export const WrappedSVG = (
+
+)
+
+export default `data:image/svg+xml;base64,${btoa(svgString)}`
diff --git a/packages/design-system/src/atoms/smart_contract_wallets/SmartContractWalletSelect.stories.tsx b/packages/design-system/src/atoms/smart_contract_wallets/SmartContractWalletSelect.stories.tsx
deleted file mode 100644
index 164cbf3355..0000000000
--- a/packages/design-system/src/atoms/smart_contract_wallets/SmartContractWalletSelect.stories.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react'
-import { SmartContractWalletSelect } from './SmartContractWalletSelect'
-import { CryptoAddressType } from '@proofzero/types/address'
-
-export default {
- title: 'Atoms/SmartContractWalletSelect',
- component: SmartContractWalletSelect,
-}
-
-const wallets = Array.from({ length: 10 }, (_, i) => ({
- addressURN: `urn:proofzero:address:${i}`,
- title: `Smart Contract Wallet ${i}`,
- type: CryptoAddressType.Wallet,
-}))
-
-const Template = (args: any) => (
-
-
-
-)
-
-export const SmartContractWalletSelectExample = Template.bind({}) as any
diff --git a/packages/design-system/src/atoms/smart_contract_wallets/SmartContractWalletSelect.tsx b/packages/design-system/src/atoms/smart_contract_wallets/SmartContractWalletSelect.tsx
deleted file mode 100644
index 5b5b574c23..0000000000
--- a/packages/design-system/src/atoms/smart_contract_wallets/SmartContractWalletSelect.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import React, { Fragment, useEffect, useState } from 'react'
-import { Listbox, Transition } from '@headlessui/react'
-import { Text } from '@proofzero/design-system/src/atoms/text/Text'
-import {
- ChevronDownIcon,
- ChevronUpIcon,
- CheckIcon,
-} from '@heroicons/react/20/solid'
-import { TbCirclePlus } from 'react-icons/tb'
-import { HiCheck } from 'react-icons/hi'
-import { OptionType } from '@proofzero/utils/getNormalisedConnectedAccounts'
-import type { SCWalletSelectListItem } from '@proofzero/utils/getNormalisedConnectedAccounts'
-
-export type SmartContractWalletSelectProps = {
- wallets?: Array
- onSelect?: (selected: SCWalletSelectListItem) => void
-}
-
-export const SmartContractWalletSelect = ({
- wallets,
- onSelect,
-}: SmartContractWalletSelectProps) => {
- const [selectedWallet, setSelectedWallet] = useState()
-
- useEffect(() => {
- if (onSelect) {
- onSelect(selectedWallet)
- }
- }, [selectedWallet])
-
- return (
-
- {({ open }) => (
-
-
- {(!selectedWallet || selectedWallet.title.length === 0) && (
-
- Select a Smart Contract Wallet
-
- )}
-
- {selectedWallet?.title?.length && (
-
- {selectedWallet.title}
-
- )}
-
- {open ? (
-
- ) : (
-
- )}
-
-
-
-
- {wallets?.map((wallet) => (
-
- {({ selected }) => (
-
-
- {wallet.title}
-
- {selected ? (
-
-
-
- ) : null}
-
- )}
-
- ))}
-
- {({ selected }) => {
- return (
-
-
-
-
- New Smart Contract Wallet
-
- {selected &&
- selectedWallet?.type === OptionType.AddNew && (
-
- )}
-
- )
- }}
-
-
-
-
- )}
-
- )
-}
diff --git a/packages/design-system/src/atoms/spinner/Spinner.tsx b/packages/design-system/src/atoms/spinner/Spinner.tsx
index 2e8c22ec72..11e90e0cda 100644
--- a/packages/design-system/src/atoms/spinner/Spinner.tsx
+++ b/packages/design-system/src/atoms/spinner/Spinner.tsx
@@ -15,6 +15,7 @@ export const Spinner = ({ color = '#000000', size = 32 }: SpinnerProps) => (
border: `${size / 4}px rgba(0, 0, 0, 0.25) solid`,
borderTop: `${size / 4}px ${color} solid`,
borderRadius: '50%',
+ margin: 'auto',
}}
/>
)
diff --git a/packages/design-system/src/atoms/toast/Toast.tsx b/packages/design-system/src/atoms/toast/Toast.tsx
index 1dc4b8bfab..68fcde9925 100644
--- a/packages/design-system/src/atoms/toast/Toast.tsx
+++ b/packages/design-system/src/atoms/toast/Toast.tsx
@@ -27,7 +27,7 @@ export const Toast = ({
}`}
>
{PreMessage && {PreMessage}
}
- {message} {' '}
+ {message} {' '}
{PostMessage && {PostMessage}
}
{remove && (
void }) => (
+}: ToastInfoProps & { remove?: () => void }) => (
void }) => (
+}: ToastWarningProps & { remove?: () => void }) => (
void
}
+const ToastIcon = {
+ urgent: ,
+ deferred: ,
+}
+
+const ToastStyle = {
+ urgent: 'bg-yellow-200 text-black w-full px-6',
+ deferred: 'bg-indigo-50 text-indigo-700 w-full',
+ warning: 'bg-orange-50 text-orange-600 w-full',
+}
+
export const ToastWithLink = ({
message,
remove,
linkHref,
linkText,
+ type = 'deferred',
}: ToastWithLinkProps) => (
}
+ PreMessage={ToastIcon[type]}
PostMessage={
@@ -28,6 +41,6 @@ export const ToastWithLink = ({
}
- className="bg-indigo-50 text-indigo-700 w-full"
+ className={ToastStyle[type]}
/>
)
diff --git a/packages/design-system/src/helpers/get-provider-icons.ts b/packages/design-system/src/helpers/get-provider-icons.ts
index 6ad3498414..0a261da676 100644
--- a/packages/design-system/src/helpers/get-provider-icons.ts
+++ b/packages/design-system/src/helpers/get-provider-icons.ts
@@ -4,10 +4,11 @@ import email from '../assets/social_icons/email.svg'
import ethereum from '../assets/social_icons/ethereum.svg'
import facebook from '../assets/social_icons/facebook.svg'
import github from '../atoms/providers/Github'
-import google from '../assets/social_icons/google.svg'
-import microsoft from '../assets/social_icons/microsoft.svg'
+import google from '../atoms/providers/Google'
+import microsoft from '../atoms/providers/Microsoft'
import twitter from '../assets/social_icons/twitter.svg'
import wallets from '../assets/social_icons/wallets.png'
+import webauthn from '../atoms/providers/Webauthn'
export default (provider: string) => {
switch (provider) {
@@ -17,6 +18,8 @@ export default (provider: string) => {
return discord
case 'email':
return email
+ case 'webauthn':
+ return webauthn
case 'ethereum':
return ethereum
case 'facebook':
@@ -32,6 +35,6 @@ export default (provider: string) => {
case 'wallet':
return wallets
default:
- return null
+ return undefined
}
}
diff --git a/packages/design-system/src/helpers/get-rgb-color.ts b/packages/design-system/src/helpers/get-rgb-color.ts
new file mode 100644
index 0000000000..a1d319f4f4
--- /dev/null
+++ b/packages/design-system/src/helpers/get-rgb-color.ts
@@ -0,0 +1,9 @@
+export default (hex: string, type: string) => {
+ let color = hex.replace(/#/g, '')
+ // rgb values
+ var r = parseInt(color.substr(0, 2), 16)
+ var g = parseInt(color.substr(2, 2), 16)
+ var b = parseInt(color.substr(4, 2), 16)
+
+ return `--color-${type}: ${r}, ${g}, ${b};`
+}
diff --git a/packages/design-system/src/helpers/get-text-color.ts b/packages/design-system/src/helpers/get-text-color.ts
new file mode 100644
index 0000000000..b240a5f637
--- /dev/null
+++ b/packages/design-system/src/helpers/get-text-color.ts
@@ -0,0 +1,32 @@
+function getRGB(c) {
+ return parseInt(c, 16) || c
+}
+
+function getsRGB(c) {
+ return getRGB(c) / 255 <= 0.03928
+ ? getRGB(c) / 255 / 12.92
+ : Math.pow((getRGB(c) / 255 + 0.055) / 1.055, 2.4)
+}
+
+function getLuminance(hexColor) {
+ return (
+ 0.2126 * getsRGB(hexColor.substr(1, 2)) +
+ 0.7152 * getsRGB(hexColor.substr(3, 2)) +
+ 0.0722 * getsRGB(hexColor.substr(-2))
+ )
+}
+
+function getContrast(f, b) {
+ const L1 = getLuminance(f)
+ const L2 = getLuminance(b)
+ return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05)
+}
+
+export default function getTextColor(bgColor: string) {
+ if (bgColor === '#6366F1') return '#ffffff'
+
+ const whiteContrast = getContrast(bgColor, '#ffffff')
+ const blackContrast = getContrast(bgColor, '#000000')
+
+ return whiteContrast > blackContrast ? '#ffffff' : '#000000'
+}
diff --git a/packages/design-system/src/helpers/index.ts b/packages/design-system/src/helpers/index.ts
new file mode 100644
index 0000000000..09234850c7
--- /dev/null
+++ b/packages/design-system/src/helpers/index.ts
@@ -0,0 +1,5 @@
+import getProviderIcons from './get-provider-icons'
+import getRGBColor from './get-rgb-color'
+import getTextColor from './get-text-color'
+
+export { getProviderIcons, getRGBColor, getTextColor }
diff --git a/packages/design-system/src/hooks/feature-flags.tsx b/packages/design-system/src/hooks/feature-flags.tsx
new file mode 100644
index 0000000000..ceea520b0a
--- /dev/null
+++ b/packages/design-system/src/hooks/feature-flags.tsx
@@ -0,0 +1,41 @@
+import { useEffect } from 'react'
+
+// Automatically called once on component mount due to useEffect with an empty dependency array.
+export const registerFeatureFlag = () => {
+ useEffect(() => {
+ const url = new URL(window.location.href)
+ const featureFlag = url.searchParams.get('feature_flag')
+
+ if (featureFlag) {
+ // Retrieve existing feature flags from localStorage or initialize to null
+ let featureFlags = localStorage?.getItem('feature_flags') ?? null
+
+ // Update the feature flags object and store it back in localStorage
+ if (!featureFlags) {
+ featureFlags = JSON.stringify({
+ [featureFlag]: true,
+ })
+ } else {
+ featureFlags = JSON.parse(featureFlags)
+ featureFlags[featureFlag] = true
+ featureFlags = JSON.stringify(featureFlags)
+ }
+
+ localStorage?.setItem('feature_flags', featureFlags)
+
+ // Clean up the URL by removing the 'feature_flag' query parameter
+ url.searchParams.delete('feature_flag')
+ history.replaceState(null, '', url.toString())
+ }
+ }, [])
+}
+
+// Retrieves feature flags from localStorage, assuming the client-side is "hydrated"
+export const useFeatureFlags = (hydrated = false) => {
+ // Return an empty object if local storage is not initialized
+ if (!hydrated) return {}
+
+ // Retrieve and parse the feature flags from localStorage, if they exist
+ const featureFlags = localStorage?.getItem('feature_flags')
+ return featureFlags ? JSON.parse(featureFlags) : {}
+}
diff --git a/packages/design-system/src/hooks/useConnectResult.tsx b/packages/design-system/src/hooks/useConnectResult.tsx
index 21321a692e..2318f5536d 100644
--- a/packages/design-system/src/hooks/useConnectResult.tsx
+++ b/packages/design-system/src/hooks/useConnectResult.tsx
@@ -2,7 +2,12 @@ import { ToastType, toast } from '../atoms/toast'
import { useEffect } from 'react'
export default (
- handledMessageTypes: string[] = ['SUCCESS', 'ALREADY_CONNECTED', 'CANCEL']
+ handledMessageTypes: string[] = [
+ 'SUCCESS',
+ 'ACCOUNT_CONNECT_ERROR',
+ 'ALREADY_CONNECTED_ERROR',
+ 'CANCEL'
+ ]
) => {
useEffect(() => {
const url = new URL(window.location.href)
@@ -18,10 +23,21 @@ export default (
{ duration: 2000 }
)
break
- case 'ALREADY_CONNECTED':
+ case 'ACCOUNT_CONNECT_ERROR':
toast(
ToastType.Error,
- { message: 'Account already connected' },
+ {
+ message: 'Could not connect this account to your identity.\
+ It may be connected to another identity.' },
+ { duration: 2000 }
+ )
+ break
+ case 'ALREADY_CONNECTED_ERROR':
+ toast(
+ ToastType.Error,
+ {
+ message: 'Account is already connected to your identity.'
+ },
{ duration: 2000 }
)
break
diff --git a/packages/design-system/src/molecules/auth-button/AuthButton.tsx b/packages/design-system/src/molecules/auth-button/AuthButton.tsx
index 12a2988cf9..95dceb361c 100644
--- a/packages/design-system/src/molecules/auth-button/AuthButton.tsx
+++ b/packages/design-system/src/molecules/auth-button/AuthButton.tsx
@@ -22,15 +22,14 @@ export const AuthButton = ({
displayContinueWith = false,
}: AuthButtonProps) => (
{Graphic && (
(
@@ -52,4 +52,4 @@ rounded-lg border shadow"
{btnText}
-)
\ No newline at end of file
+)
diff --git a/packages/design-system/src/molecules/email-connection-modal/EmailConnection.tsx b/packages/design-system/src/molecules/email-connection-modal/EmailConnection.tsx
index acb3ac4085..4a2e7702f7 100644
--- a/packages/design-system/src/molecules/email-connection-modal/EmailConnection.tsx
+++ b/packages/design-system/src/molecules/email-connection-modal/EmailConnection.tsx
@@ -1,23 +1,23 @@
-import React, { FC } from 'react'
-import googleIcon from '@proofzero/design-system/src/assets/social_icons/google.svg'
-import microsoftIcon from '@proofzero/design-system/src/assets/social_icons/microsoft.svg'
+import React from 'react'
+import googleIcon from '@proofzero/design-system/src/atoms/providers/Google'
+import microsoftIcon from '@proofzero/design-system/src/atoms/providers/Microsoft'
import appleIcon from '@proofzero/design-system/src/atoms/providers/Apple'
import { MdOutlineEmail } from 'react-icons/md'
import { Text } from '../../atoms/text/Text'
import { Button } from '../../build'
-import { EmailAddressType, OAuthAddressType } from '@proofzero/types/address'
+import { EmailAccountType, OAuthAccountType } from '@proofzero/types/account'
import { NestedErrorPage } from '../../pages/nested-error/NestedErrorPage'
type NonEmptyArray
= [T, ...T[]]
export type EmailConnectionProp = {
addr_type:
- | EmailAddressType.Email
- | OAuthAddressType.Google
- | OAuthAddressType.Microsoft
- | OAuthAddressType.Apple
+ | EmailAccountType.Email
+ | OAuthAccountType.Google
+ | OAuthAccountType.Microsoft
+ | OAuthAccountType.Apple
callback: () => void
}
@@ -27,16 +27,16 @@ export type EmailConnectionsProps = {
}
const iconMapper = {
- [OAuthAddressType.Google]: (
+ [OAuthAccountType.Google]: (
),
- [OAuthAddressType.Microsoft]: (
+ [OAuthAccountType.Microsoft]: (
),
- [OAuthAddressType.Apple]: (
-
+ [OAuthAccountType.Apple]: (
+
),
- [EmailAddressType.Email]: ,
+ [EmailAccountType.Email]: ,
}
export const EmailConnection = ({
@@ -44,11 +44,11 @@ export const EmailConnection = ({
cancelCallback,
}: EmailConnectionsProps) => {
const genericEmailProvider = providers.filter(
- (provider) => provider.addr_type === EmailAddressType.Email
+ (provider) => provider.addr_type === EmailAccountType.Email
)
const nonGenericEmailProviders = providers.filter(
- (provider) => provider.addr_type !== EmailAddressType.Email
+ (provider) => provider.addr_type !== EmailAccountType.Email
)
return providers.length ? (
@@ -61,7 +61,7 @@ export const EmailConnection = ({
{genericEmailProvider.length ? (
) : null}
{
cancelCallback()
}}
diff --git a/packages/design-system/src/molecules/email-otp-validator/EmailOTPValidator.tsx b/packages/design-system/src/molecules/email-otp-validator/EmailOTPValidator.tsx
index 2e842718a3..cfdf35e41e 100644
--- a/packages/design-system/src/molecules/email-otp-validator/EmailOTPValidator.tsx
+++ b/packages/design-system/src/molecules/email-otp-validator/EmailOTPValidator.tsx
@@ -107,19 +107,27 @@ export default function EmailOTPValidator({
>
{goBack && (
)}
-
+
Please check your email
-
-
-
We've sent a code to
-
{email}
+
+
+
+ We've sent a code to
+
+
+ {email}
+
@@ -186,14 +194,14 @@ export default function EmailOTPValidator({
inputRefs[i].current?.select()
}}
- className={`flex text-base lg:text-2xl py-7 px-3.5 h-20 justify-center items-center text-gray-600 border rounded-lg text-center ${
- isInvalid ? 'border-red-500' : ''
+ className={`flex text-base lg:text-2xl py-7 px-3.5 h-20 justify-center items-center text-gray-600 dark:text-white dark:bg-gray-800 border rounded-lg text-center ${
+ isInvalid ? 'border-red-500' : 'dark:border-gray-600'
}`}
/>
))}
- {showInvalidMessage && invalid && (
+ {!loading && (showInvalidMessage || invalid) && (
@@ -258,7 +266,7 @@ export default function EmailOTPValidator({
)}
{
diff --git a/packages/design-system/src/molecules/loader/Loader.tsx b/packages/design-system/src/molecules/loader/Loader.tsx
index 183ad879ba..071751ab34 100644
--- a/packages/design-system/src/molecules/loader/Loader.tsx
+++ b/packages/design-system/src/molecules/loader/Loader.tsx
@@ -1,7 +1,102 @@
import React from 'react'
import styled, { keyframes } from 'styled-components'
-export const Loader = () => {
+export const Loader = ({ mainColor }: { mainColor?: string }) => {
+ const loaderBackgroundAnimation = keyframes`
+ 0%, 24.9% {
+ background-color: #e5e7eb;
+ }
+ 25%, 49.9% {
+ background-color: #d1d5db;
+ }
+ 50%, 74.9% {
+ background-color: #9ca3af;
+ }
+ 75%, 100% {
+ background-color: ${mainColor ? mainColor : '#6366f1'};
+ }
+ `
+
+ const loaderFrontAnimation = keyframes`
+ 0% {
+ width: 0;
+ background-color: #d1d5db;
+ }
+ 24.9% {
+ width: 50%;
+ background-color: #d1d5db;
+ }
+ 25% {
+ width: 0;
+ background-color: #9ca3af;
+ }
+ 49.9% {
+ width: 50%;
+ background-color: #9ca3af;
+ }
+ 50% {
+ width: 0;
+ background-color: ${mainColor ? mainColor : '#6366f1'};
+ }
+ 74.9% {
+ width: 50%;
+ background-color: ${mainColor ? mainColor : '#6366f1'};
+ }
+ 75% {
+ width: 0%;
+ background-color: #e5e7eb;
+ }
+ 100% {
+ width: 50%;
+ background-color: #e5e7eb;
+ }
+ `
+ const Header = styled.header.attrs({
+ role: 'progressbar',
+ 'aria-busy': 'true',
+ })`
+ position: fixed;
+ top: 0;
+ left: 0;
+ padding-top: 8px;
+ width: 100%;
+ background-color: #e5e7eb;
+ animation-name: ${loaderBackgroundAnimation};
+ animation-duration: 3.5s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ &:before {
+ display: block;
+ position: fixed;
+ top: 0;
+ width: 0;
+ height: 8px;
+ background: #afa;
+ animation: preloader-front linear 3.5s infinite;
+ content: '';
+ right: 50%;
+ animation-name: ${loaderFrontAnimation};
+ animation-duration: 3.5s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ }
+ &:after {
+ display: block;
+ position: fixed;
+ top: 0;
+ width: 0;
+ height: 8px;
+ background: #afa;
+ animation: preloader-front linear 3.5s infinite;
+ content: '';
+ left: 50%;
+ animation-name: ${loaderFrontAnimation};
+ animation-duration: 3.5s;
+ animation-timing-function: linear;
+ animation-iteration-count: infinite;
+ }
+ `
+
return (
{
/>
)
}
-
-const loaderBackgroundAnimation = keyframes`
-0%, 24.9% {
- background-color: #e5e7eb;
- }
- 25%, 49.9% {
- background-color: #d1d5db;
- }
- 50%, 74.9% {
- background-color: #9ca3af;
- }
- 75%, 100% {
- background-color: #6366f1;
- }
-`
-
-const loaderFrontAnimation = keyframes`
-0% {
- width: 0;
- background-color: #d1d5db;
- }
- 24.9% {
- width: 50%;
- background-color: #d1d5db;
- }
- 25% {
- width: 0;
- background-color: #9ca3af;
- }
- 49.9% {
- width: 50%;
- background-color: #9ca3af;
- }
- 50% {
- width: 0;
- background-color: #6366f1;
- }
- 74.9% {
- width: 50%;
- background-color: #6366f1;
- }
- 75% {
- width: 0%;
- background-color: #e5e7eb;
- }
- 100% {
- width: 50%;
- background-color: #e5e7eb;
- }
-`
-
-const Header = styled.header.attrs({
- role: 'progressbar',
- 'aria-busy': 'true',
-})`
- position: fixed;
- top: 0;
- left: 0;
- padding-top: 8px;
- width: 100%;
- background-color: #e5e7eb;
- animation-name: ${loaderBackgroundAnimation};
- animation-duration: 3.5s;
- animation-timing-function: linear;
- animation-iteration-count: infinite;
- &:before {
- display: block;
- position: fixed;
- top: 0;
- width: 0;
- height: 8px;
- background: #afa;
- animation: preloader-front linear 3.5s infinite;
- content: '';
- right: 50%;
- animation-name: ${loaderFrontAnimation};
- animation-duration: 3.5s;
- animation-timing-function: linear;
- animation-iteration-count: infinite;
- }
- &:after {
- display: block;
- position: fixed;
- top: 0;
- width: 0;
- height: 8px;
- background: #afa;
- animation: preloader-front linear 3.5s infinite;
- content: '';
- left: 50%;
- animation-name: ${loaderFrontAnimation};
- animation-duration: 3.5s;
- animation-timing-function: linear;
- animation-iteration-count: infinite;
- }
-`
diff --git a/packages/design-system/src/molecules/modal/Modal.tsx b/packages/design-system/src/molecules/modal/Modal.tsx
index 73334fbc15..5cf757d6d2 100644
--- a/packages/design-system/src/molecules/modal/Modal.tsx
+++ b/packages/design-system/src/molecules/modal/Modal.tsx
@@ -1,23 +1,19 @@
import React, { Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
-import { HiOutlineX } from 'react-icons/hi'
+import classNames from 'classnames'
export type ModalProps = {
children: any
-
isOpen: boolean
handleClose?: (value: boolean) => void
-
fixed?: boolean
- closable?: boolean
}
export const Modal = ({
isOpen = false,
fixed = false,
handleClose,
- closable = true,
children,
...rest
}: ModalProps) => {
@@ -53,25 +49,18 @@ export const Modal = ({
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
-
+
-
-
{
- if (handleClose && closable) handleClose(false)
- }}
- >
-
-
-
{children}
diff --git a/packages/design-system/src/molecules/smart-contract-wallet-connection/SmartContractWalletConnection.tsx b/packages/design-system/src/molecules/smart-contract-wallet-connection/SmartContractWalletConnection.tsx
index d9af39fb74..7952ce6c02 100644
--- a/packages/design-system/src/molecules/smart-contract-wallet-connection/SmartContractWalletConnection.tsx
+++ b/packages/design-system/src/molecules/smart-contract-wallet-connection/SmartContractWalletConnection.tsx
@@ -17,7 +17,7 @@ export const SmartContractWalletCreationSummary = ({
}) => {
return (
-
+
void
onSignOut: () => void
onChooseOther: () => void
@@ -27,24 +28,24 @@ export default ({
logoURL = AuthenticationScreenDefaults.defaultLogoURL,
userProfile,
appProfile,
- onAuth = () => { },
- onSignOut = () => { },
- onChooseOther = () => { },
+ onAuth = () => {},
+ onSignOut = () => {},
+ onChooseOther = () => {},
}: AccountSelectProps) => {
return (
-
+
Choose an account
@@ -55,7 +56,7 @@ export default ({
href={appProfile.websiteURL}
target="_blank"
rel="noreferrer"
- className="text-indigo-500"
+ className="text-skin-primary"
>
{appProfile.name}
@@ -95,13 +96,12 @@ export default ({
-
- or
-
+
+ or
+
-
diff --git a/packages/design-system/src/templates/authentication/Authentication.stories.mdx b/packages/design-system/src/templates/authentication/Authentication.stories.mdx
index e571949eab..b3addf94bc 100644
--- a/packages/design-system/src/templates/authentication/Authentication.stories.mdx
+++ b/packages/design-system/src/templates/authentication/Authentication.stories.mdx
@@ -1,8 +1,8 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs'
import Authentication, { AuthenticationScreenDefaults } from './Authentication'
-import { createConfig } from 'wagmi'
-import { getDefaultConfig } from 'connectkit'
+import { createConfig, WagmiConfig } from 'wagmi'
+import { getDefaultConfig, ConnectKitProvider } from 'connectkit'
import { Text } from '@proofzero/design-system/src/atoms/text/Text'
import { Avatar } from '@proofzero/design-system/src/atoms/profile/avatar/Avatar'
@@ -12,44 +12,47 @@ import subtractLogo from '../../assets/subtract-logo.svg'
export const Template = ({ displayKeys }) => {
return (
-
-
-
-
Login to Passport
-
- {AuthenticationScreenDefaults.defaultSubheading}
-
-
- >
- }
- />
+
+
+
+
+
+
Login to Passport
+
+ {AuthenticationScreenDefaults.defaultSubheading}
+
+
+ >
+ }
+ />
+
+
)
}
# Authentication
-
+ < Canvas >
{
>
{Template.bind({})}
-
+
diff --git a/packages/design-system/src/templates/authentication/Authentication.tsx b/packages/design-system/src/templates/authentication/Authentication.tsx
index a0a0f69ff3..eabb6d3315 100644
--- a/packages/design-system/src/templates/authentication/Authentication.tsx
+++ b/packages/design-system/src/templates/authentication/Authentication.tsx
@@ -1,25 +1,36 @@
-import React, { useContext } from 'react'
+import React, { useContext, lazy } from 'react'
import circleLogo from './circle-logo.svg'
import subtractLogo from '../../assets/subtract-logo.svg'
import { Text } from '../../atoms/text/Text'
-import { WagmiConfig, Config } from 'wagmi'
import ConnectOAuthButton, {
OAuthProvider,
} from '../../atoms/buttons/connect-oauth-button'
-import { ConnectButton } from '../../atoms/buttons/connect-button/ConnectButton'
import { AuthButton } from '../../molecules/auth-button/AuthButton'
import { HiOutlineMail } from 'react-icons/hi'
import { TosAndPPol } from '../../atoms/info/TosAndPPol'
import { ThemeContext } from '../../contexts/theme'
+import { WrappedSVG as WebauthnGraphic } from '../../atoms/providers/Webauthn'
+
+const ConnectButton = lazy(() =>
+ import('../../atoms/buttons/connect-button/ConnectButton').then((module) => ({
+ default: module.ConnectButton,
+ }))
+)
export const AuthenticationScreenDefaults = {
defaultLogoURL: circleLogo,
defaultHeading: 'Welcome to the Private Web',
+ defaultSignMessage: `Welcome to Rollup!
+
+Sign this message to accept the Rollup Terms of Service (https://rollup.id/tos), no password needed!
+
+This will not trigger a blockchain transaction or cost any gas fees.`,
defaultSubheading: 'How would you like to continue?',
knownKeys: [
+ 'webauthn',
'wallet',
'email',
'google',
@@ -29,49 +40,53 @@ export const AuthenticationScreenDefaults = {
'discord',
'github',
],
+ color: {
+ light: '#6366F1',
+ dark: '#C6C7FF',
+ },
+ radius: 'lg',
}
-export type AppProfile = {
- name: string
- iconURL: string
- termsURL: string
- privacyURL: string
- websiteURL: string
+export const getDisplayNameFromProviderString = (provider: string): string => {
+ switch (provider) {
+ case 'webauthn':
+ return 'Passkeys'
+ default:
+ return provider.charAt(0).toUpperCase() + provider.substring(1).toLowerCase()
+ }
}
+export const appendNonceTemplate = (signMessage: string) =>
+ `${signMessage}${signMessage.endsWith('\n') ? '' : '\n'}\n{{nonce}}`
+
export type AuthenticationProps = {
logoURL?: string
- appProfile?: AppProfile
displayKeys: string[]
mapperArgs: DisplayKeyMapperArgs
Header?: JSX.Element
Actions?: JSX.Element
- radius?: string
}
-export default ({
- appProfile,
+export default function Authentication({
displayKeys,
mapperArgs,
Header,
Actions,
- radius = 'lg',
-}: AuthenticationProps) => {
+}: AuthenticationProps) {
displayKeys = displayKeys.filter((key) =>
AuthenticationScreenDefaults.knownKeys.includes(key)
)
- const { dark } = useContext(ThemeContext)
+ const { dark, theme } = useContext(ThemeContext)
return (
@@ -89,13 +104,12 @@ export default ({
{displayKeys.length > 2 && (
<>
-
{displayKeyDisplayFn(displayKeys.slice(2), mapperArgs)}
>
)}
@@ -120,7 +134,6 @@ export default ({
type DisplayKeyMapperArgs = {
clientId: string
- wagmiConfig: Config
signData: any
walletConnectCallback?: (address: string) => void
walletSignCallback?: (
@@ -131,45 +144,45 @@ type DisplayKeyMapperArgs = {
) => void
walletConnectErrorCallback?: (error: Error) => void
navigate?: (URL: string) => void
- FormWrapperEl?: ({ children, provider }) => JSX.Element
+ authnQueryParams: string
loading?: boolean
flex?: boolean
displayContinueWith?: boolean
enableOAuthSubmit?: boolean
+ signMessageTemplate: string
}
const displayKeyMapper = (
key: string,
{
clientId,
- wagmiConfig,
signData,
- walletConnectCallback = () => {},
- walletSignCallback = () => {},
- walletConnectErrorCallback = () => {},
- navigate = () => {},
- FormWrapperEl = ({ children }) => <>{children}>,
+ walletConnectCallback = () => { },
+ walletSignCallback = () => { },
+ walletConnectErrorCallback = () => { },
+ navigate = () => { },
+ authnQueryParams,
loading = false,
flex = false,
displayContinueWith = false,
enableOAuthSubmit = false,
+ signMessageTemplate,
}: DisplayKeyMapperArgs
) => {
let el
switch (key) {
case 'wallet':
el = (
-
-
-
+
)
break
case 'email':
@@ -184,16 +197,29 @@ const displayKeyMapper = (
/>
)
break
+ case 'webauthn':
+ el = (
+
navigate(`/authenticate/${clientId}/webauthn`)}
+ Graphic={WebauthnGraphic}
+ text={'Passkey'}
+ fullSize={flex}
+ displayContinueWith={displayContinueWith}
+ />
+ )
+ break
default:
el = (
-
-
-
+ {
+ const search = authnQueryParams ? `?${authnQueryParams}` : ''
+ navigate(`/connect/${key}${search}`)
+ }}
+ />
)
}
@@ -274,7 +300,6 @@ const displayKeyDisplayFn = (
Math.ceil(displayKeys.length / 2),
displayKeys.length
)
-
return [
...displayKeyDisplayFn(firstHalf, mapperArgs),
...displayKeyDisplayFn(secondHalf, mapperArgs),
diff --git a/packages/design-system/src/templates/authorization/Authorization.stories.mdx b/packages/design-system/src/templates/authorization/Authorization.stories.mdx
index 343fce3d6f..1151a17795 100644
--- a/packages/design-system/src/templates/authorization/Authorization.stories.mdx
+++ b/packages/design-system/src/templates/authorization/Authorization.stories.mdx
@@ -4,17 +4,25 @@ import Authorization from './Authorization'
import subtractLogo from '../../assets/subtract-logo.svg'
+import { HiOutlineEnvelope } from 'react-icons/hi2'
+
import { SCOPES_JSON } from '@proofzero/security/scopes'
+import googleIcon from '@proofzero/design-system/src/atoms/providers/Google'
+import microsoftIcon from '@proofzero/design-system/src/atoms/providers/Microsoft'
+import appleIcon from '@proofzero/design-system/src/atoms/providers/Apple'
+import { OptionType } from '@proofzero/utils/getNormalisedConnectedAccounts'
+
import {
NodeType,
- OAuthAddressType,
- EmailAddressType,
- CryptoAddressType,
-} from '@proofzero/types/address'
+ OAuthAccountType,
+ EmailAccountType,
+ CryptoAccountType,
+} from '@proofzero/types/account'
+
export const Template = (args) => (
(
privacyURL: 'foo',
termsURL: 'bar',
}}
- requestedScope={['openid', 'profile', 'connected_accounts', 'email']}
+ requestedScope={['openid', 'profile', 'connected_accounts', 'email', 'erc_4337']}
scopeMeta={{
scopes: SCOPES_JSON,
}}
connectedEmails={[
{
- email: 'email@example.com',
- type: EmailAddressType.Email,
- addressURN:
- 'urn:rollupid:address/0xc2b930f1fc2a55ddc1bf89e8844ca0479567ac44f3e2eea58216660e26947686',
+ title: 'email@gmail.com',
+ value: 'urn:rollupid:account/1',
+ icon: ,
+ },
+ {
+ title: 'email@microsoft.com',
+ value: 'urn:rollupid:account/2',
+ icon: ,
+ },
+ {
+ title: 'perez@apple.com',
+ value: 'urn:rollupid:account/5',
+ icon: ,
},
{
- email: 'email2@example.com',
- type: OAuthAddressType.Microsoft,
- addressURN:
- 'urn:rollupid:address/0xc2b930f1fc2a55ddc1bf99e8844ca0479567ac44f3e2eea58216660e26947686',
+ title: 'email@yahoo.com',
+ value: 'urn:rollupid:account/3',
+ icon: ,
+ selected: true,
},
]}
selectEmailCallback={() => {}}
addNewEmailCallback={() => {}}
connectedAccounts={[
{
- address: 'email@example.com',
- title: 'email@example.com',
- icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA3IiBoZWlnaHQ9IjEwNyIgdmlld0JveD0iMCAwIDEwNyAxMDciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDciIGhlaWdodD0iMTA3IiByeD0iMTcuODMwOCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzgxMDNfMjEwMTApIi8+CjxwYXRoIGQ9Ik02Ny44NjYzIDg2LjgyNDZDODAuMjMwMiA4MS4xMjgzIDg4LjgxMjUgNjguNjI3IDg4LjgxMjUgNTQuMTIxMUM4OC44MTI1IDM0LjI0NTMgNzIuNyAxOC4xMzI4IDUyLjgyNDIgMTguMTMyOEMzMi45NDg0IDE4LjEzMjggMTYuODM1OSAzNC4yNDUzIDE2LjgzNTkgNTQuMTIxMUMxNi44MzU5IDY3LjIyMDkgMjMuODM1MSA3OC42ODU5IDM0LjI5NzcgODQuOTgwN1Y1My45MDgxTDM0LjI5ODkgNTMuOTA5M0MzNC40MTI0IDQzLjc3NSA0Mi42NjMgMzUuNTk0NiA1Mi44MjQyIDM1LjU5NDZDNjMuMDU2MSAzNS41OTQ2IDcxLjM1MDcgNDMuODg5MiA3MS4zNTA3IDU0LjEyMTFDNzEuMzUwNyA2NC4zNTE4IDYzLjA1ODEgNzIuNjQ1NiA1Mi44Mjc5IDcyLjY0NzZMNjcuODY2MyA4Ni44MjQ2WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl84MTAzXzIxMDEwIiB4MT0iNTMuNSIgeTE9IjAiIHgyPSI1My41IiB5Mj0iMTA3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM2MzY2RjEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzk0NkQwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==',
- type: 'email',
- id: 'urn:rollupid:address/0x98f8b8473269c7e4444756d5ecef7dce5457a5d58df4100b46478402f59de57c',
+ value: `urn:rollupid:account:0`,
+ title: `Account 0`,
+ subtitle: "SC Wallet - Account 0"
},
{
- address: 'email2@example.com',
- title: 'MS Email',
- icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA3IiBoZWlnaHQ9IjEwNyIgdmlld0JveD0iMCAwIDEwNyAxMDciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDciIGhlaWdodD0iMTA3IiByeD0iMTcuODMwOCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzgxMDNfMjEwMTApIi8+CjxwYXRoIGQ9Ik02Ny44NjYzIDg2LjgyNDZDODAuMjMwMiA4MS4xMjgzIDg4LjgxMjUgNjguNjI3IDg4LjgxMjUgNTQuMTIxMUM4OC44MTI1IDM0LjI0NTMgNzIuNyAxOC4xMzI4IDUyLjgyNDIgMTguMTMyOEMzMi45NDg0IDE4LjEzMjggMTYuODM1OSAzNC4yNDUzIDE2LjgzNTkgNTQuMTIxMUMxNi44MzU5IDY3LjIyMDkgMjMuODM1MSA3OC42ODU5IDM0LjI5NzcgODQuOTgwN1Y1My45MDgxTDM0LjI5ODkgNTMuOTA5M0MzNC40MTI0IDQzLjc3NSA0Mi42NjMgMzUuNTk0NiA1Mi44MjQyIDM1LjU5NDZDNjMuMDU2MSAzNS41OTQ2IDcxLjM1MDcgNDMuODg5MiA3MS4zNTA3IDU0LjEyMTFDNzEuMzUwNyA2NC4zNTE4IDYzLjA1ODEgNzIuNjQ1NiA1Mi44Mjc5IDcyLjY0NzZMNjcuODY2MyA4Ni44MjQ2WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl84MTAzXzIxMDEwIiB4MT0iNTMuNSIgeTE9IjAiIHgyPSI1My41IiB5Mj0iMTA3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM2MzY2RjEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzk0NkQwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==',
- type: 'microsoft',
- id: 'urn:rollupid:address/0x3c7d7e3fef81c03333ed63d4ac83d2a1840356122163985deb1615e6ecfc25be',
+ value: `urn:rollupid:account:1`,
+ title: `Account 1`,
+ subtitle: "Github - Account 1"
},
{
- address: 'Github-Account',
- title: 'Github',
- icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA3IiBoZWlnaHQ9IjEwNyIgdmlld0JveD0iMCAwIDEwNyAxMDciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDciIGhlaWdodD0iMTA3IiByeD0iMTcuODMwOCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzgxMDNfMjEwMTApIi8+CjxwYXRoIGQ9Ik02Ny44NjYzIDg2LjgyNDZDODAuMjMwMiA4MS4xMjgzIDg4LjgxMjUgNjguNjI3IDg4LjgxMjUgNTQuMTIxMUM4OC44MTI1IDM0LjI0NTMgNzIuNyAxOC4xMzI4IDUyLjgyNDIgMTguMTMyOEMzMi45NDg0IDE4LjEzMjggMTYuODM1OSAzNC4yNDUzIDE2LjgzNTkgNTQuMTIxMUMxNi44MzU5IDY3LjIyMDkgMjMuODM1MSA3OC42ODU5IDM0LjI5NzcgODQuOTgwN1Y1My45MDgxTDM0LjI5ODkgNTMuOTA5M0MzNC40MTI0IDQzLjc3NSA0Mi42NjMgMzUuNTk0NiA1Mi44MjQyIDM1LjU5NDZDNjMuMDU2MSAzNS41OTQ2IDcxLjM1MDcgNDMuODg5MiA3MS4zNTA3IDU0LjEyMTFDNzEuMzUwNyA2NC4zNTE4IDYzLjA1ODEgNzIuNjQ1NiA1Mi44Mjc5IDcyLjY0NzZMNjcuODY2MyA4Ni44MjQ2WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl84MTAzXzIxMDEwIiB4MT0iNTMuNSIgeTE9IjAiIHgyPSI1My41IiB5Mj0iMTA3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM2MzY2RjEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzk0NkQwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==',
- type: 'github',
- id: 'urn:rollupid:address/0xa69240d7b361e122d22aa68ff97b9530c7c85953fba9dac392ca8dbfb88e17cc',
+ value: `urn:rollupid:account:2`,
+ title: `Account 2`,
+ subtitle: "Google - Account 2"
},
{
- address: '0x6c60Da9471181Aa54C648c6e203663A5501363F3',
- title: 'ens.eth',
- icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTA3IiBoZWlnaHQ9IjEwNyIgdmlld0JveD0iMCAwIDEwNyAxMDciIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDciIGhlaWdodD0iMTA3IiByeD0iMTcuODMwOCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzgxMDNfMjEwMTApIi8+CjxwYXRoIGQ9Ik02Ny44NjYzIDg2LjgyNDZDODAuMjMwMiA4MS4xMjgzIDg4LjgxMjUgNjguNjI3IDg4LjgxMjUgNTQuMTIxMUM4OC44MTI1IDM0LjI0NTMgNzIuNyAxOC4xMzI4IDUyLjgyNDIgMTguMTMyOEMzMi45NDg0IDE4LjEzMjggMTYuODM1OSAzNC4yNDUzIDE2LjgzNTkgNTQuMTIxMUMxNi44MzU5IDY3LjIyMDkgMjMuODM1MSA3OC42ODU5IDM0LjI5NzcgODQuOTgwN1Y1My45MDgxTDM0LjI5ODkgNTMuOTA5M0MzNC40MTI0IDQzLjc3NSA0Mi42NjMgMzUuNTk0NiA1Mi44MjQyIDM1LjU5NDZDNjMuMDU2MSAzNS41OTQ2IDcxLjM1MDcgNDMuODg5MiA3MS4zNTA3IDU0LjEyMTFDNzEuMzUwNyA2NC4zNTE4IDYzLjA1ODEgNzIuNjQ1NiA1Mi44Mjc5IDcyLjY0NzZMNjcuODY2MyA4Ni44MjQ2WiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl84MTAzXzIxMDEwIiB4MT0iNTMuNSIgeTE9IjAiIHgyPSI1My41IiB5Mj0iMTA3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM2MzY2RjEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzk0NkQwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==',
- type: 'eth',
- id: 'urn:rollupid:address/0x4416ad52d0d65d4b8852b8041039822e92ff4aa301af1b3ab987bd930f6fb4c8',
+ value: `urn:rollupid:account:3`,
+ title: `Account 3`,
+ subtitle: "Microsoft - Account 3"
},
]}
selectAccountsCallback={() => {}}
disableAuthorization={true}
transitionState={'idle'}
+ connectedSmartContractWallets={[
+ {
+ value: `urn:rollupid:account:0`,
+ title: `Account 0`,
+ subtitle: "SC Wallet - Account 0"
+ },
+ {
+ value: `urn:rollupid:account:1`,
+ title: `Account 1`,
+ subtitle: "Github - Account 1"
+ },
+ {
+ value: `urn:rollupid:account:2`,
+ title: `Account 2`,
+ subtitle: "Google - Account 2"
+ },
+ {
+ value: `urn:rollupid:account:3`,
+ title: `Account 3`,
+ subtitle: "Microsoft - Account 3"
+ },
+ ]}
+ selectSmartWalletCallback={() => {}}
+ addNewSmartWalletCallback={() => {}}
>
{' '}
@@ -87,6 +120,6 @@ export const Template = (args) => (
# Authorization
-
+ < Canvas >
{Template.bind({})}
-
+
diff --git a/packages/design-system/src/templates/authorization/Authorization.tsx b/packages/design-system/src/templates/authorization/Authorization.tsx
index 966b7fba3f..b74fc23f82 100644
--- a/packages/design-system/src/templates/authorization/Authorization.tsx
+++ b/packages/design-system/src/templates/authorization/Authorization.tsx
@@ -1,26 +1,22 @@
-import React, { useContext } from 'react'
+import React from 'react'
import { Avatar } from '../../atoms/profile/avatar/Avatar'
import { Text } from '../../atoms/text/Text'
import authorizeCheck from './authorize-check.svg'
-import { SmartContractWalletSelect } from '../../atoms/smart_contract_wallets/SmartContractWalletSelect'
import subtractLogo from '../../assets/subtract-logo.svg'
import { Spinner } from '../../atoms/spinner/Spinner'
import { Button } from '../../atoms/buttons/Button'
import Info from '../../atoms/info/Info'
-import {
- EmailSelectListItem,
- OptionType,
- SCWalletSelectListItem,
-} from '@proofzero/utils/getNormalisedConnectedAccounts'
-import { EmailSelect } from '../../atoms/email/EmailSelect'
-import { ConnectedAccountSelect } from '../../atoms/accounts/ConnectedAccountSelect'
-import { GetAddressProfileResult } from '@proofzero/platform/address/src/jsonrpc/methods/getAddressProfile'
-import { AuthorizationControlSelection } from '@proofzero/types/application'
-import { AddressURN } from '@proofzero/urns/address'
import { ScopeDescriptor } from '@proofzero/security/scopes'
+import { AuthorizationControlSelection } from '@proofzero/types/application'
import { TosAndPPol } from '../../atoms/info/TosAndPPol'
-import { ThemeContext } from '../../contexts/theme'
import ScopeIcon from './ScopeIcon'
+import {
+ Dropdown,
+ DropdownListboxButtonType,
+ DropdownSelectListItem,
+} from '../../atoms/dropdown/DropdownSelectList'
+
+import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid'
type UserProfile = {
pfpURL: string
@@ -44,19 +40,28 @@ type AuthorizationProps = {
transitionState: 'idle' | 'submitting' | 'loading'
- connectedSmartContractWallets: SCWalletSelectListItem[]
+ connectedSmartContractWallets?: Array
addNewSmartWalletCallback: () => void
- selectSmartWalletCallback: (selected: AddressURN[]) => void
+ selectSmartWalletsCallback: (selected: Array) => void
+ selectAllSmartWalletsCallback: (
+ val: Array
+ ) => void
+ selectedSCWallets:
+ | Array
+ | Array
- connectedEmails: EmailSelectListItem[]
+ connectedEmails?: Array
addNewEmailCallback: () => void
- selectEmailCallback: (selected: EmailSelectListItem) => void
+ selectEmailCallback: (selected: DropdownSelectListItem) => void
+ selectedEmail?: DropdownSelectListItem
- connectedAccounts: GetAddressProfileResult[]
+ connectedAccounts?: Array
addNewAccountCallback: () => void
- selectAccountsCallback: (
- selected: AddressURN[] | AuthorizationControlSelection[]
- ) => void
+ selectAccountsCallback: (selected: Array) => void
+ selectAllAccountsCallback: (val: Array) => void
+ selectedConnectedAccounts:
+ | Array
+ | Array
cancelCallback: () => void
authorizeCallback: (scopes: string[]) => void
@@ -64,6 +69,7 @@ type AuthorizationProps = {
radius?: string
}
+//eslint-disable-next-line react/display-name
export default ({
userProfile,
appProfile,
@@ -72,13 +78,18 @@ export default ({
transitionState,
connectedSmartContractWallets,
addNewSmartWalletCallback,
- selectSmartWalletCallback,
+ selectSmartWalletsCallback,
+ selectAllSmartWalletsCallback,
+ selectedSCWallets,
connectedEmails,
addNewEmailCallback,
selectEmailCallback,
+ selectedEmail,
connectedAccounts,
addNewAccountCallback,
selectAccountsCallback,
+ selectAllAccountsCallback,
+ selectedConnectedAccounts,
cancelCallback,
authorizeCallback,
disableAuthorize = false,
@@ -100,234 +111,336 @@ export default ({
}
scopesToDisplay.unshift('system_identifiers')
}
-
- const { dark, theme } = useContext(ThemeContext)
-
return (
-
-
-
-
+
+
-
-
-
-
-
- {appProfile.name}
-
-
- would like access to the following information
-
-
-
-
- REQUESTED
-
-
- {scopesToDisplay.map((scope: string, i: number) => {
+ />
+