diff --git a/gatsby-config.js b/gatsby-config.js index 99d6a679..db9fb143 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -10,6 +10,8 @@ * governing permissions and limitations under the License. */ + + module.exports = { siteMetadata: { pages: [ @@ -224,5 +226,6 @@ module.exports = { ] }, plugins: [`@adobe/gatsby-theme-aio`], - pathPrefix: process.env.PATH_PREFIX || "/developer-console/docs/" + pathPrefix: process.env.PATH_PREFIX || "/developer-console/docs/", + }; diff --git a/package.json b/package.json index 091d68e8..11587873 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,9 @@ "url": "https://github.com/icaraps" }, "dependencies": { - "@adobe/gatsby-theme-aio": "^4.14.4", + "@adobe/gatsby-theme-aio": "^4.14.12", "gatsby": "4.22.0", + "http-proxy-middleware": "^3.0.3", "react": "^17.0.2", "react-dom": "^17.0.2" }, diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/AccessToken.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/AccessToken.js new file mode 100644 index 00000000..65579d78 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/AccessToken.js @@ -0,0 +1,142 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import { ProgressCircle } from "@adobe/gatsby-theme-aio/src/components/ProgressCircle"; +import { Button } from "@adobe/gatsby-theme-aio/src/components/Button"; +import { CopyIcon } from '../Icons'; +import { Toast } from '@adobe/gatsby-theme-aio/src/components/Toast'; +import GetCredentialContext from '../GetCredentialContext'; +import { generateToken, getCredentialSecrets } from '../Service'; +import { ActionButton } from '@adobe/gatsby-theme-aio/src/components/ActionButton'; + +const AccessToken = ({ accessToken, response, scopesDetails }) => { + const [credentialToken, setCredentialToken] = useState(null); + const { selectedOrganization } = useContext(GetCredentialContext); + const [isCopiedTooltip, setIsCopiedTooltip] = useState(false); + const [isHoveringCopyButton, setIsHoveringCopyButton] = useState(false); + + const handleGenerateToken = async () => { + setCredentialToken('loading'); + const secrets = await getCredentialSecrets(response, selectedOrganization); + if (secrets) { + let clientId = response?.workspaces ? response?.workspaces[0]?.credentials[0]?.clientId : response?.apiKey; + const tokenVal = await generateToken(clientId, secrets?.clientSecret, scopesDetails); + navigator.clipboard.writeText(tokenVal); + setCredentialToken(tokenVal); + } + }; + + const handleSecretCopyCode = (copiedVal) => { + setIsCopiedTooltip(true); + navigator.clipboard.writeText(copiedVal); + setTimeout(() => setIsCopiedTooltip(false), 1000); // Hide tooltip after 1 second + }; + + useEffect(() => { + setCredentialToken(null); + }, [response]); + + return ( + <> + {accessToken && ( +
+ {accessToken?.heading && ( +

{accessToken?.heading}

+ )} + {credentialToken === null ? ( + accessToken?.buttonLabel && ( +
handleGenerateToken()} + css={css` + width: fit-content; + & > button { + background-color : #0265dc !important; + } + `}> + +
+ ) + ) : ( + credentialToken === 'loading' ? ( + + ) : ( +
+

+ {credentialToken} +

+
handleSecretCopyCode(credentialToken)} + css={css` + position: relative; + & > button { + border: 1px solid rgba(177, 177, 177) !important; + padding: 4px !important; + border-radius: 2px !important; + } + `}> + setIsHoveringCopyButton(true)} + onMouseLeave={() => setIsHoveringCopyButton(false)} + > + + + {isHoveringCopyButton && ( +
+
Copy
+
+
+ )} +
+
+ ) + )} +
+ )} + { + isCopiedTooltip && ( + + ) + } + + ); +}; + +export { AccessToken }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardAPIKey.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardAPIKey.js new file mode 100644 index 00000000..88e09d4a --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardAPIKey.js @@ -0,0 +1,10 @@ +import React from 'react' +import ShowCard from './ShowCard'; + +const CardAPIKey = ({ cardAPIKey, apiKey }) => { + return ( + + ) +} + +export { CardAPIKey }; \ No newline at end of file diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardAllowedOrigins.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardAllowedOrigins.js new file mode 100644 index 00000000..0ba25ac2 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardAllowedOrigins.js @@ -0,0 +1,10 @@ +import React from 'react' +import ShowCard from './ShowCard'; + +const CardAllowedOrigins = ({ cardAllowedOrigins, allowedOrigins }) => { + return ( + + ) +} + +export { CardAllowedOrigins }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientDetails.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientDetails.js new file mode 100644 index 00000000..0eae54b4 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientDetails.js @@ -0,0 +1,82 @@ +import React, { useContext } from 'react'; +import { css } from "@emotion/react"; +import { CardAPIKey } from './CardAPIKey'; +import { CardClientId } from './CardClientId'; +import { CardAllowedOrigins } from './CardAllowedOrigins'; +import { CardClientSecret } from './CardClientSecret'; +import { CardOrganizationName } from './CardOrganizationName'; +import { CardScopes } from './CardScopes'; +import { CardImsOrgID } from './CardImsOrgID'; +import GetCredentialContext from '../GetCredentialContext'; + +const CardClientDetails = ({ + clientDetails, + clientIdDetails, + clientSecretDetails, + organizationDetails, + scopesDetails, + apiKeyDetails, + allowedOriginsDetails, + organizationName, + allowedOrigins, + response, + imsOrgID +}) => { + + const { selectedOrganization } = useContext(GetCredentialContext); + + const splitedOrderBy = clientDetails?.orderBy ? clientDetails?.orderBy?.split(',') : []; + + return ( +
+

{clientDetails?.heading}

+ + { + splitedOrderBy?.length > 0 ? + <> + {splitedOrderBy?.map((list) => { + if (list === "APIKey") { + return apiKeyDetails && + } + if (list === "AllowedOrigins") { + return allowedOrigins && + } + if (list === "ImsOrgID") { + return imsOrgID && + } + if (list === "OrganizationName") { + return organizationDetails && + } + if (list === "ClientId") { + return clientIdDetails && + } + if (list === "ClientSecret") { + return clientSecretDetails && + } + if (list === "Scopes") { + return scopesDetails && + } + })} + + : + <> + {apiKeyDetails && ()} + {clientIdDetails && ()} + {allowedOrigins && ()} + {clientSecretDetails && ()} + {organizationDetails && ()} + {scopesDetails && } + {imsOrgID && ()} + + } + +
+ ); +}; + +export { CardClientDetails }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientId.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientId.js new file mode 100644 index 00000000..4c9ca2ba --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientId.js @@ -0,0 +1,10 @@ + import React from 'react' + import ShowCard from './ShowCard' + + const CardClientId = ({ cardClientId, clientId }) => { + return ( + + ) + } + + export { CardClientId } diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientSecret.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientSecret.js new file mode 100644 index 00000000..b9a93a94 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardClientSecret.js @@ -0,0 +1,10 @@ +import React from 'react' +import ShowCard from './ShowCard'; + +const CardClientSecret = ({ cardClientSecret, response }) => { + return ( + + ) +} + +export { CardClientSecret }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardImsOrgID.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardImsOrgID.js new file mode 100644 index 00000000..7b42d884 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardImsOrgID.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ShowCard from './ShowCard'; + +const CardImsOrgID = ({ cardImsOrgID, imsOrgId }) => { + return ; +}; + +export { CardImsOrgID }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardOrganizationName.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardOrganizationName.js new file mode 100644 index 00000000..7ed147c6 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardOrganizationName.js @@ -0,0 +1,10 @@ +import React from 'react' +import ShowCard from './ShowCard'; + +const CardOrganizationName = ({ cardOrganizationName, organization }) => { + return ( + + ) +} + +export { CardOrganizationName }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardScopes.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardScopes.js new file mode 100644 index 00000000..9c370c71 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/CardScopes.js @@ -0,0 +1,9 @@ +import React from 'react' +import ShowCard from './ShowCard'; + +const CardScopes = ({ cardScopes }) => { + return ( + + ) +} +export { CardScopes }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/DevConsoleLink.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/DevConsoleLink.js new file mode 100644 index 00000000..11b62cc8 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/DevConsoleLink.js @@ -0,0 +1,60 @@ +import React, { useContext } from 'react'; +import { css } from '@emotion/react'; +import { MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from '../FormFields'; +import GetCredentialContext from '../GetCredentialContext'; +import { LinkOut } from '../Icons'; + +const DevConsoleLink = ({ devConsoleLinkHeading, credentialName, projectId }) => { + + const { selectedOrganization } = useContext(GetCredentialContext); + + return ( + <> +
+ {devConsoleLinkHeading && ( +

+ {devConsoleLinkHeading} +

+ )} + + +
+

+ {credentialName} +

+
+
+ +
+
+
+ + ); +}; + +export { DevConsoleLink }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/ShowCard.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/ShowCard.js new file mode 100644 index 00000000..ba627978 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Card/ShowCard.js @@ -0,0 +1,188 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import { CopyIcon } from '../Icons'; +import { ActionButton } from "@adobe/gatsby-theme-aio/src/components/ActionButton"; +import { ProgressCircle } from "@adobe/gatsby-theme-aio/src/components/ProgressCircle"; +import { Toast } from '@adobe/gatsby-theme-aio/src/components/Toast'; +import GetCredentialContext from '../GetCredentialContext'; +import { getCredentialSecrets } from '../Service'; + +const ShowCard = ({ + heading, + value, + isClientSecret, + buttonLabel, + isOraganization, + response +}) => { + + const [showClientSecret, setShowClientSecret] = useState(null); + const [isCopiedTooltip, setIsCopiedTooltip] = useState(false); + const [isHoveringCopyButton, setIsHoveringCopyButton] = useState(false); + const { selectedOrganization } = useContext(GetCredentialContext); + + const handleCreateClientSecret = async () => { + setShowClientSecret('loading'); + const secrets = await getCredentialSecrets(response, selectedOrganization); + navigator.clipboard.writeText(secrets?.clientSecret); + setShowClientSecret(secrets); + }; + + const handleSecretCopyCode = (copiedVal) => { + setIsCopiedTooltip(true); + navigator.clipboard.writeText(copiedVal); + setTimeout(() => setIsCopiedTooltip(false), 1000); // Hide tooltip after 1 second + } + + useEffect(() => { + setShowClientSecret(null); + }, [response]); + + return ( +
+

{heading}

+
+ {isClientSecret && ( + showClientSecret === null ? ( +
{ handleCreateClientSecret() }} data-cy="retrieve-client-secret" + css={css` + & > button { + border: 1px solid rgba(177, 177, 177) !important; + padding: 4px !important; + border-radius: 2px !important; + } + `}> + {buttonLabel} +
+ ) : showClientSecret === 'loading' ? ( + + ) : ( +
+

+ {showClientSecret?.clientSecret} +

+
+
setIsHoveringCopyButton(true)} + css={css` + & > button { + border: 1px solid rgba(177, 177, 177) !important; + padding: 4px !important; + border-radius: 2px !important; + } + `} + onMouseLeave={() => setIsHoveringCopyButton(false)} data-cy="copy-client-secret" onClick={() => { handleSecretCopyCode(showClientSecret?.clientSecret) }}> + +
+ {isHoveringCopyButton && ( +
+
Copy
+
+
+ )} +
+
+ ) + )} + + {value && ( +

+ {value} +

+ )} + + {!isClientSecret && ( +
setIsHoveringCopyButton(true)} + onMouseLeave={() => setIsHoveringCopyButton(false)} + css={css` + position: relative; + display: ${isOraganization ? 'none' : 'block'}; + `}> +
{ handleSecretCopyCode(value) }} + css={css` + & > button { + border: 1px solid rgba(177, 177, 177) !important; + padding: 4px !important; + border-radius: 2px !important; + } + `} + > + +
+ {isHoveringCopyButton && ( +
+
Copy
+
+
+ )} +
+ )} +
+ { + isCopiedTooltip && ( + + ) + } +
+ ); +}; + +export default ShowCard; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/ContextHelp.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/ContextHelp.js new file mode 100644 index 00000000..dedeb550 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/ContextHelp.js @@ -0,0 +1,64 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { css } from "@emotion/react"; +import classNames from 'classnames'; +import { MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from './FormFields'; + +export const ContextHelp = ({ heading, text, link, label }) => { + + const [isVisible, setisVisible] = useState(false); + const buttonRef = useRef(); + + const handleClickOutside = e => { + if (buttonRef.current.contains(e.target)) setisVisible(!isVisible); + else setisVisible(false); + }; + + useEffect(() => { + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }); + + return ( +
+
+
+ +
+ {heading &&

{heading}

} + {text &&

{text}

} + {link && {label}} +
+
+
+
+ ) +} diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/CredentialDetailsCard.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/CredentialDetailsCard.js new file mode 100644 index 00000000..127a9e92 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/CredentialDetailsCard.js @@ -0,0 +1,250 @@ +import React, { useEffect, useState } from 'react'; +import { MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from './FormFields'; +import { css } from '@emotion/react'; +import { AccessToken } from './Card/AccessToken'; +import { DevConsoleLink } from './Card/DevConsoleLink'; +import { CardClientDetails } from './Card/CardClientDetails'; +import { CardClientId } from './Card/CardClientId'; +import { CardClientSecret } from './Card/CardClientSecret'; +import { CardOrganizationName } from './Card/CardOrganizationName'; +import { CardScopes } from './Card/CardScopes'; +import { CardAPIKey } from './Card/CardAPIKey'; +import { CardAllowedOrigins } from './Card/CardAllowedOrigins'; +import { ReturnAccessToken } from './Return/ReturnAccessToken'; +import { ReturnDevConsoleLink } from './Return/ReturnDevConsoleLink'; +import { ReturnCredentialDetails } from './Return/ReturnCredentialDetails'; +import { ReturnClientId } from './Return/ReturnClientId'; +import { ReturnClientSecret } from './Return/ReturnClientSecret'; +import { ReturnScopes } from './Return/ReturnScopes'; +import { ReturnAPIKey } from './Return/ReturnAPIKey'; +import { ReturnAllowedOrigins } from './Return/ReturnAllowedOrigins'; +import { ReturnOrganizationName } from './Return/ReturnOrganizationName'; +import { ArrowDown, ArrowRight, KeyIcon, LinkOut } from './Icons'; +import { CardImsOrgID } from './Card/CardImsOrgID'; + +export const CredentialDetailsCard = ({ + credentialName, + productList, + ProductComponent, + AccessTokenComponent, + DevConsoleLinkComponent, + ClientDetailsComponent, + allowedOriginsDetails, + organizationName, + nextButtonLink, + nextButtonLabel, + devConsoleLink, + developerConsoleManage, + response, + myCredentialFields, + returnFields, + collapse +}) => { + + let accessToken, devConsoleLinkHeading, clientDetails, clientIdDetails, clientSecretDetails, organizationDetails, scopesDetails, apiKeyDetails, allowedOrigins, imsOrgID; + if (myCredentialFields) { + accessToken = myCredentialFields[AccessToken]; + devConsoleLinkHeading = myCredentialFields[DevConsoleLink]?.heading; + clientDetails = myCredentialFields[CardClientDetails]; + clientIdDetails = myCredentialFields[CardClientId]; + clientSecretDetails = myCredentialFields[CardClientSecret]; + organizationDetails = myCredentialFields[CardOrganizationName]; + scopesDetails = myCredentialFields[CardScopes]; + apiKeyDetails = myCredentialFields[CardAPIKey]; + allowedOrigins = myCredentialFields[CardAllowedOrigins]; + imsOrgID = myCredentialFields?.[CardImsOrgID]; + } + else if (returnFields) { + accessToken = returnFields?.[ReturnAccessToken]; + devConsoleLinkHeading = returnFields?.[ReturnDevConsoleLink]?.heading; + clientDetails = returnFields?.[ReturnCredentialDetails]; + clientIdDetails = returnFields?.[ReturnClientId]; + clientSecretDetails = returnFields?.[ReturnClientSecret]; + scopesDetails = returnFields?.[ReturnScopes]; + apiKeyDetails = returnFields?.[ReturnAPIKey]; + organizationDetails = returnFields?.[ReturnOrganizationName]; + allowedOrigins = returnFields?.[ReturnAllowedOrigins]; + imsOrgID = returnFields?.[CardImsOrgID]; + } + + const bool = collapse === "true" ? true : false; + const [isCollapse, setIsCollapse] = useState(false); + + const handleCollapse = () => { + setIsCollapse(!isCollapse); + } + + useEffect(() => { + if (!bool) { + setIsCollapse(true); + } + }, [bool]) + + console.log('clientDetails', clientDetails) + console.log('isCollapse', isCollapse) + + return ( + <> +
+
+
+
+ +
+

{credentialName}

+
+ {productList && } +
+
+
+ { + bool && +
+ {isCollapse ? : } +
+ } +
+ + {isCollapse && + <> +
+
+ {accessToken && } + + {devConsoleLinkHeading && ( + + )} + + {clientDetails && ( + + )} + +
+ {nextButtonLabel && + + + } + {developerConsoleManage && + +
+
{developerConsoleManage}
+
+ +
+
+
+ } +
+
+ + } +
+
+ + ); +}; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/CredentialForm.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/CredentialForm.js new file mode 100644 index 00000000..c2f2d51a --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/CredentialForm.js @@ -0,0 +1,436 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import classNames from 'classnames'; +import '@spectrum-css/contextualhelp/dist/index-vars.css'; +import { Toast } from '@adobe/gatsby-theme-aio/src/components/Toast'; +import { handleAllowedDomainsValidation, MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from './FormFields'; +import { SideComponent } from './SideComponent'; +import { CredentialName } from './Form/CredentialName'; +import { AllowedOrigins } from './Form/AllowedOrigins'; +import { Downloads } from './Form/Downloads'; +import { Download } from './Form/Download'; +import { SideContent } from './Form/SideContent'; +import { AdobeDeveloperConsole } from './Form/AdobeDeveloperConsole'; +import { CreateCredential } from './Form/CreateCredential'; +import { MyCredential } from './MyCredential'; +import { Loading } from './Loading'; +import { IllustratedMessage } from './IllustratedMessage'; +import { Product, Products } from './Products'; +import { Organization } from './Organization'; +import GetCredentialContext from './GetCredentialContext'; + +const credentialNameRegex = /^(?=[A-Za-z0-9\s]{6,}$)[A-Za-z0-9\s]*$/; + +const CredentialForm = ({ + showCreateForm, + setShowCreateForm, + isCreateNewCredential, + setIsCreateNewCredential, + setIsPrevious, + formData, + setFormData +}) => { + const { getCredentialData } = useContext(GetCredentialContext); + const formProps = getCredentialData; + + const [loading, setLoading] = useState(false); + const [isError, setIsError] = useState(false); + const [response, setResponse] = useState({}); + const [errResp, setErrorResp] = useState(''); + const [showCredential, setShowCredential] = useState(false); + const [formField, setFormField] = useState([]); + const [isValid, setIsValid] = useState(false); + const [isMyCredential, setIsMyCredential] = useState(false); + const [isShow, setIsShow] = useState(false); + const [alertShow, setAlertShow] = useState(false); + + const [isAllowedOriginsValid, setIsAllowedOriginsValid] = useState(); + + const { selectedOrganization, template, previousProjectDetail } = useContext(GetCredentialContext); + + const credentialForm = formProps?.[CredentialForm]; + const isFormValue = credentialForm?.children?.filter(data => + Object.keys(data.props).some(key => key.startsWith('contextHelp')) + ); + + const initialLoad = () => { + const fields = {}; + const downloadObj = { label: 'Language', selectOptions: [] }; + const productsObj = { label: 'products', productList: [] }; + + credentialForm?.children.forEach(({ type, props }) => { + if (type === Downloads && props?.children) { + downloadObj.required = props.required || false; + downloadObj.selectOptions.push( + ...[].concat(props.children).map(({ props: { title, href } }) => ({ title, href })) + ); + setFormData(prevData => ({ + ...prevData, + ...(Array.isArray(props.children) ? null : { Download: props.children?.props?.title }), + })); + } + if (type === Products && props?.children) { + productsObj.productList.push( + ...[].concat(props.children).map(({ props: { label, icon } }) => ({ label, icon })) + ); + } + fields[type] = { ...props, required: type === CredentialName || props?.required }; + }); + + if (downloadObj.selectOptions.length) { + fields[Download] = downloadObj; + if (downloadObj.selectOptions.length === 1) { + setFormData(prevData => ({ + ...prevData, + Download: downloadObj.selectOptions[0]?.title, + zipUrl: downloadObj.selectOptions[0]?.href, + })); + } + } + if (productsObj?.productList.length) { + fields[Product] = productsObj; + } + + const isCredential = previousProjectDetail?.count ? true : false; + if (isCredential) { + setIsMyCredential(true); + } else { + setIsMyCredential(false); + } + + setFormField(fields); + }; + + useEffect(() => { + if (window.adobeIMS?.isSignedInUser()) { + setTimeout(() => { + setLoading(false); + }, 1000); + } else { + setLoading(true); + } + }, [window.adobeIMS?.isSignedInUser()]); + + useEffect(() => { + if (showCreateForm) setIsError(false); + }, [showCreateForm]); + + + useEffect(() => { + initialLoad(); + }, []); + + useEffect(() => { + const isValidCredentialName = credentialNameRegex.test(formData.CredentialName); + + const isCheckAllowedOrgins = credentialForm.children.some((child) => { + return child.type === AllowedOrigins; + }) + let isAllowedOriginsValid; + if (isCheckAllowedOrgins) { + if (formData['AllowedOrigins']) { + isAllowedOriginsValid = handleAllowedDomainsValidation(formData['AllowedOrigins']) + } + } + else { + isAllowedOriginsValid = true; + } + setIsAllowedOriginsValid(isAllowedOriginsValid) + + const isValid = isValidCredentialName && (!isCheckAllowedOrgins || isAllowedOriginsValid) && formData.Agree === true; + setIsValid(isValid); + }, [formData]); + + const handleChange = (e, type) => { + let value; + if (type === 'Download') { + value = e.title; + } + else { + value = type === 'Downloads' || type === 'Agree' ? e.target.checked : e.target.value; + } + + setFormData(prevData => ({ ...prevData, [type]: value })); + if (type === 'Downloads') { + handleChange(download?.selectOptions[0], 'Download') + } + + if (type === 'Download') { + const selectedData = formField?.[Download]?.selectOptions.find( + data => data.title === value + ); + selectedData && setFormData(prevData => ({ ...prevData, zipUrl: selectedData.href })); + } + }; + + const handleErrors = (detailedMessage) => { + setLoading(false); + setAlertShow(true); + setIsValid(false); + setErrorResp(detailedMessage); + setShowCreateForm(true); + setIsError(true); + } + + const createCredential = async () => { + const token = window.adobeIMS?.getTokenFromStorage()?.token; + + if (!token) { + console.log('User not logged in'); + } + + setLoading(true); + setShowCreateForm(false); + + const apis = template.apis.map(api => ({ + code: api.code, + credentialType: api.credentialType, + flowType: api.flowType, + licenseConfigs: + Array.isArray(api.licenseConfigs) && api.licenseConfigs.length > 0 + ? [{ ...api.licenseConfigs[0], op: 'add' }] + : [], + })); + + const data = { + projectName: formData['CredentialName'], + description: 'created for get credential', + metadata: { + domain: formData['AllowedOrigins'], + }, + orgId: selectedOrganization.code, + apis, + }; + + try { + const url = `/templates/install/${template.id}`; + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + 'x-api-key': window?.adobeIMS?.adobeIdData?.client_id, + Accept: 'application/json', + }, + body: JSON.stringify(data), + }); + + const resResp = await response?.json(); + + if (response.ok) { + setResponse(resResp); + setShowCredential(true); + setAlertShow(true); + setLoading(false); + } else { + const jsonString = resResp.errors[0].message.match(/\((\{.*\})\)/)[1]; + const errorDetails = JSON.parse(jsonString); + handleErrors(errorDetails.messages[0].message) + } + } catch (error) { + setShowCreateForm(true); + setLoading(false); + setAlertShow(true); + setErrorResp(error.message); + setIsError(true); + } + }; + + useEffect(() => { + if (isMyCredential) { + setIsPrevious(true); + setShowCreateForm(true); + } + }, [isMyCredential]); + + const sideObject = formField?.[SideComponent]; + const credentialName = formField?.[CredentialName]; + const allowedOrigins = formField?.[AllowedOrigins]; + const downloads = formField?.[Downloads]; + const download = formField?.[Download]; + const products = formField?.[Products]; + const product = formField?.[Product]; + const adobeDeveloperConsole = formField?.[AdobeDeveloperConsole]; + + const handleRestart = () => { + setShowCreateForm(true); + setShowCredential(false); + setIsCreateNewCredential(true); + setIsMyCredential(true); + setFormData({}); + }; + + return ( + <> + {showCreateForm && !loading && ( +
+
+
+ {credentialForm?.title && ( +

+ {credentialForm?.title} +

+ )} + {credentialForm?.paragraph && ( +

{credentialForm?.paragraph}

+ )} +

setIsShow(true)}> + {selectedOrganization.type === "developer" ? + "You're creating this credential in your personal developer organization" : + <>You're creating this credential in [{selectedOrganization?.name}].} + +

+
+
+
+
+
+ {credentialName && ( + + )} + {allowedOrigins && ( + + )} + {downloads && download && ( + + )} + {formData['Downloads'] && download && ( + + )} + + {adobeDeveloperConsole && ( + + )} + +
+
+ {sideObject ? ( + + ) : null} +
+
+ )} + {alertShow && ( + <> + { + + } + + )} + {loading && !showCredential && !isError && !showCreateForm && ( + + )} + {isError && !showCreateForm && !showCredential && ( + + )} + {showCredential && !showCreateForm && ( + + )} + + ); +}; + +export { CredentialForm }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/ErrorBoundary.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/ErrorBoundary.js new file mode 100644 index 00000000..b66f2abd --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/ErrorBoundary.js @@ -0,0 +1,33 @@ +import React, { Component } from 'react'; +import { IllustratedMessage } from './IllustratedMessage'; + +class ErrorBoundary extends Component { + constructor(props) { + super(props); + this.state = { + hasError: false, + error: null, + errorInfo: null, + errorMsg: this.props.errorMessage + }; + } + + componentDidCatch(error, errorInfo) { + this.setState({ + hasError: true, + error: error, + errorInfo: errorInfo, + }); + } + + render() { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +} + +export default ErrorBoundary; + diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/AdobeDeveloperConsole.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/AdobeDeveloperConsole.js new file mode 100644 index 00000000..b4005617 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/AdobeDeveloperConsole.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { css } from "@emotion/react"; + +const AdobeDeveloperConsole = ({ formData, handleChange, adobeDeveloperConsole }) => { + return ( +
+ handleChange(e, 'Agree')} data-cy="update-terms-condition"/> +

{adobeDeveloperConsole?.label} + {adobeDeveloperConsole?.linkText}. +

+
+ ) +} + +export { AdobeDeveloperConsole }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/AllowedOrigins.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/AllowedOrigins.js new file mode 100644 index 00000000..60aa8251 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/AllowedOrigins.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { css } from "@emotion/react"; +import { FormFields } from '../FormFields'; + +const AllowedOrigins = ({ originsProps, isFormValue, type, formData, handleChange, isAllowedOriginsValid }) => { + + const isRed = isAllowedOriginsValid!==undefined ? !isAllowedOriginsValid : false; + + return ( + + + + ) +} + +export { AllowedOrigins }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/CreateCredential.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/CreateCredential.js new file mode 100644 index 00000000..3b38120a --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/CreateCredential.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { css } from "@emotion/react"; + +const CreateCredential = ({ createCredential, isValid, setIsCreateNewCredential, isCreateNewCredential }) => { + return ( +
+ + {isCreateNewCredential &&

setIsCreateNewCredential(false)} data-cy="cancel-new-credential">Cancel

} +
+ ) +} + +export { CreateCredential }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/CredentialName.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/CredentialName.js new file mode 100644 index 00000000..eafbb7a2 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/CredentialName.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { css } from "@emotion/react"; +import { FormFields } from "../FormFields"; +import { AlertIcon } from '../Icons'; + +const CredentialName = ({ nameProps, isFormValue, formData, handleChange }) => { + + const credentialNameRegex = /^(?=[A-Za-z0-9\s]{6,}$)[A-Za-z0-9\s]*$/; + const inValidName = !credentialNameRegex.test(formData['CredentialName']) + const isRed = formData["CredentialName"]?.length !== 0 && inValidName; + return ( + +
+ handleChange(e, "CredentialName")} + placeholder={nameProps?.placeholder} + maxLength={nameProps?.range} + data-cy="add-credential-name" + /> + +
+
+ ) +} + +export { CredentialName } diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/Download.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/Download.js new file mode 100644 index 00000000..c36d5259 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/Download.js @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import { css } from "@emotion/react"; +import { FormFields } from "../FormFields"; +import { Picker } from '@adobe/gatsby-theme-aio/src/components/Picker'; + +const Download = ({ downloadProp, isFormValue, handleChange }) => { + const [selectedIndex, setSelectedIndex] = useState(0); + + return ( + <> + { + downloadProp?.selectOptions?.length > 1 && + +
div{ + position: relative; + } + + & >div> button{ + width: 100%; + } + + & >div> div{ + width: 100%; + } + `}> + { + return { + title: option?.title, + selected: index === selectedIndex, + }; + })} + onChange={index => { + setSelectedIndex(index); + handleChange(downloadProp?.selectOptions[index], "Download"); + }} + data-cy="select-download-language" + id="selectBox" + /> +
+
+ } + + + ) +} + +export { Download }; \ No newline at end of file diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/Downloads.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/Downloads.js new file mode 100644 index 00000000..13b511de --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/Downloads.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { css } from "@emotion/react"; +import { ContextHelp } from "../ContextHelp"; + +const Downloads = ({ downloadsProp, handleChange, formData }) => { + + const { label, contextHelpLabelForLink, contextHelpLink, contextHelpText, contextHelp, contextHelpHeading } = downloadsProp; + + return ( +
+ handleChange(e, "Downloads")} checked={formData['Downloads']} data-cy="download-checkBox" /> +

{label}

+
+ {contextHelp && } +
+
+ ) +} + +export { Downloads }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/SideContent.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/SideContent.js new file mode 100644 index 00000000..6b5f102e --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Form/SideContent.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { css } from "@emotion/react"; +import { MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from '../FormFields'; + +const SideContent = ({ sideContent, SideComp }) => { + return ( + <> +
+
+ +
+ + ) +} + +export { SideContent }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/FormFields.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/FormFields.js new file mode 100644 index 00000000..8ee08077 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/FormFields.js @@ -0,0 +1,128 @@ +import React from 'react'; +import { ContextHelp } from './ContextHelp'; +import { css } from "@emotion/react"; +import classNames from "classnames"; + +export const FormFields = ({ isFormValue, fields, children, formData, isRed }) => { + + const { label, range, contextHelpLabelForLink, contextHelpLink, contextHelpText, contextHelp, contextHelpHeading, description, className, required } = fields; + + return ( +
+
+
+ {label && } + {required && *} + {isFormValue?.length ? +
div > div > div > button { + border: none; + margin-left: 4px; + } + `} > + {contextHelp && } +
: null + } +
+ {range && + {formData['CredentialName'] ? range - formData['CredentialName']?.length : range} + } +
+
+ {children} +
+ {description &&
+

+ {description} +

+
+ } +
+ ) +} + +export const MIN_MOBILE_WIDTH = "320px"; +export const MAX_MOBILE_WIDTH = "767px"; +export const MAX_TABLET_SCREEN_WIDTH = "1024px"; +export const MIN_TABLET_SCREEB_WIDTH = "768px"; + +const domainPattern = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$|localhost$/; + +export const handleAllowedDomainsValidation = (value) => { + const domains = value && value.split(','); + let errors = []; + if (value && value !== '') { + if (domains.length > 5) { + errors.push('A maximum of 5 domains are allowed'); + } + domains.forEach((domain) => { + let domainToCheck = domain.trim(); + if (domainToCheck.startsWith('*.')) { + domainToCheck = domainToCheck.substring(2); + } + if (!validateDomain(domainToCheck)) { + errors.push(`Domain ${domain} is invalid`); + } + }); + + if (errors.length) { + return false + } + else { + return true + } + } +} + +const validateDomain = (domainToCheck) => { + if (domainToCheck.includes(':')) { + const domainsSplit = domainToCheck.split(':'); + if (domainsSplit.length !== 2) { + return false; + } + + // port is only allowed for localhost + if (domainsSplit[0] !== 'localhost') { + return false; + } + + try { + const port = parseInt(domainsSplit[1]); + if (!port || port < 1024 || port > 65535) { + return false; + } + } catch (e) { + return false; + } + + domainToCheck = domainsSplit[0]; + } + return domainPattern.test(domainToCheck); +} diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/GetCredentialContext.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/GetCredentialContext.js new file mode 100644 index 00000000..a8aad72a --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/GetCredentialContext.js @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { createContext } from 'react'; + +const GetCredentialContext = createContext({}); + +export {GetCredentialContext as default}; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Icons.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Icons.js new file mode 100644 index 00000000..c5a846d6 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Icons.js @@ -0,0 +1,83 @@ +import React from "react"; + +export const CopyIcon = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ) +}; + +export const LinkOut = () => { + return ( + + + + + ) +}; + +export const KeyIcon = () => { + return ( + + + + ) +} + + +export const AlertIcon = () => { + return ( + + + + + ) +} + +export const ArrowDown = () => { + return ( + + + + ) +} + +export const ArrowRight = () => { + return ( + + + + ) +} \ No newline at end of file diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/IllustratedMessage.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/IllustratedMessage.js new file mode 100644 index 00000000..4437c55d --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/IllustratedMessage.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { css } from "@emotion/react"; +import classNames from "classnames"; + +const UnKnownErrorImage = () => { + return ( + + + + ) +} + +const IllustratedMessage = ({ errorMessage }) => { + + return ( +
+ +

UnKnown Error

+ { + <> +

An error has occured when you tried to create a new credential.

+

Please try to submit the form again.

+ +

{errorMessage?.helpLinkText}

+ + } +
+ ) +} + +export { IllustratedMessage , UnKnownErrorImage } diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/JoinBetaProgram.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/JoinBetaProgram.js new file mode 100644 index 00000000..f60d463e --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/JoinBetaProgram.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { css } from "@emotion/react"; +import classNames from "classnames"; +import { MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from './FormFields'; + +const JoinBetaProgram = ({ joinBeta }) => { + + return ( +
+

{joinBeta?.heading}

+

{joinBeta?.text}

+ + + +
+ ) +} + +export { JoinBetaProgram }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Loading.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Loading.js new file mode 100644 index 00000000..5871e3f2 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Loading.js @@ -0,0 +1,65 @@ +import React, { useEffect, useRef } from 'react'; +import { css } from "@emotion/react"; +import { ProgressCircle } from '@adobe/gatsby-theme-aio/src/components/ProgressCircle'; + +const Loading = ({ + credentials, + downloadStatus, + isCreateCredential, + initialLoading +}) => { + const divRef = useRef(null); + useEffect(() => { + if (divRef.current) { + divRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center', + }); + } + }, []) + + return ( + <> + {credentials?.title &&

{credentials?.title}

} +
+ + {initialLoading && +
+ {"Loading..."} +
} +
+ {isCreateCredential && "Creating credentials..."} +
+ {downloadStatus && +
This process may take a few moments. Once complete, your download will start.
+ } +
+ + ) +} + +export { Loading }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/MyCredential.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/MyCredential.js new file mode 100644 index 00000000..7d550dce --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/MyCredential.js @@ -0,0 +1,304 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import classNames from 'classnames'; +import JSZip from 'jszip'; +import JSZipUtils from 'jszip-utils'; +import { saveAs } from 'file-saver'; +import { MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from './FormFields'; +import { Toast } from '@adobe/gatsby-theme-aio/src/components/Toast'; +import { CardProducts } from './Products'; +import { SideComponent } from './SideComponent'; +import { AccessToken } from './Card/AccessToken'; +import { DevConsoleLink } from './Card/DevConsoleLink'; +import { CardClientDetails } from './Card/CardClientDetails'; +import { SideContent } from './Form/SideContent'; +import GetCredentialContext from './GetCredentialContext'; +import { CredentialDetailsCard } from './CredentialDetailsCard'; + +const MyCredential = ({ + formData, + response, + handleRestart +}) => { + const { getCredentialData, selectedOrganization: organizationName } = useContext(GetCredentialContext); + const credentialProps = getCredentialData; + + const [isDownloadStart, setIsDownloadStart] = useState(); + const [isCopiedTooltip, setCopiedTooltip] = useState(''); + + const myCredentialFields = {}; + const productsObj = { label: 'products', productList: [] }; + + credentialProps?.[MyCredential]?.children.forEach(({ type, props }) => { + myCredentialFields[type] = props; + if (props.children && type === CardClientDetails) { + myCredentialFields[type] = props; + props?.children?.forEach(({ type, props }) => { + myCredentialFields[type] = props; + }); + } + if (type === CardProducts && props?.children) { + productsObj.productList.push( + ...[].concat(props.children).map(({ props: { label, icon } }) => ({ label, icon })) + ); + } + }); + const product = productsObj?.productList; + + useEffect(() => { + if (formData['Downloads']) { + downloadZIP( + `/console/api/organizations/${organizationName?.id}/projects/${response.projectId}/workspaces/${response.workspaceId}/download`, + formData['Download'], + formData['zipUrl'] + ); + } + }, []); + + const card = credentialProps?.[MyCredential]; + + const downloadZIP = async (downloadAPI, fileName = 'download', zipFileURL) => { + setIsDownloadStart(true); + try { + const zipData = await JSZipUtils.getBinaryContent(zipFileURL); + const zipArrayBuffer = new Uint8Array(zipData).buffer; + const zip = new JSZip(); + + await zip.loadAsync(zipArrayBuffer); + + const token = window.adobeIMS?.getTokenFromStorage()?.token; + const options = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + token, + 'x-api-key': window?.adobeIMS?.adobeIdData?.client_id, + }, + }; + + const response = await fetch(downloadAPI, options); + + if (response.status === 200) { + const credential = await response.json(); + + zip.file('credential.json', JSON.stringify(credential)); + + const modifiedZipBlob = await zip.generateAsync({ type: 'blob' }); + saveAs(modifiedZipBlob, `${fileName}.zip`); + } else { + console.error('Failed to fetch additional data. Response status:', response.status); + } + } catch (error) { + console.error('An error occurred:', error); + } finally { + setIsDownloadStart(false); + } + }; + + return ( +
+
+
+ {card?.title && ( +

+ {card?.title} +

+ )} + {isDownloadStart && ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ Downloading... +

+
+ )} +
+ {formData['Downloads'] && card?.paragraph && ( +

+ {card?.paragraph} +

+ )} + {formData['Downloads'] && ( +

+ Download not working? + +

+ )} +
+
+
+ +
+

+ Need another credential +

+

+ +

+
+
+ {card?.children ? ( + + ) : null} +
+ {isCopiedTooltip && ( + + )} +
+ ); +}; + +export { MyCredential }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Organization.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Organization.js new file mode 100644 index 00000000..1d82d63c --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Organization.js @@ -0,0 +1,221 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import '@spectrum-css/contextualhelp/dist/index-vars.css'; +import { Picker } from '@adobe/gatsby-theme-aio/src/components/Picker'; +import GetCredentialContext from './GetCredentialContext'; + +const Organization = () => { + + const { allOrganizations, switchOrganization, selectedOrganization } = useContext(GetCredentialContext); + + const [selectedIndex, setSelectedIndex] = useState(allOrganizations.findIndex(org => org.id === selectedOrganization.id)); + const [isOpenDialog, setIsOpenDialog] = useState(false); + + useEffect(() => { + const underlay = document.querySelector('[data-testid="underlay"]'); + if (underlay) { + document.body.style.overflow = 'hidden'; + } + return () => { + document.body.style.overflow = ''; + }; + }, [isOpenDialog]); + + const close = () => { + setIsOpenDialog(false) + } + + return ( + <> + setIsOpenDialog(true)}>Change organization + {isOpenDialog && +
+ + } + + ); +}; + +export { Organization }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/PreviousCredential.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/PreviousCredential.js new file mode 100644 index 00000000..48ff3e7d --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/PreviousCredential.js @@ -0,0 +1,156 @@ +import React, { useContext, useState } from 'react'; +import { css } from '@emotion/react'; +import '@spectrum-css/contextualhelp/dist/index-vars.css'; +import classNames from 'classnames'; +import { MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from './FormFields'; +import { PreviousProject } from './PreviousProject'; +import { Organization } from './Organization'; +import { CredentialForm } from './CredentialForm'; +import { ReturnSideComp } from './Return/ReturnSideComp'; +import { CardProducts } from './Products'; +import { ReturnCustomComp } from './Return/ReturnCustomComp'; +import { ReturnNewCredential } from './Return/ReturnNewCredential'; +import GetCredentialContext from './GetCredentialContext'; +import { ReturnCredentialDetails } from './Return/ReturnCredentialDetails'; + +const PreviousCredential = ({ setIsCreateNewCredential }) => { + const { getCredentialData } = useContext(GetCredentialContext); + const returnProps = getCredentialData; + + const [isShow, setIsShow] = useState(false); + const credentialHeader = returnProps[CredentialForm]; + const { selectedOrganization } = useContext(GetCredentialContext); + + const returnFields = {}; + const productList = []; + + returnProps?.[PreviousProject]?.children.forEach(({ type, props }) => { + returnFields[type] = props; + + if (props?.children) { + + const children = Array.isArray(props.children) ? props.children : [props.children]; + + children.forEach(({ props: childProps, type: childType }) => { + if (type === ReturnSideComp || type === ReturnCredentialDetails) { + returnFields[childType] = childProps; + } else if (type === CardProducts) { + const { label, icon } = childProps; + productList.push({ label, icon }); + } + + }); + } + + }); + + const returnSideComp = returnFields?.[ReturnSideComp]; + const returnCustomComp = returnFields?.[ReturnCustomComp]; + const returnNewCredential = returnFields?.[ReturnNewCredential]; + + return ( + <> +
+
+
+ {credentialHeader?.title && ( +

+ {credentialHeader?.title} +

+ )} + {credentialHeader?.paragraph && ( +

{credentialHeader?.paragraph}

+ )} +

setIsShow(true)} + css={css` + color: var(--spectrum-global-color-gray-800); + display: inline-flex; + `}> + {selectedOrganization.type === "developer" ? + "You’re viewing in your personal developer organization" : + <>You’re viewing in [ {selectedOrganization?.name} ] .} + +

+
+
+
+
+ {returnSideComp && ( + + )} +
+ +
+
+ +
+
+
+ + ); +}; + +export { PreviousCredential }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/PreviousProject.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/PreviousProject.js new file mode 100644 index 00000000..291fe73c --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/PreviousProject.js @@ -0,0 +1,76 @@ +import React, { useContext, useState } from 'react'; +import { css } from '@emotion/react'; +import '@spectrum-css/contextualhelp/dist/index-vars.css'; +import { Toast } from '@adobe/gatsby-theme-aio/src/components/Toast'; +import classNames from 'classnames'; +import { ReturnAccessToken } from './Return/ReturnAccessToken'; +import { ProjectsDropdown } from './Return/ProjectsDropdown'; +import { ReturnDevConsoleLink } from './Return/ReturnDevConsoleLink'; +import { ReturnManageDeveloperConsole } from './Return/ReturnManageDeveloperConsole'; +import { ReturnCredentialDetails } from './Return/ReturnCredentialDetails'; +import { CardProducts } from './Products'; +import GetCredentialContext from './GetCredentialContext'; +import { CredentialDetailsCard } from './CredentialDetailsCard'; + +const PreviousProject = ({ returnFields, productList, collapse }) => { + + const { getCredentialData: returnProps, selectedOrganization, previousProjectDetail } = useContext(GetCredentialContext); + + const [selectedIndex, setSelectedIndex] = useState(0); + const [isCopiedTooltip, setCopiedTooltip] = useState(''); + + const previousProjectsDetails = previousProjectDetail?.projects + const previousProject = returnProps?.[PreviousProject]; + const projectsDropdown = returnFields?.[ProjectsDropdown]; + const returnManageDeveloperConsole = returnFields?.[ReturnManageDeveloperConsole]; + const response = previousProjectsDetails?.[selectedIndex]; + const projectDetails = previousProjectsDetails?.[selectedIndex]; + const manageProps = returnProps[PreviousProject]; + + const allowedDomains = projectDetails?.workspaces[0]?.credentials[0]?.metadata?.["adobeid.domain"]; + + return ( + <> +
+ {previousProject?.title &&

{previousProject?.title}

} + + {previousProject?.paragraph &&

{previousProject?.paragraph}

} + + {returnManageDeveloperConsole && } + + {projectsDropdown && } + + {/* ----------- credential form ------------ */} + + + +
+ {isCopiedTooltip && } + + + ) +}; + +export { PreviousProject }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/Products.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/Products.js new file mode 100644 index 00000000..6b2af567 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/Products.js @@ -0,0 +1,241 @@ +import React, { useRef, useState, useEffect } from 'react'; +import { css } from '@emotion/react'; +import { ActionButton } from "@adobe/gatsby-theme-aio/src/components/ActionButton" + +const Products = ({ products, product }) => { + return ( +
+
+ {products?.label && ( + + )} + +
+
+ ); +}; + +const Product = ({ productList }) => { + return ; +}; + +const CardProduct = ({ productList }) => { + return ( + <> + {productList?.map((product, index) => { + if (index < 2) + return ( +
+ {product?.icon ? ( +
+ + {product?.label} +
+ ) : ( +

{product?.label}

+ )} +
+ ); + })} + + ); +}; + +const CardProducts = ({ productList }) => { + + return ( +
+ + +
+ ); +}; + +const CommonProduct = ({ productList }) => { + + return ( + <> + {productList && + productList?.map((data, index) => { + if (index < 2) + return ( +
+ + +
+ ); + })} + + {productList?.length > 2 && ( + + )} + + ); +}; + +const ModelTrigger = ({ productList }) => { + + const [isVisible, setisVisible] = useState(false); + const buttonRef = useRef(); + + const handleClickOutside = e => { + if (buttonRef?.current?.contains(e.target)) setisVisible(!isVisible); + else setisVisible(false); + }; + + useEffect(() => { + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }); + + return ( + <> +
button, & > button : active { + border: none; + background: transparent !important; + } + `}> + + {productList.length - 2 > 0 && ( +
+ +{productList.length - 2} more +
+ )} +
+ {isVisible &&
+
+
+ +
+
+
} +
+ + ) +} + +export { Product, Products, CardProducts, CardProduct }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetails.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetails.js new file mode 100644 index 00000000..ebed8a69 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetails.js @@ -0,0 +1,165 @@ +import React, { useContext, useEffect, useRef, useState } from 'react'; +import '@spectrum-css/contextualhelp/dist/index-vars.css'; +import { css } from '@emotion/react'; +import { Button } from "@adobe/gatsby-theme-aio/src/components/Button"; +import { RequestAccessModal } from './RequestAccessModal'; +import GetCredentialContext from '../GetCredentialContext'; +import { Organization } from '../Organization'; +import { Products } from '../Products'; + +const OrganizationAccessDetails = ({ restrictedAccess, products }) => { + + const { template, selectedOrganization } = useContext(GetCredentialContext); + const [isOpen, setIsOpen] = useState(false); + + let productList = []; + + if (Array.isArray(products?.children)) { + productList = products?.children.map((child) => (child?.props)) + } else { + productList.push(products?.children?.props); + } + + let product = { productList }; + + const handleClose = () => { + setIsOpen(!isOpen) + } + + const [isVisible, setisVisible] = useState(false); + const buttonRef = useRef(); + + const handleClickOutside = e => { + if (buttonRef?.current?.contains(e.target)) setisVisible(!isVisible); + else setisVisible(false); + }; + + useEffect(() => { + document.addEventListener('click', handleClickOutside); + return () => document.removeEventListener('click', handleClickOutside); + }); + + return ( + <> +
+ {restrictedAccess?.title && ( +
{restrictedAccess?.title}
+ )} +

+ You’re creating this credential in {' '} + {selectedOrganization.type === "developer" ? + "your personal developer organization" : + <> + [ + + {selectedOrganization.name} + + ] + } but you do not have a developer access in this organization and need admin approval to + use this API. + span { + color: #000000; + } + + &>span: hover { + color: #000000; + } + + & > div { + display: inline; + } + + span { + padding: 0px 0px 0px 3px !important; + } + `}> + + +

+ {products && } + {restrictedAccess?.buttonLabel && ( + template.requestAccessAppId &&
+
+
+ {!template.isRequestPending ?
: +
+

Request Pending

+
button { + border: none; + padding: 4px !important; + border-radius: 2px !important; + } + `} + > + +
+

Your request is pending approval

+

You'll hear back from your admin soon. If your request is approved, you'll get an email with instructions on how to start using your apps and service + Learn more about requesting Adobe apps.

+
+
+
+
+ } +
+ {isOpen && } +
+
+ )} +
+ + ); +}; + +export { OrganizationAccessDetails }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsEdgeCase.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsEdgeCase.js new file mode 100644 index 00000000..275c2093 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsEdgeCase.js @@ -0,0 +1,131 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import { Button } from "@adobe/gatsby-theme-aio/src/components/Button"; +import { Organization } from '../Organization'; + +import GetCredentialContext from '../GetCredentialContext'; +import Context from '@adobe/gatsby-theme-aio/src/components/Context'; + +const OrganizationAccessDetailsEdgeCase = ({ content, isNoProduct, productName }) => { + const { selectedOrganization } = useContext(GetCredentialContext); + const { ims } = useContext(Context); + + const [emailID, setEmailID] = useState(''); + useEffect(() => { + (async () => { + if (ims && ims.isSignedInUser()) { + const profile = await ims.getProfile(); + setEmailID(profile?.email); + } + })(); + }, [ims]); + + return ( + <> +
+ {content?.title && +
+ {content?.title} +
} + {isNoProduct ? +

+ You are currently signed in with [ + {emailID}] + in {" "} + {selectedOrganization.type === "developer" ? + "your personal developer organization" : + <>organization [ + + {selectedOrganization.name} + + ] + } + currently does not have access to these APIs Contact us to learn more about {productName} APIs and how to get a free trial. + span, &>span: hover { + color: #000000; + } + + & > div { + display: inline; + } + + span { + padding: 0px 0px 0px 3px !important; + } + `}> + + +

+ : +

+ You are currently signed in with [ + {emailID}] + in {" "} + {selectedOrganization.type === "developer" ? + "your personal developer organization" : + <> organization [ + {selectedOrganization.name}] + } + and can not access {productName} APIs. + span, &>span: hover { + color: #000000; + } + + & > div { + display: inline; + } + + span { + padding: 0px 0px 0px 3px !important; + } + `}> + + +

+ } +
span { + color: #292929; + } + `}> + {content?.buttonLink && {content?.buttonLabel && }} +
+
+ + ); +}; + +export { OrganizationAccessDetailsEdgeCase }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNoProduct.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNoProduct.js new file mode 100644 index 00000000..ff032935 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNoProduct.js @@ -0,0 +1,13 @@ +import React from "react"; +import { OrganizationAccessDetailsEdgeCase } from "./OrganizationAccessDetailsEdgeCase"; + +const OrganizationAccessDetailsNoProduct = ({ content, productName }) => { + + return ( + <> + + + ); +}; + +export { OrganizationAccessDetailsNoProduct }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotMember.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotMember.js new file mode 100644 index 00000000..46831b8d --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotMember.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { OrganizationAccessDetailsEdgeCase } from './OrganizationAccessDetailsEdgeCase'; + +const OrganizationAccessDetailsNotMember = ({content , productName}) => { + + return ( + <> + + + ); +}; + +export { OrganizationAccessDetailsNotMember }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotSignUp.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotSignUp.js new file mode 100644 index 00000000..9d0df0cd --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotSignUp.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { OrganizationAccessDetailsEdgeCase } from './OrganizationAccessDetailsEdgeCase'; + +const OrganizationAccessDetailsNotSignUp = ({ content, productName }) => { + return ( + <> + + + ); +}; + +export { OrganizationAccessDetailsNotSignUp }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsType1User.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsType1User.js new file mode 100644 index 00000000..b614a83a --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/OrganizationAccessDetailsType1User.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { OrganizationAccessDetailsEdgeCase } from './OrganizationAccessDetailsEdgeCase'; + +const OrganizationAccessDetailsType1User = ({content,productName}) => { + return ( + <> + + + ); +}; + +export { OrganizationAccessDetailsType1User }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/RequestAccess.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/RequestAccess.js new file mode 100644 index 00000000..6c316bc3 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/RequestAccess.js @@ -0,0 +1,77 @@ +import React, { useContext } from 'react'; +import { RestrictedAccess } from './RestrictedAccessFields'; +import { css } from '@emotion/react'; +import { MAX_MOBILE_WIDTH } from '../FormFields'; +import '@spectrum-css/contextualhelp/dist/index-vars.css'; +import { RequestAccessSide } from './RequestAccessSide'; +import GetCredentialContext from '../GetCredentialContext'; +import { OrganizationAccessDetailsEdgeCase } from './OrganizationAccessDetailsEdgeCase'; + +const RequestAccess = ({ productName }) => { + const { getCredentialData } = useContext(GetCredentialContext); + + const requestAccess = getCredentialData?.[RequestAccess]; + let side, restrictedAccess, organizationAccessDetailsEdgeCase; + + if (Array.isArray(requestAccess?.children)) { + requestAccess?.children?.forEach(({ type, props }) => { + if (type === RequestAccessSide) { + side = props?.children; + } + if (type === RestrictedAccess) { + restrictedAccess = props; + } + if (type === OrganizationAccessDetailsEdgeCase) { + organizationAccessDetailsEdgeCase = props; + } + }); + } else { + restrictedAccess = requestAccess?.children?.props; + } + + return ( + <> +
+
+ {requestAccess?.title && ( +

{requestAccess?.title}

+ )} + {requestAccess?.paragraph && ( +

+ {requestAccess?.paragraph} +

+ )} +
+
+ {restrictedAccess && } + {side && } +
+
+ + ); +}; + +export { RequestAccess }; diff --git a/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/RequestAccessModal.js b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/RequestAccessModal.js new file mode 100644 index 00000000..bb394ef3 --- /dev/null +++ b/src/@adobe/gatsby-aio-theme/components/GetCredential/RequestAccess/RequestAccessModal.js @@ -0,0 +1,139 @@ +import React, { useCallback, useContext, useEffect, useState } from "react"; +import "@spectrum-css/contextualhelp/dist/index-vars.css"; +import { ProgressCircle } from "@adobe/gatsby-theme-aio/src/components/ProgressCircle"; +import GetCredentialContext from "../GetCredentialContext"; +import { css } from '@emotion/react'; + +const INITIAL_IFRAME_HEIGHT = 420; + +const RequestAccessModal = ({ accessPlatformAppId, close }) => { + const [targetUrl, setTargetUrl] = useState(''); + const [loading, setLoading] = useState(true); + const [iframeHeight, setIframeHeight] = useState(INITIAL_IFRAME_HEIGHT); + const { setTemplate, template, selectedOrganization } = useContext(GetCredentialContext); + + useEffect(() => { + const setTargetUrlHelper = async () => { + const { userId } = await window.adobeIMS.getProfile(); + const acrsHostPrefix = window.adobeIMS.adobeIdData.environment === 'prod' ? '' : 'stage.'; + setTargetUrl(`https://${acrsHostPrefix}acrs.adobe.com/requestAccess?flow=frame&colorScheme=light&applicationId=UDPWeb1&appId=${accessPlatformAppId}&userId=${userId}&accessRequestType=apiAccess`); + }; + setTargetUrlHelper(); + }, [setTargetUrl, accessPlatformAppId]); + + const handleIframeMessage = useCallback( + (event) => { + if (!event.isTrusted || (typeof event.data !== 'string' && !(event.data instanceof String))) { + return; + } + const eventData = JSON.parse(event.data); + + if (!eventData || eventData.app !== 'acrs-request-access' || eventData.type !== 'System') { + return; + } + + switch (eventData.subType) { + case 'AppLoaded': + setLoading(false); + break; + case 'Resize': + setIframeHeight(eventData.data.height); + break; + case 'Close': + close(false); + fetchTemplate(); + break; + } + }, + [close, setLoading, setIframeHeight], + ); + + const fetchTemplate = async () => { + try { + const url = `/templates/${template.id}` + const response = await fetch(url, { + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${window.adobeIMS.getTokenFromStorage().token}`, + "x-api-key": window.adobeIMS.adobeIdData.client_id, + "x-org-id": selectedOrganization.code + } + }); + + if (!response.ok) { + console.error('Template not found. Please check template id'); + return; + } + setTemplate(await response.json()); + } + catch (error) { + console.error('Error fetching template:', error); + } + } + + useEffect(() => { + window.addEventListener('message', handleIframeMessage); + return () => window.removeEventListener('message', handleIframeMessage); + }, [handleIframeMessage]); + + return ( +
.changeOrg { + border: none; + background: white; + padding: 0; + height: fit-content; + text-decoration: underline; + cursor: pointer; + } + `}> +