diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index f2bcf7d41b..7165ef7423 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -18,17 +18,31 @@ on: - "cypress/**" jobs: smoke_tests: - runs-on: ubuntu-latest + container: + image: cypress/browsers:node-20.14.0-chrome-126.0.6478.114-1-ff-127.0.1-edge-126.0.2592.61-1 + options: --user root + runs-on: self-hosted steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + + - name: 'Create env file' + run: | + touch ./example/.env + echo GATSBY_IMS_SRC="https://auth-stg1.services.adobe.com/imslib/imslib.min.js" >> ./example/.env + echo GATSBY_IMS_CONFIG='{"client_id": "stage_adobe_io", "scope": "AdobeID,openid,unified_dev_portal,read_organizations,additional_info.projectedProductContext,additional_info.roles,gnav,read_pc.dma_bullseye,creative_sdk,adobeio_api,service_principals.read,service_principals.write,read_client_secret", "environment": "stg1"}' >> ./example/.env + cat ./example/.env - name: Run Tests uses: cypress-io/github-action@v5 with: - build: yarn build - start: yarn serve - wait-on: 'http://[::1]:9000' + start: yarn dev:https:ci + wait-on: 'https://localhost.corp.adobe.com:9000' + wait-on-timeout: 150 browser: chrome config-file: cypress.config.js - spec: cypress/e2e/smoke.cy.js + spec: | + cypress/e2e/smoke.cy.js + cypress/e2e/get-credentials.cy.js + env: + NODE_TLS_REJECT_UNAUTHORIZED: '0' diff --git a/README.md b/README.md index b4b39ee69f..31763306b3 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,23 @@ Using a theme, all of your default configuration lives in an npm package. - [Writing Enhanced Markdown](#writing-enhanced-markdown) - [Metadata with Front matter](#metadata-with-front-matter) - [OpenAPI](#openapi) - - [Redocly API Block](#Redocly API Block) + - [Redocly API Block](#RedoclyAPIBlock) + - [On-premise license keys](#on-premise-license-keys) + - [Local development limitation and workaround](#local-development-limitation-and-workaround) + - [Full width page](#full-width-page) + - [Displaying long API response descriptions](#displaying-long-api-response-descriptions) + - [width (optional)](#width-optional) + - [typography (optional)](#typography-optional) + - [codeblock (optional)](#codeblock-optional) + - [disableSidebar (optional)](#disablesidebar-optional) + - [disableSearch (optional)](#disablesearch-optional) + - [hideTryItPanel (optional)](#hidetryitpanel-optional) + - [scrollYOffset (optional)](#scrollyoffset-optional) + - [sortOperationsAlphabetically (optional)](#sortoperationsalphabetically-optional) + - [sortTagsAlphabetically (optional)](#sorttagsalphabetically-optional) + - [jsonSampleExpandLevel (optional)](#jsonsampleexpandlevel-optional) + - [generateCodeSamples (optional)](#generatecodesamples-optional) + - [requestInterceptor (optional)](#requestinterceptor-optional) - [JSDoc](#jsdoc) - [MDX](#mdx) - [Modular Content System](#modular-content-system) @@ -121,6 +137,7 @@ Using a theme, all of your default configuration lives in an npm package. - [Table Block](#table-block) - [Tabs Block](#tabs-block) - [Product Card](#product-card) + - [Mini Product Card](#product-card) - [Product Card Grid](#product-card-grid) - [Resource Card](#resource-card) - [MiniResourceCard](#miniresourcecard) @@ -1076,11 +1093,195 @@ We use [Redoc](https://github.com/Redocly/redoc) to render OpenAPI specs. Simply -### Redocly API Block +### RedoclyAPIBlock -We can now host your own open api yaml files and be rendered by Redocly documents. This way can avoid to iframe in and host our own api yaml files in Redocly. +```js + +``` + +We can now host your own OpenAPI YAML files and have them rendered by Redocly documents. This approach allows us to avoid using iframes and instead host our own API YAML files directly in Redocly. + +#### On-premise license keys + +When using RedoclyAPIBlock, ensure that GATSBY_REDOCLY_KEY: ${{ secrets.REDOCLY_LICENSE_KEY }} is added to the deploy.yml file in your repository (e.g. add to [build-dev](https://github.com/AdobeDocs/ff-services-docs/blob/24b9b522e7521d7169265871023cccfa1f03f349/.github/workflows/deploy.yml#L145) and [build-production](https://github.com/AdobeDocs/ff-services-docs/blob/24b9b522e7521d7169265871023cccfa1f03f349/.github/workflows/deploy.yml#L252)). Any public repo in the AdobeDocs organization will have access to this development environment [secret](https://github.com/organizations/AdobeDocs/settings/secrets/actions). + +Dev-site team is responsible for making sure the license key is up to date, which can be found in redocly account settings -> On-premise license keys. + +#### Local development limitation and workaround + +Due to the way Redocly API works, it’s not possible to test RedoclyAPIBlock locally: +![redocly-api-block-on-local](docs/images/redocly-api-block-on-local.png) + +To test, temporarily deploy to stage: +![redocly-api-block-on-stage](docs/images/redocly-api-block-on-stage.png) + +#### Full width page + +Use [custom layout](#custom-layout) to do a full width page + +#### Displaying long API response descriptions + +By default, Redocly displays API response descriptions as the button text (styled as one line, no-wrap). If too long, the text could get truncated, and the page widens beyond 100%: +![redocly-api-block-long-description-without-x-summary](docs/images/redocly-api-block-long-description-without-x-summary.png) +![redocly-api-block-long-description-without-x-summary-yaml](docs/images/redocly-api-block-long-description-without-x-summary-yaml.png) + +For long descriptions, Redocly suggests to use the [x-summary](https://redocly.com/docs/api-reference-docs/specification-extensions/x-summary/) specification extension. If specified, it is used as the response button text, and the description is rendered under the button: +![redocly-api-block-long-description-with-x-summary](docs/images/redocly-api-block-long-description-with-x-summary.png) +![redocly-api-block-long-description-with-x-summary-yaml](docs/images/redocly-api-block-long-description-with-x-summary-yaml.png) + +#### width (optional) + +```js + +``` +Sets the width (of the right panel). + +Defaults to ```'500px'``` + +https://redocly.com/docs/api-reference-docs/configuration/theming/#path=rightPanel + +#### typography (optional) + +```js + +``` + +Controls the appearance of text. + +Defaults to ```'fontFamily: `adobe-clean, "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Trebuchet MS", "Lucida Grande", sans-serif`'``` + +https://redocly.com/docs/api-reference-docs/configuration/theming/#path=typography + +#### codeblock (optional) + +```js + +``` + +Controls the appearance of code snippets. + +Defaults to ```"tokens: { punctuation: { color: 'white' }}"``` + +https://redocly.com/docs/api-reference-docs/configuration/theming/#path=codeBlock + +#### disableSidebar (optional) + +```js + +``` + +If set to `true`, hides the navigation sidebar (the left panel). Setting this option to `false` does not disable the search feature. + +Defaults to ```false``` + +https://redocly.com/docs/api-reference-docs/configuration/functionality/#theme-object-openapi-schema + +#### disableSearch (optional) + +```js + +``` + +Disables search indexing and hides the search box from the API documentation page. + +Defaults to ```false``` + +https://redocly.com/docs/api-reference-docs/configuration/functionality/#theme-object-openapi-schema + +#### hideTryItPanel (optional) + +```js + +``` + +Disables the Try it console in the right panel. + +Defaults to ```false``` + +https://redocly.com/docs/api-reference-docs/configuration/functionality/#theme-object-openapi-schema + +#### scrollYOffset (optional) + +```js + +``` + +Specifies a vertical scroll-offset. This is useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc. You can specify the scrollYOffset value as a number - a fixed number of pixels to be used as the offset. + +Defaults to ```0``` -When using this block, GATSBY_REDOCLY_KEY : ${{ secrets.REDOCLY_LICENSE_KEY }} will needs to be added in deploy.yml file in the repo. +https://redocly.com/docs/api-reference-docs/configuration/functionality/#theme-object-openapi-schema + + +#### sortOperationsAlphabetically (optional) + +```js + +``` + +When set to `true`, sorts operations in the navigation sidebar and in the middle panel alphabetically.˙ + +Defaults to ```false``` + +https://redocly.com/docs/api-reference-docs/configuration/functionality/#theme-object-openapi-schema + +#### sortTagsAlphabetically (optional) + +```js + +``` + +When set to `true`, sorts tags in the navigation sidebar and in the middle panel alphabetically. Note that only tags will be sorted alphabetically in the middle panel, not the operations associated with each tag. To sort operations alphabetically as well, you must set `sortOperationsAlphabetically` to `true`. + +Defaults to ```false``` + +https://redocly.com/docs/api-reference-docs/configuration/functionality/#theme-object-openapi-schema + +#### jsonSampleExpandLevel (optional) + +```js + +``` + +Sets the default expand level for JSON payload samples (response and request body). Takes a number >= 1, or the string 'all'. + +Defaults to ```2``` + +https://redocly.com/docs/api-reference-docs/configuration/functionality/#theme-object-openapi-schema + +#### generateCodeSamples (optional) + +```js + +``` + +Controls options for generating code samples, including code sample languages. + +Defaults to ```languages: [], skipOptionalParameters: false``` + +https://redocly.com/docs/api-reference-docs/configuration/functionality#theme-object-openapi-schema + +#### requestInterceptor (optional) + +```js + + +![Forge the path to customer success](./S_AniConvertToGIF.png) + +# Firefly API + +Firefly API + +- [Learn more](https://developer-stage.adobe.com/) + +``` + +Use `slots` to identify the markdown content: + +- `image`(required) +- `heading`(required) +- `text`(optional) +- `buttons`(optional) + +Use `repeat` to define how many code sections are part. + + ### Edition The Edition component is used to display the edition of the product. @@ -2534,6 +2767,8 @@ as a dependency in another site, you can run the command `npm install --save ado Your site package will show up under `node_modules/[PACKAGE_NAME]` e.g. `node_modules/dev-site-documentation-template`. +When pointing to a github repo as a dependency and wanting to get the latest change from that dependency, you have to ensure that your package.json is pointing to latest version of that dependecy. Otherwise, if you have already pulled down that dependency and it exists in your `node_modules/`, it won't grab the latest changes. To get around this, simply delete your `node_modules/` and then `yarn install` again. + See full example below using a Variant block. #### Filtering content with Variant Blocks diff --git a/cypress.config.js b/cypress.config.js index a34e9572d8..dcccecd614 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -1,15 +1,21 @@ -const { defineConfig } = require('cypress') - -module.exports = defineConfig({ - viewportHeight: 900, - viewportWidth: 1440, - e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - return require('./cypress/plugins/index.js')(on, config) - }, - baseUrl: 'http://localhost:9000', - specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', - }, -}) +const { defineConfig } = require('cypress') + +module.exports = defineConfig({ + viewportHeight: 900, + viewportWidth: 1440, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require('./cypress/plugins/index.js')(on, config) + }, + baseUrl: 'https://localhost.corp.adobe.com:9000', + specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', + }, + pageLoadTimeout: 500000, + userEmail: 'cypress1@adobe.com', + userPassword: 'ScotchB33r&1234567', + defaultCommandTimeout: 60000, + video: false, + chromeWebSecurity: false +}) diff --git a/cypress/e2e/get-credentials.cy.js b/cypress/e2e/get-credentials.cy.js new file mode 100644 index 0000000000..e6da4b28ee --- /dev/null +++ b/cypress/e2e/get-credentials.cy.js @@ -0,0 +1,199 @@ +const API_KEY = 'apikey'; +const OAUTH_S2S = 'oauths2s'; + +function init(route) { + cy.visit(route).assertRoute(route); + cy.expect('button[data-cy="sign-in-btn"]').to.exist; + cy.get('button[data-cy="sign-in-btn"]').should('be.visible'); + cy.get('button[data-cy="sign-in-btn"]').should('be.enabled'); + cy.get('button[data-cy="sign-in-btn"]').click(); + cy.login(); + cy.get('button[data-cy="sign-in-btn"]').should('not.exist'); + cy.assertRoute(route + '#'); +} + +function checkRequestAccessEdgeCase() { + cy.get('[data-cy="accessDetails-edgeCase-btn"]').should('be.visible'); +}; + +function getIframeBody() { + return cy.get('iframe[data-cy="request-access-iframe"]') + .its('0.contentDocument.body').should('not.be.empty') + .then(cy.wrap); +} + +function checkRequestAccess() { + cy.get('body').then(($body) => { + // if the request is not already sent, send the request + if ($body.find('[data-cy="request-access-button"]').length > 0) { + cy.get('[data-cy="request-access-button"]').click(); + + getIframeBody().find('h3[data-testid="title"]').invoke('text').then((text) => { + if (text.trim() !== 'Your request is on its way') { + getIframeBody().find('button[data-testid="send-request-button"]').should('exist').click(); + } + }); + getIframeBody().find('button[data-testid="close-button"]').should('exist').click(); + } + }); +}; + +function checkReturnFlow(credentialType) { + // verify return flow is visible + cy.get('[data-cy="return-flow"]').should('be.visible'); + + // verify clicking on create new credential button opens the form + returnToForm(); + + // verify clicking on cancel button closes the form + cy.get('[data-cy="cancel-new-credential"]').click(); + // cy.get('[data-cy="return-flow"]').should('be.visible'); + cy.get('body').then(($body) => { + if ($body.find('[data-cy="return-flow"]').length) { + cy.get('[data-cy="return-flow"]').should('be.visible'); + } + }); + + // verify clicking on manage projects console button exists + cy.get('[data-cy="manage-projects-console"]').should('exist'); + + // ensure project picker is visible + cy.get('button[data-cy="projects-picker"]').should('be.visible').should('be.enabled'); + + // verify all the information is visible based on credential type + checkCredential(credentialType); +}; + +function checkAPIKey() { + + // Check if the collapse open button is present and click it if it is + cy.get('body').then(($body) => { + if ($body.find('[data-cy="collapse-open"]').length) { + cy.get('[data-cy="collapse-open"]').click(); + } + }); + + // verify API key is visible + cy.contains('API Key').should('be.visible'); + + // verify API key copy button is clickable + cy.get('[data-cy="API Key-copyIcon"]').should('be.visible'); + + // verify allowed domains copy button is clickable + cy.get('[data-cy="Allowed domains-copyIcon"]').should('be.visible'); + + // verify IMS Organization ID copy button is clickable + cy.get('[data-cy="IMS Organization ID-copyIcon"]').should('exist'); + +} + +function checkOAuthS2S() { + + // Check if the collapse open button is present and click it if it is + cy.get('body').then(($body) => { + if ($body.find('[data-cy="collapse-open"]').length) { + cy.get('[data-cy="collapse-open"]').click(); + } + }); + + cy.get('body').then(($body) => { + if ($body.find('[data-cy="generate-token"]').length) { + cy.get('[data-cy="generate-token"]').should('be.visible'); + } + }); + // cy.get('button[data-cy="copy-token"]').should('exist'); + cy.get('[data-cy="credentialName-link"]').should('exist'); + cy.get('[data-cy="ClientId-copyIcon"]').should('exist'); + cy.get('[data-cy="retrieve-client-secret"]').should('be.visible'); + // cy.get('button[data-cy="copy-client-secret"]').should('exist'); + cy.get('[data-cy="Scopes-copyIcon"]').should('exist'); + cy.get('[data-cy="IMS Organization ID-copyIcon"]').should('exist'); + cy.contains('openid, AdobeID, read_organizations, firefly_api, ff_apis').should('exist'); +} + +function checkCredential(credentialType) { + switch (credentialType) { + case API_KEY: + checkAPIKey(); + break; + case OAUTH_S2S: + checkOAuthS2S(); + break; + } + + cy.get('[data-cy="next-step-button"]').should('exist'); + if (credentialType !== API_KEY) { + cy.get('[data-cy="Manage-Dev-Console-link"]').should('exist'); + } +}; + +function addCredential(credentialType) { + const credentialName = `CypressTest${credentialType}${Math.floor(Math.random() * 1000)}`; + cy.get('[data-cy="add-credential-name"]').click().should('have.focus'); + cy.get('[data-cy="add-credential-name"]').type(credentialName).should('have.value', credentialName); + if (credentialType === API_KEY) { + cy.get('[data-cy="add-allowed-origins"]').click().should('have.focus'); + cy.get('[data-cy="add-allowed-origins"]').type('localhost:9000').should('have.value', 'localhost:9000'); + cy.get('[data-cy="download-checkBox"]').check().should('be.checked'); + cy.get('[data-cy="select-download-language"]').click(); + cy.get('ul p').contains('JavaScript').click(); + } + cy.get('[data-cy="terms-condition-link"]').should('be.visible'); + cy.get('[data-cy="update-terms-condition"]').check().should('be.checked'); + cy.get('[data-cy="create-credential-btn"]').should('be.visible'); + cy.get('[data-cy="create-credential-btn"]').should('be.enabled'); + cy.get('[data-cy="create-credential-btn"]').click(); + if (credentialType === API_KEY) { + cy.get('body').then(($body) => { + if ($body.find('button[data-cy="restart-download"]').length) { + cy.get('button[data-cy="restart-download"]').should('be.visible').should('be.enabled').click(); + } + }); + } + checkCredential(credentialType); + cy.get('[data-cy="Restart-new-credential"]').click(); +}; + +function waitForLoader() { + cy.get('div[data-cy="loader"]').should('exist'); + cy.get('div[data-cy="loader"]').should('not.exist'); + +} + +function selectOrganization(orgName) { + cy.get('span[data-cy="change-organization-btn"]').should('be.visible').click(); + cy.get('button[data-cy="organization-picker"]').should('be.visible').should('be.enabled').click(); + cy.contains(orgName).should('exist').click(); + cy.get('button[data-cy="submit-change-organization"]').should('be.visible').should('be.enabled').click(); + waitForLoader(); +}; + +function returnToForm() { + cy.get('[data-cy="create-new-credential"]').should('be.visible'); + cy.get('[data-cy="create-new-credential"]').click(); + cy.get('[data-cy="credential-form"]').should('be.visible'); +} + +describe('Get Credentials Test', () => { + it('API Key credential', () => { + init('/getCredential/'); + checkReturnFlow(API_KEY); + selectOrganization('AdobeIOTestingOrg'); + checkReturnFlow(API_KEY); + // return to the form + returnToForm(); + addCredential(API_KEY); + }); + + it('OAuth S2S credential', () => { + init('/get-credential-oauth/'); + checkRequestAccessEdgeCase(); + selectOrganization('Romans entp org'); + checkRequestAccess(); + selectOrganization('MAC New Feature Testing'); + checkReturnFlow(OAUTH_S2S); + // return to the form + returnToForm(); + addCredential(OAUTH_S2S); + }); +}); \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 81279abd7c..e7b7c86fcf 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -1,29 +1,134 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add("login", (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) - -Cypress.Commands.add(`assertRoute`, (route) => { - cy.url().should(`equal`, `${window.location.origin}${route}`); -}); +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) +const baseUrl = Cypress.config('baseUrl'); + +Cypress.Commands.add(`assertRoute`, (route) => { + cy.url().should(`equal`, `${window.location.origin}${route}`); +}); + +Cypress.Commands.add('login', () => { + // wait for page to settle + cy.wait(5000); + + cy.get('body').then(($body) => { + if ($body.find("[data-id='EmailPage-EmailField']").length) { + cy.setCredentials(); + } + }); +}); + +/** + * @description set credentials on the login page code taken from + * https://git.corp.adobe.com/pixel/cypress-acrites/blob/c398947b29f8315c611ae0438afe5f4e3e2b1d88/plugins/e2e-core-commands/src/commands/login.js#L52 + * @method setCredentials + * @param {String} login login email used to login + * @param {String} password password used to login + */ +Cypress.Commands.add('setCredentials', () => { + const login = Cypress.config().userEmail; + const password = Cypress.config().userPassword; + // set login value + cy.get("[data-id='EmailPage-EmailField']").should('be.visible').should('be.enabled').type(login); + + // click to continue + cy.get("[data-id='EmailPage-ContinueButton']").should('be.visible').should('be.enabled').click(); + + // wait to pass the login step + cy.get('.EmailPage').should('not.exist'); + + // case `Select an account` page + cy.get('body').then(($body) => { + if ($body.find('.IdentitiesPage').length) { + // we have the UI to select type of account + cy.get('.IdentitiesPage').should('be.visible'); + + // click to select personal account + cy.get("[data-id='AccountChooser-AccountList-individual']").click(); + } + }); + + // set password value + cy.get("[data-id='PasswordPage-PasswordField']").should('be.visible').should('be.enabled').type(password); + + // click to continue + cy.get("[data-id='PasswordPage-ContinueButton']").should('be.visible').should('be.enabled').click(); + + // wait to pass the password step + cy.get('.PasswordPage').should('not.exist'); + + // case `Introducing profile selection` page + cy.get('body').then(($body) => { + if ($body.find('[data-id="PP-T2E-ProfilesSetup-Introduction"]').length) { + cy.get('[data-id="PP-T2E-ProfilesSetup-Introduction-ContinueButton"]').should('be.visible').click(); + } + }); + + // case `Entreprise configuration` page + cy.get('body').then(($body) => { + if ($body.find('[data-id="PP-T2E-ProfilesSetup-Complete"]').length) { + cy.get('[data-id="PP-T2E-ProfilesSetup-Complete-ContinueButton"]').should('be.visible').click(); + } + }); + + // case `Want to signin without your password` page + cy.get('body').then(($body) => { + if ($body.find('[data-id="PasswordlessOptInPP-Page"]').length) { + cy.get('[data-id="PasswordlessOptInPP-continue-button"]').should('be.visible').click(); + } + }); + + // case `Get updates about Adobe products and services` page + cy.get('body').then(($body) => { + if ($body.find('[data-id="MarketingConsent"]').length) { + cy.get('[data-id="PP-RecordMarketingConsent-ContinueBtn"]').should('be.visible').click(); + } + }); + + // case `Add a backup email address` page + cy.get('body').then(($body) => { + if ($body.find('[data-id="PP-AddSecondaryEmail-Page"]').length) { + cy.contains('Not now').should('be.visible').click(); + } + }); + + // case `Add a mobile phone number` page + cy.get('body').then(($body) => { + if ($body.text().includes('Add a mobile phone number')) { + cy.contains('Not now').should('be.visible').click(); + } + }); + + cy.get('body').then(($body) => { + if ($body.text().includes('Select a profile to sign in')) { + cy.contains('Personal Profile').should('be.visible').click(); + } + }); + + cy.intercept('POST', 'https://adobeid-na1-stg1.services.adobe.com/ims/check/v6/token*', (req) => { + console.log('intercepted ims token exchange request'); + req.headers['Origin'] = new URL(baseUrl).origin; + }).as('imsTokenExchange'); +}); diff --git a/docs/images/mini-product-card.png b/docs/images/mini-product-card.png new file mode 100644 index 0000000000..e71a576c25 Binary files /dev/null and b/docs/images/mini-product-card.png differ diff --git a/docs/images/redocly-api-block-long-description-with-x-summary-yaml.png b/docs/images/redocly-api-block-long-description-with-x-summary-yaml.png new file mode 100644 index 0000000000..258c67f479 Binary files /dev/null and b/docs/images/redocly-api-block-long-description-with-x-summary-yaml.png differ diff --git a/docs/images/redocly-api-block-long-description-with-x-summary.png b/docs/images/redocly-api-block-long-description-with-x-summary.png new file mode 100644 index 0000000000..157258f508 Binary files /dev/null and b/docs/images/redocly-api-block-long-description-with-x-summary.png differ diff --git a/docs/images/redocly-api-block-long-description-without-x-summary-yaml.png b/docs/images/redocly-api-block-long-description-without-x-summary-yaml.png new file mode 100644 index 0000000000..6fad9937cd Binary files /dev/null and b/docs/images/redocly-api-block-long-description-without-x-summary-yaml.png differ diff --git a/docs/images/redocly-api-block-long-description-without-x-summary.png b/docs/images/redocly-api-block-long-description-without-x-summary.png new file mode 100644 index 0000000000..0ba55d172b Binary files /dev/null and b/docs/images/redocly-api-block-long-description-without-x-summary.png differ diff --git a/docs/images/redocly-api-block-on-local.png b/docs/images/redocly-api-block-on-local.png new file mode 100644 index 0000000000..87b35e8d42 Binary files /dev/null and b/docs/images/redocly-api-block-on-local.png differ diff --git a/docs/images/redocly-api-block-on-stage.png b/docs/images/redocly-api-block-on-stage.png new file mode 100644 index 0000000000..1178118d19 Binary files /dev/null and b/docs/images/redocly-api-block-on-stage.png differ diff --git a/example/certs/cert.crt b/example/certs/cert.crt new file mode 100644 index 0000000000..b85430f7e0 --- /dev/null +++ b/example/certs/cert.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgITdQO1/vrfGcXYta2eGROZ5f7NmjANBgkqhkiG9w0BAQsF +ADAyMRMwEQYDVQQKDApBZG9iZSBJbmMuMRswGQYDVQQDDBJSb290TG9jYWxBZG9i +ZUNlcnQwHhcNMjQwNzE5MjA1MDU3WhcNMjUxMjAxMjA1MDU3WjAjMSEwHwYDVQQD +DBhsb2NhbGhvc3QuY29ycC5hZG9iZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDsT2KUTea2IAOf+8ilTMqPVE9SKqI+IeiV2XjtZcqRw9FMeH7a +Ck4DQjIqjToMH9EmXM7LQdqqLzShlrcBs0G9dc6+1JzVCv+XUzmVgL+lrv13mvQP +8yuxNLUZqQC2GAR6ur93iFiGqBnHwJ7IK35fw9YdGtoKmfAp1Ff/VmWPnd2GOsqd +FyHWr8lic/g5dDqkDymzgnBmt8ok3D2Jz8cDqbITYTRgCGDK+iZUE2JrQvEduznj +vvJSzVncWkjyQtq19GsiE0KADdVEo7nqR2WcLVKuoyUbVStDNgPjfMd1quBrOd6G +QWEbTcalwDba1DaLsDmVgm+0CyFz8FVfsPfPAgMBAAGjfzB9MB8GA1UdIwQYMBaA +FLClBzlsV0KaFwHEyVN3mYMY2Od5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMCMG +A1UdEQQcMBqCGGxvY2FsaG9zdC5jb3JwLmFkb2JlLmNvbTAdBgNVHQ4EFgQU4Vdm +BiEsgbxOJxeyKNYtzAFhLyUwDQYJKoZIhvcNAQELBQADggEBAEnIxw9fWMhs6lc6 +mL5QfRhFsd7yB54mfHK626YWfw8mieTn4uOZkfGGXPXYTkEQBLoK/2KNjHZbQh7f +5o+ipZIpgUztMGFKKgocsxd/acWCZD/iDfvh7cv49DnNK5tEKz+cUz9nT1dkzf5D +/wfrtKCs9fzBFJJT/hSD/2Ez2kY28pSvPYzArez1r/uJeiSmpfet2WSqNckk2mKz +6KAosvsEvo0tt2mIv1Q/zBgHrmFdmoBTnbZzFv3j/7I1UTPIMTePQkz0UuY9KUlU +J0Xqc3EF+YdextrSZ5S+PZLsxmOEUU6wG2Ex5KXNdR5OYFmRA44MJkoi0CDoakTx +IZ0695Y= +-----END CERTIFICATE----- diff --git a/example/certs/key.key b/example/certs/key.key new file mode 100644 index 0000000000..4ea9334b03 --- /dev/null +++ b/example/certs/key.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDsT2KUTea2IAOf ++8ilTMqPVE9SKqI+IeiV2XjtZcqRw9FMeH7aCk4DQjIqjToMH9EmXM7LQdqqLzSh +lrcBs0G9dc6+1JzVCv+XUzmVgL+lrv13mvQP8yuxNLUZqQC2GAR6ur93iFiGqBnH +wJ7IK35fw9YdGtoKmfAp1Ff/VmWPnd2GOsqdFyHWr8lic/g5dDqkDymzgnBmt8ok +3D2Jz8cDqbITYTRgCGDK+iZUE2JrQvEduznjvvJSzVncWkjyQtq19GsiE0KADdVE +o7nqR2WcLVKuoyUbVStDNgPjfMd1quBrOd6GQWEbTcalwDba1DaLsDmVgm+0CyFz +8FVfsPfPAgMBAAECggEAR1NKuOKugUg2b+JqVGJSPWLAMlfF8qT/c8NZOaZgtxB4 +qvEvsJYtp9QiGi/YVnKACWk3zafJ3VY0N5WrFStK62mUhorb733LWZx4JAThU5v2 +y1QM2IeYJ3t2MhGNsmibaYPWaLjRp4szhNRB2L8v/K26gbxqFYsTLzz1do9YRjlW +eoDjTb/G3lX+hMksD94CNL1msXuKYLwvIw+gmjJUGI+AdCuVb9Hj9E30E1J1Cv6a +MKAplhqQsJ7MLPU8fZqhaRAtrHwmQlLofskm4yf0b3AtccOtcJ23vkglsxw5SpYs +xAZx0kEMRM6j81pQURV+HJDhbJ6ESACE3htDjz0bOQKBgQD2ipZaDfJ3A15/b5gF +NnbTKJikhVwcj3Iz9IFYC0vru4b86Hn9gPRagSwPktoMQTb43sE1JBh8FLgfpliF +C1uUVUM74ZW9z6z/v6kRC3E49zLb92iNrl9CVI5PvIVsSeMq3kiiMGXjuhT55UM3 +e4ecHTv0te9pP6ZXjoYJMPzS2wKBgQD1YE+pOQCNSn/hgwv6WV/3pKypqho3QxT6 +sOh7r170ugPQzWlpvXOQLgHnD54zFhuxBloiRzZ0O/D0VDHP4AehBoG0cH5PooCo ++mDC09nk2ggN1jxSs4rqhipF3A+DW4QcLP1ggui/jXiD1qDMcQTWw9SsBncFSH99 +NVq++ArPHQKBgQDryzBxgNVdLkO67igfFDAhVLpo34NxbWB0gG0Un/lExF4elNnX +svYBeXmCi5kwD09cRu/Su2sPgWF4I8iVtn8oJ0PeiaqFl5KGJ/Cy9JadPQ/PgZjl +qNhCwEw7xrp1D1XNMjuVo2RPoTFer/7Rmbvhe7RUH42Sw+V76DwZnliv8QKBgQDN +zwdh/46n87nJM7qDNB2bQAMWl7c/bMOW9XU58MErcS4sH25wI1hfsUclP+NIJB37 +4EEPiT1yRqh2tnvFSLn9ZNC7wLMhJkLV+JPaNgkHu5yPxoZ4M2GsDfY8/DQC0GrB +mza2kXaY3BceNZJPh+gwHrctxopj4wgTxAGmNbQTPQKBgE9sJgJRKUxcvGbxL/g7 +y4iSAH9fRxK2ixBd/tlRtLH7BvyYEx+iYGGYJd7QgCM3JYkenwvaKdVrMbFc3jFb +TUU3AEVNDwa1Thaoclw/wU39Ciko7x4feoLQV3uphmuFSKqt8N/oAqAkrYiMNRC9 +Yx8TXEtC49SFPj3vabhJzIYt +-----END PRIVATE KEY----- diff --git a/example/gatsby-config.js b/example/gatsby-config.js index cbede411a5..c26163269d 100644 --- a/example/gatsby-config.js +++ b/example/gatsby-config.js @@ -10,6 +10,8 @@ * governing permissions and limitations under the License. */ +const { createProxyMiddleware } = require("http-proxy-middleware"); + module.exports = { pathPrefix: process.env.PATH_PREFIX || '/example/', siteMetadata: { @@ -35,6 +37,14 @@ module.exports = { title: 'Adobe Analytics', path: 'index.md' }, + { + title: 'Get API Key credential', + path: "/getCredential" + }, + { + title: 'Get OAuth S2S credential', + path: "/get-credential-oauth" + }, { title: 'Docs', menu: [ @@ -105,7 +115,7 @@ module.exports = { }, { title: 'Community Forums', - path: 'https://adobe.com/communities/index.html' + path: 'https://community.adobe.com/' } ] }, @@ -249,9 +259,31 @@ module.exports = { } ] } + ] }, plugins: [ `@adobe/gatsby-theme-aio` ], + developMiddleware: app => { + app.use( + "/console/api", + createProxyMiddleware({ + target: "https://developer-stage.adobe.com/console/api", + secure: false, + changeOrigin: true, + }) + ); + app.use("/templates", createProxyMiddleware({ + target: "https://developer-stage.adobe.com/templates", + secure: false, + changeOrigin: true, + })); + + app.use("/ims", createProxyMiddleware({ + target: "https://ims-na1-stg1.adobelogin.com/ims", + secure: false, + changeOrigin: true, + })); + }, }; diff --git a/example/package.json b/example/package.json index 0acdb9a6ec..f967f09d34 100644 --- a/example/package.json +++ b/example/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "dependencies": { "@adobe/gatsby-theme-aio": "*", - "gatsby": "4.22.0" + "gatsby": "4.22.0", + "http-proxy-middleware": "^3.0.0" }, "scripts": { "start": "gatsby build && gatsby serve", diff --git a/example/src/pages/credential/GetCredentialApiKey.js b/example/src/pages/credential/GetCredentialApiKey.js new file mode 100644 index 0000000000..199ba3d0a7 --- /dev/null +++ b/example/src/pages/credential/GetCredentialApiKey.js @@ -0,0 +1,136 @@ +import React from 'react' +import { GetCredential } from '../../../../packages/gatsby-theme-aio/src/components/GetCredential'; +import creativeCloud from "../../pages/creative_cloud/images/cc-icon.png"; + +const GetCredentialApiKey = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + +
+

API key credential

+

Submitting this form creates an API Key credential. The API key credential identifies your application to Adobe servers and can help accept or reject requests originating from certain domains.

+

Learn more

+ Authentication documentation + Express Embed SDK documentation +
+
+ +
+ + + + + + +
+

API key credential

+

Submitting this form creates an API Key credential. The API key credential identifies your application to Adobe servers and can help accept or reject requests originating from certain domains.

+

Learn more

+ Authentication documentation + Express Embed SDK documentation +
+
+ + + + + + + + + + + + + + +
+ + + + + +
+

Welcome back

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+
+
+ +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+

API key credential

+

Submitting this form creates an API Key credential. The API key credential identifies your application to Adobe servers and can help accept or reject requests originating from certain domains.

+

Learn more

+ Authentication documentation + Express Embed SDK documentation +
+
+
+ +
+ + ) +} + +export default GetCredentialApiKey; diff --git a/example/src/pages/credential/GetCredentialExternal.js b/example/src/pages/credential/GetCredentialExternal.js deleted file mode 100644 index be1e34f53a..0000000000 --- a/example/src/pages/credential/GetCredentialExternal.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { GetCredential } from "../../../../packages/gatsby-theme-aio/src/components/GetCredential"; -import "./Credential.css" - -const GetCredentialExternal = () => { - return ( - - - - - - - - - - - - - - {/* - - */} - - - -
-

API key credential

-

Submitting this form created an API Key credential. The API key credential identifies your application to Adobe servers and can help accept or reject requests originating from certian domains.

-

Learn more

- Authentication documentation - Adobe Express Embed SDK documentation -
-
- -
- - - - - - -
-

API key credential

-

An API Key credential was created. The API key credential identifies your application to Adobe servers and can help accept or reject request originating from certain domains.

-

Learn more

- Authentication documentation - Adobe Express Embed SDK documentation -
-
- -
- - - - - -
- - ) -} - -export default GetCredentialExternal; diff --git a/example/src/pages/credential/GetCredentialOAuthS2s.js b/example/src/pages/credential/GetCredentialOAuthS2s.js new file mode 100644 index 0000000000..8095c86279 --- /dev/null +++ b/example/src/pages/credential/GetCredentialOAuthS2s.js @@ -0,0 +1,216 @@ +import React from 'react' +import { GetCredential } from '../../../../packages/gatsby-theme-aio/src/components/GetCredential'; +import firefly from "./images/firefly.png"; +import ps from "./images/ps.png"; + +const GetCredentialOAuthS2s = () => { + return ( + + + + + + + + + + + + + + + + + +
+
+

+ OAuth server-to-server credential +

+

+ This credential allows you to use industry standard OAuth2.0 + libraries to generate access tokens using the OAuth 2.0 client + credentials grant type. +

+
+ +
+
+ +
+ + + + + + +
+
+

+ OAuth server-to-server credential +

+

+ This credential allows you to use industry standard OAuth2.0 + libraries to generate access tokens using the OAuth 2.0 client + credentials grant type. +

+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+

Welcome back

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+
+

+ OAuth server-to-server credential +

+

+ This credential allows you to use industry standard OAuth2.0 + libraries to generate access tokens using the OAuth 2.0 client + credentials grant type. +

+
+ +
+
+
+ +
+ + ) +} + +export default GetCredentialOAuthS2s; diff --git a/example/src/pages/credential/images/firefly.png b/example/src/pages/credential/images/firefly.png new file mode 100644 index 0000000000..7122679f97 Binary files /dev/null and b/example/src/pages/credential/images/firefly.png differ diff --git a/example/src/pages/credential/images/ps.png b/example/src/pages/credential/images/ps.png new file mode 100644 index 0000000000..e10fbac62a Binary files /dev/null and b/example/src/pages/credential/images/ps.png differ diff --git a/example/src/pages/get-credential-oauth/index.md b/example/src/pages/get-credential-oauth/index.md new file mode 100644 index 0000000000..74f432bf7f --- /dev/null +++ b/example/src/pages/get-credential-oauth/index.md @@ -0,0 +1,3 @@ +import GetCredentialOAuthS2s from "../credential/GetCredentialOAuthS2s.js" + + \ No newline at end of file diff --git a/example/src/pages/getCredential/index.md b/example/src/pages/getCredential/index.md new file mode 100644 index 0000000000..b2c3280acc --- /dev/null +++ b/example/src/pages/getCredential/index.md @@ -0,0 +1,3 @@ +import GetCredentialApiKey from "../credential/GetCredentialApiKey.js"; + + \ No newline at end of file diff --git a/example/src/pages/index.md b/example/src/pages/index.md index 22ffe52776..fc6221d042 100644 --- a/example/src/pages/index.md +++ b/example/src/pages/index.md @@ -32,9 +32,9 @@ import TextBlockComp from "./textblock/textblock.md" import MiniResource from "./miniresource_block/index.md" import TextBlockImage from "./imageTextBlock/index.md" import ListComp from "./listblock/index.md" -import GetCredentialExternal from "./credential/GetCredentialExternal.js" import PDFViewerComp from "./pdf_viewer/index.md" import ProfileCardComp from "./profile_card/index.md" +import MiniProductCardComp from "./mini_product_card/index.md" @@ -43,6 +43,8 @@ import ProfileCardComp from "./profile_card/index.md" Adobe Product API offers limitless ways to integrate your most important customer data into key business processes. Adobe Product API offer limitless ways. + + #### Resources @@ -120,8 +122,6 @@ When rate limiting is being enforced you will get `429` HTTP response codes with - - @@ -344,5 +344,14 @@ Inline Alert as a nested component + + + + + + + + + diff --git a/example/src/pages/mini_product_card/S_AniChangeBackground.png b/example/src/pages/mini_product_card/S_AniChangeBackground.png new file mode 100644 index 0000000000..3ea78fbf1c Binary files /dev/null and b/example/src/pages/mini_product_card/S_AniChangeBackground.png differ diff --git a/example/src/pages/mini_product_card/S_AniConvertToGIF.png b/example/src/pages/mini_product_card/S_AniConvertToGIF.png new file mode 100644 index 0000000000..9c49e49ca7 Binary files /dev/null and b/example/src/pages/mini_product_card/S_AniConvertToGIF.png differ diff --git a/example/src/pages/mini_product_card/S_AniCrop.png b/example/src/pages/mini_product_card/S_AniCrop.png new file mode 100644 index 0000000000..a93fa981d2 Binary files /dev/null and b/example/src/pages/mini_product_card/S_AniCrop.png differ diff --git a/example/src/pages/mini_product_card/S_AniMergeVideo.png b/example/src/pages/mini_product_card/S_AniMergeVideo.png new file mode 100644 index 0000000000..66bf85ca55 Binary files /dev/null and b/example/src/pages/mini_product_card/S_AniMergeVideo.png differ diff --git a/example/src/pages/mini_product_card/index.md b/example/src/pages/mini_product_card/index.md new file mode 100644 index 0000000000..f197f7b891 --- /dev/null +++ b/example/src/pages/mini_product_card/index.md @@ -0,0 +1,38 @@ + +## Mini Product card + +Miniresource card allows to put the text, image, description + + + +![Forge the path to customer success](./S_AniConvertToGIF.png) + +# Firefly API + +Firefly API + +- [Learn more](https://developer-stage.adobe.com/) + +![Forge the path to customer success](./S_AniConvertToGIF.png) + +# Photoshop API + +Photoshop API + +- [Learn more](https://developer-stage.adobe.com/) + +![Forge the path to customer success](./S_AniConvertToGIF.png) + +# Lightroom API + +Lightroom API + +- [Learn more](https://developer-stage.adobe.com/) + +![Forge the path to customer success](./S_AniConvertToGIF.png) + +# Content Tagging API + +Content Tagging API + +- [Learn more](https://developer-stage.adobe.com/) diff --git a/package.json b/package.json index bee89eea24..d212516af1 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dev:clean": "yarn workspace example clean && yarn workspace example develop", "dev:https": "yarn workspace example develop:https", "dev:https:local": "yarn workspace example gatsby develop --https --host local.corp.adobe.com", + "dev:https:ci": "yarn workspace example develop --https --host localhost.corp.adobe.com --port 9000 --key-file ./certs/key.key --cert-file ./certs/cert.crt", "dev:schema": "yarn workspace example develop:schema", "build": "yarn workspace example build", "build:incremental": "GATSBY_EXPERIMENTAL_PAGE_BUILD_ON_DATA_CHANGES=true yarn workspace example build --log-pages", @@ -33,11 +34,14 @@ "clean": "yarn workspace example clean", "format": "npx prettier --write '**/*.{js,json}'", "cy:open": "cypress open", - "test:e2e": "start-server-and-test start http://localhost:9000 cy:open" + "test:e2e": "yarn clean && START_SERVER_AND_TEST_INSECURE=1 CYPRESS_SUPPORT=y start-server-and-test dev:https https://localhost.corp.adobe.com:9000 cy:open" }, "resolutions": { "sharp": "0.33.0", "gatsby-sharp": "1.12.0" }, - "packageManager": "yarn@3.2.3" + "packageManager": "yarn@3.2.3", + "dependencies": { + "http-proxy-middleware": "^3.0.0" + } } diff --git a/packages/gatsby-theme-aio/CHANGELOG.md b/packages/gatsby-theme-aio/CHANGELOG.md index c97efed778..5a0047c967 100644 --- a/packages/gatsby-theme-aio/CHANGELOG.md +++ b/packages/gatsby-theme-aio/CHANGELOG.md @@ -3,6 +3,179 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [4.14.13](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.14.12..@adobe/gatsby-theme-aio@4.14.13) (2024-10-10) + +### Fix +* Fixes to the create credentials component [afa017b](https://github.com/adobe/aio-theme/commit/afa017b34905c4152b118837bc7ad6f9c2d31566) + + +## [4.14.12](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.14.10..@adobe/gatsby-theme-aio@4.14.12) (2024-09-24) + +### Features +* Add scrollYOffset config to RedoclyAPIBlock [1d2746c](https://github.com/adobe/aio-theme/commit/1d2746c2bc74d5055ec3cdc425fd635bd7bc94ab) + +## [4.14.10](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.14.9..@adobe/gatsby-theme-aio@4.14.10) (2024-08-14) + +### Features +* Add sortTagsAlphabetically config to RedoclyAPIBlock [fced957](https://github.com/adobe/aio-theme/commit/fced957e73c2f0414cb23a5bcdcb4ca4c9d5c979) +* Add sortOperationsAlphabetically config to RedoclyAPIBlock [24d431e](https://github.com/adobe/aio-theme/commit/24d431ea26fdc7fe278e35169709aa35bdae295f) + +## [4.14.9](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.14.8..@adobe/gatsby-theme-aio@4.14.9) (2024-08-09) + +### Features +* **DEVSITE-1070:** Sticky feedback component [285a739](https://github.com/adobe/aio-theme/commit/285a7397f63c6218dc0a73bda93f753fe23af865) + +## [4.14.8](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.14.7..@adobe/gatsby-theme-aio@4.14.8) (2024-08-07) + +### Fix +* Index correct mdast value for link text [ec5969d](https://github.com/adobe/aio-theme/commit/ec5969d8e6594d2555ded8e0f9c92530b84c88c2) + +## [4.14.7](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.14.6..@adobe/gatsby-theme-aio@4.14.7) (2024-07-30) + +### Fix +* **DEVSITE-1154:** Maximize columns in discover block [053003f](https://github.com/adobe/aio-theme/pull/1608/commits/053003f0970ce48c0e7fdd3578465ab27f0bfefe) + + +## [4.14.6](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.13.5..@adobe/gatsby-theme-aio@4.14.6) (2024-07-25) + +### Fix +* fix failing tests [c84a757](https://github.com/adobe/aio-theme/commit/c84a757103138aca16a1096613b31ffc4f01be1d) + +## [4.14.5](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.13.4..@adobe/gatsby-theme-aio@4.14.5) (2024-07-25) + +### Fix +* Remove react-spectrum dependency and other get credentials fixes [313c26a](https://github.com/adobe/aio-theme/commit/313c26aa026895a9ad115382f8b3cd671e555e45) + +### Features +* Added e2e tests for get credentials [31838dc](31838dc5ec68d7aaeaa8ada4ed0b766690f50168) + +## [4.14.4](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.13.3..@adobe/gatsby-theme-aio@4.14.4) (2024-07-16) + +### Fix +* objectIDs formula and contentDigest for partial indexing [e8ec4de](https://github.com/adobe/aio-theme/commit/e8ec4de54be809368f376dd06470993bc21f0bd6) + +* Missing search results content/description errors [e8ec4de](https://github.com/adobe/aio-theme/commit/e8ec4de54be809368f376dd06470993bc21f0bd6) + +* Redirect user to the signin page if API call errors [41a4e1d](https://github.com/adobe/aio-theme/commit/41a4e1da872905b1976f23ce75dc23d47c706c9e) + + +## [4.14.3](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.13.2..@adobe/gatsby-theme-aio@4.14.3) (2024-07-11) + +### Fix +* Missing "@adobe/react-spectrum" in package dependancies [7e34919](https://github.com/adobe/aio-theme/commit/7e349191f22d6e8fd12a14eb60d25551756ec498) + +## [4.14.2](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.13.1..@adobe/gatsby-theme-aio@4.14.2) (2024-07-11) + +### Features +* Enable partial algolia indexing and use contentDigest for diff check [de64ca0](https://github.com/adobe/aio-theme/commit/de64ca0282a4049cb21946e2279ebe7895f74c12) + +### Fix +* Calculate record size instead of using file size when creating algolia records [de64ca0](https://github.com/adobe/aio-theme/commit/de64ca0282a4049cb21946e2279ebe7895f74c12) + + +## [4.14.1](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.13.0..@adobe/gatsby-theme-aio@4.14.1) (2024-07-10) + +### Features +* Add get credential component [ead5d5e](https://github.com/adobe/aio-theme/commit/ead5d5eed51cf04f124839aac5b72a0653e6c19f) + +## [4.13.0](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.12.0..@adobe/gatsby-theme-aio@4.13.0) (2024-07-08) + +### Fix +* Version bump to fix npm + +## [4.12.0](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.16...@adobe/gatsby-theme-aio@4.12.0) (2024-07-08) + +### Fix +* Fix breaking dependency - set preact-render-to-string@ ^6.5.5 [a0cbb62](https://github.com/adobe/aio-theme/commit/a0cbb62824e77fe36744f77992f4422fd5b0e28c) + + +## [4.11.16](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.15...@adobe/gatsby-theme-aio@4.11.16) (2024-06-19) + +### Features +* Add option to add request interceptor in RedoclyAPIBlock [f044e7b](https://github.com/adobe/aio-theme/commit/f044e7bf2c87b5fe31d54355751655ec7958d02f) + +## [4.11.15](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.14...@adobe/gatsby-theme-aio@4.11.15) (2024-06-17) + +### Features +* Add option to generate code samples in RedoclyAPIBlock [f751bb5](https://github.com/adobe/aio-theme/commit/f751bb5f91dbbfd8f78bcdea0ea89093f907bbf2) + +## [4.11.14](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.13...@adobe/gatsby-theme-aio@4.11.14) (2024-05-14) + +### Fix +* Update community forums link on footer [0bcfd4a](https://github.com/adobe/aio-theme/commit/0bcfd4abccfd0744550412f0ffacd86d7d0ab3a3) + +## [4.11.13](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.12...@adobe/gatsby-theme-aio@4.11.13) (2024-04-22) + +### Fix + +* Fixed the page load issues in the get credential component [a78b072](https://github.com/adobe/aio-theme/commit/a78b0720396b6b35634c9db7125bbc608da506fe) + + +## [4.11.12](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.11...@adobe/gatsby-theme-aio@4.11.12) (2024-04-4) + +### Fix + +* Fix half width hero image misalignement [9128a8a](https://github.com/adobe/aio-theme/commit/9128a8a5ff85293402b8956e826145af0485f384) + + +## [4.11.11](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.10...@adobe/gatsby-theme-aio@4.11.11) (2024-03-28) + +### Fix + +* **DEVSITE-1116:** Fit resources in main so it aligns correctly with hero [b01b2ab](https://github.com/adobe/aio-theme/pull/1567/commits/b01b2abbab9f969c3ee84299f9f4ff0f65a323e8) + +## [4.11.10](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.9...@adobe/gatsby-theme-aio@4.11.10) (2024-03-21) + +### Features + +* **DEVSITE-1112:** Make expand level of json samples customizable [3f9d02a](https://github.com/adobe/aio-theme/pull/1567/commits/3f9d02a1a82162ee3585679b6dd7f2256ec52bbf) + +## [4.11.9](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.8...@adobe/gatsby-theme-aio@4.11.9) (2024-03-20) + +### Features + +* Added Mini Product Resources card [ebd47c8](https://github.com/adobe/aio-theme/pull/1567/commits/ebd47c85ff6f37fa5b23e9f38a3ec3653615ad8b) +* Added video in the Hero Block [ebd47c8](https://github.com/adobe/aio-theme/pull/1567/commits/ebd47c85ff6f37fa5b23e9f38a3ec3653615ad8b) +* Modified the design in accordion [ebd47c8](https://github.com/adobe/aio-theme/pull/1567/commits/ebd47c85ff6f37fa5b23e9f38a3ec3653615ad8b) + +## [4.11.8](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.7...@adobe/gatsby-theme-aio@4.11.8) (2024-03-20) + +### Features + +* Add option to disable Try It widget [eef5338](https://github.com/adobe/aio-theme/commit/eef53381ba865693b311a43974f119e5b7a7419d) + +## [4.11.7](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.4...@adobe/gatsby-theme-aio@4.11.7) (2024-03-13) + +### Features + +* **DEVSITE-1106:** Make width and codeblock customizable [8a76182](https://github.com/adobe/aio-theme/commit/8a76182c4f259331975e478d320a19d01a21bf3c) +* **DEVSITE-1106:** Make disableSidebar and disableSearch customizable [486625c](https://github.com/adobe/aio-theme/commit/486625c98452b8a5f657840e366a701069e41e0d) +* **DEVSITE-1106:** Make typography customizable [a5e783b](https://github.com/adobe/aio-theme/commit/a5e783bfd5e516de4eb2e2d6d7f26135a8a0e7c3) + +### Fix + +* **DEVSITE-1106:** Hide loading animation as a work-around to make the redocly integration appear more seamless [c3396c3](https://github.com/adobe/aio-theme/commit/c3396c308ff4a2623500aceba744eec47cef374c) + +## [4.11.4](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.3...@adobe/gatsby-theme-aio@4.11.4) (2023-02-28) + +### Fix +* Make changes to Footer [1c840a1](https://github.com/adobe/aio-theme/commit/1c840a10f6f9ef8b8e0702b6f55d425e6b210e88) + +## [4.11.3](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.2...@adobe/gatsby-theme-aio@4.11.3) (2023-01-24) + +### Fix +* To fix a Redocly right panel size. [af99e1a](https://github.com/adobe/aio-theme/commit/af99e1afe0aa5a8a89ef512e81eaa03ebcd609d7) + +## [4.11.2](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.1...@adobe/gatsby-theme-aio@4.11.2) (2023-01-24) + +### Fix +* To fix a Redocly component bug. [fd36030](https://github.com/adobe/aio-theme/commit/fd36030e6989a6269df2aa38f39adc2bb6914646) + +## [4.11.1](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.11.0...@adobe/gatsby-theme-aio@4.11.1) (2023-01-23) + +### Fix +* Media component should not autoplay. [f376a65](https://github.com/adobe/aio-theme/commit/f376a65347a508845765a9a19761b059d11868b0) + ## [4.11.0](https://github.com/adobe/aio-theme/compare/@adobe/gatsby-theme-aio@4.10.9...@adobe/gatsby-theme-aio@4.11.0) (2023-12-13) ### Features diff --git a/packages/gatsby-theme-aio/algolia/create-record.js b/packages/gatsby-theme-aio/algolia/create-record.js index 0654cc6f25..a7980a22a6 100644 --- a/packages/gatsby-theme-aio/algolia/create-record.js +++ b/packages/gatsby-theme-aio/algolia/create-record.js @@ -10,13 +10,13 @@ * governing permissions and limitations under the License. */ -const { getAdobeDocType } = require("./helpers/get-adobe-doctype"); +const { getAdobeDocType } = require('./helpers/get-adobe-doctype'); async function createRecord(rawRecord, file) { const record = { category: getCategory(file.category), content: rawRecord.content, - contentDigest: rawRecord.contentDigest, + contentDigest: file.contentDigest, contentHeading: rawRecord.contentHeading, description: rawRecord.description, excerpt: file.excerpt, @@ -30,7 +30,7 @@ async function createRecord(rawRecord, file) { objectID: rawRecord.objectID, path: getPath(rawRecord, file), product: file.product, - size: file.size, + size: Buffer.byteLength(JSON.stringify(rawRecord), 'utf16'), slug: file.slug, title: rawRecord.title, url: getUrl(rawRecord, file), @@ -62,13 +62,13 @@ function getKeywords(keywords) { // Full url complete with anchor link to the nearest heading for the search record result. function getUrl(rawRecord, file) { - return `${process.env.GATSBY_SITE_DOMAIN_URL}${file.pathPrefix}${file.slug == null ? '' : file.slug - }${rawRecord.anchor}`; + return `${process.env.GATSBY_SITE_DOMAIN_URL}${file.pathPrefix}${ + file.slug == null ? '' : file.slug + }${rawRecord.anchor}`; } function getPath(rawRecord, file) { - return `${file.pathPrefix}${file.slug == null ? '' : file.slug - }${rawRecord.anchor}`; + return `${file.pathPrefix}${file.slug == null ? '' : file.slug}${rawRecord.anchor}`; } module.exports = createRecord; diff --git a/packages/gatsby-theme-aio/algolia/helpers/html-parser.js b/packages/gatsby-theme-aio/algolia/helpers/html-parser.js index df08c0821a..47ed207ec3 100644 --- a/packages/gatsby-theme-aio/algolia/helpers/html-parser.js +++ b/packages/gatsby-theme-aio/algolia/helpers/html-parser.js @@ -2,7 +2,6 @@ // https://github.com/mansona/html-extractor/blob/master/lib/algoliaHtmlExtractor.js const { JSDOM } = require('jsdom'); -const uuid = require('uuid'); // Extract content from an HTML page in the form of items with associated headings data module.exports = class HtmlParser { @@ -20,7 +19,7 @@ module.exports = class HtmlParser { return new JSDOM(input).window.document.querySelectorAll(selector); } - run(input, options = {}) { + run(input, options, file = {}) { const runOptions = this.defaultOptions(options); const { headingSelector, cssSelector, tagsToExclude } = runOptions; // @@ -41,7 +40,7 @@ module.exports = class HtmlParser { // // We select all nodes that match either the headings or the elements to // extract. This will allow us to loop over it in order it appears in the DOM - this.css(input, `${headingSelector},${cssSelector}`).forEach(node => { + this.css(input, `${headingSelector},${cssSelector}`).forEach((node, index) => { // If it's a heading, we update our current hierarchy if (node.matches(headingSelector)) { // Which level heading is it? @@ -92,8 +91,7 @@ module.exports = class HtmlParser { }, }; - item.objectID = uuid.v4(item); - item.contentDigest = uuid.v4(content); + item.objectID = `${file.contentDigest}_${index}`; item.words = content.split(' ').length; item.title = Object.values(currentHierarchy).filter(h => h)[0]; item.contentHeading = Object.values(currentHierarchy).filter(h => h)[1]; diff --git a/packages/gatsby-theme-aio/algolia/helpers/parse-html.js b/packages/gatsby-theme-aio/algolia/helpers/parse-html.js index 803268452c..5551194816 100644 --- a/packages/gatsby-theme-aio/algolia/helpers/parse-html.js +++ b/packages/gatsby-theme-aio/algolia/helpers/parse-html.js @@ -13,9 +13,9 @@ const HtmlParser = require('./html-parser'); const htmlParser = new HtmlParser(); -function parseHtml(content, options) { +function parseHtml(content, options, file) { const rawRecords = htmlParser - .run(content, { cssSelector: options.tagsToIndex }) + .run(content, { cssSelector: options.tagsToIndex }, file) .filter( htmlElement => htmlElement.content.length >= options.minCharsLengthPerTag && diff --git a/packages/gatsby-theme-aio/algolia/helpers/parse-mdx.js b/packages/gatsby-theme-aio/algolia/helpers/parse-mdx.js index 69b95cb17e..808ee170a7 100644 --- a/packages/gatsby-theme-aio/algolia/helpers/parse-mdx.js +++ b/packages/gatsby-theme-aio/algolia/helpers/parse-mdx.js @@ -11,7 +11,6 @@ */ const { selectAll } = require('unist-util-select'); -const uuid = require('uuid'); function parseMdx(markdownFile) { // See: https://github.com/syntax-tree/mdast for node type info. @@ -25,27 +24,38 @@ function parseMdx(markdownFile) { const rawRecords = []; const minCharLength = 5; - for (const mdastNode of mdastNodes) { + for (const [index, mdastNode] of mdastNodes.entries()) { let nodeValue = ''; nodeValue = mdastNode.type === 'code' ? mdastNode.value - : mdastNode.children.map(child => child.value).join(''); + : mdastNode.children + .map(child => { + if (child.type === 'link') { + return child?.children?.find(child => child.type === 'text')?.value; + } + return child?.value; + }) + .join(''); if (nodeValue.length < minCharLength) continue; const rawRecord = { - objectID: uuid.v4(nodeValue), - contentDigest: uuid.v4(nodeValue), + objectID: `${markdownFile.contentDigest}_${index}`, content: nodeValue, headings: markdownFile.headings.map(heading => heading.value), contentHeading: getContentHeading(mdastNode, markdownFile), anchor: getAnchor(mdastNode, markdownFile), featured: markdownFile.featured, category: markdownFile.category, - title: markdownFile.title === '' || markdownFile.title == null ? markdownFile.headings[0]?.value : markdownFile.title, + title: + markdownFile.title === '' || markdownFile.title == null + ? markdownFile.headings[0]?.value + : markdownFile.title, description: - markdownFile.description === '' || markdownFile.description == null ? markdownFile.excerpt : markdownFile.description, + markdownFile.description === '' || markdownFile.description == null + ? markdownFile.excerpt + : markdownFile.description, words: nodeValue.split(' ').length, }; diff --git a/packages/gatsby-theme-aio/algolia/index-records.js b/packages/gatsby-theme-aio/algolia/index-records.js index 88fc8b74b1..644bc52a11 100644 --- a/packages/gatsby-theme-aio/algolia/index-records.js +++ b/packages/gatsby-theme-aio/algolia/index-records.js @@ -64,6 +64,7 @@ function indexRecords(isDryRun) { frameSrc: node.childMdx.frontmatter.frameSrc, headings: node.childMdx.headings, howRecent: node.howRecent, + contentDigest: node.internal.contentDigest, icon: node.icon, isNew: node.isNew, keywords: node.childMdx.frontmatter.keywords, @@ -72,7 +73,7 @@ function indexRecords(isDryRun) { objectID: node.id, openAPISpec: node.childMdx.frontmatter.openAPISpec, pathPrefix: `${pathPrefix}/`, - product: productFromPath.productName, + product: productFromPath.productName ? productFromPath.productName : "other", size: node.size, slug: node.childMdx.slug, title: node.childMdx.frontmatter.title, @@ -136,7 +137,7 @@ function indexRecords(isDryRun) { // Create 'raw' records from content let rawRecords = Object.keys(htmlContent).length > 0 - ? parseHtml(htmlContent.content, htmlContent.options) + ? parseHtml(htmlContent.content, htmlContent.options, markdownFile) : parseMdx(markdownFile); if (rawRecords == null || rawRecords.length <= 0) continue; @@ -145,6 +146,7 @@ function indexRecords(isDryRun) { for (const rawRecord of rawRecords) { const record = await createAlgoliaRecord(rawRecord, markdownFile); if (record.size > 20000) { + console.warn(JSON.stringify(record)); console.warn(`Record at path ${record?.path} objectID=${record?.objectID} is too big size=${record?.size}/20000 bytes and will be skipped.`); } else { diff --git a/packages/gatsby-theme-aio/gatsby-config.js b/packages/gatsby-theme-aio/gatsby-config.js index 81de24c195..fb2f3a84cc 100644 --- a/packages/gatsby-theme-aio/gatsby-config.js +++ b/packages/gatsby-theme-aio/gatsby-config.js @@ -135,6 +135,8 @@ module.exports = { options: { appId: process.env.GATSBY_ALGOLIA_APPLICATION_ID, indexName: process.env.GATSBY_ALGOLIA_INDEX_ENV_PREFIX ? `${process.env.GATSBY_ALGOLIA_INDEX_ENV_PREFIX}-${process.env.GATSBY_ALGOLIA_INDEX_NAME}` : process.env.GATSBY_ALGOLIA_INDEX_NAME, + // Use Admin API key without GATSBY_ prefix, so that the key isn't exposed in the application + // Tip: use Search API key with GATSBY_ prefix to access the service from within components apiKey: process.env.ALGOLIA_WRITE_API_KEY, queries: indexRecords(isDryRun), chunkSize: 1000, @@ -147,7 +149,7 @@ module.exports = { }, settings: indexSettings(), mergeSettings: false, - enablePartialUpdates: false, + enablePartialUpdates: true, matchFields: ['contentDigest'], concurrentQueries: false, // default: true dryRun: isDryRun, // default: true. When false, a new index is pushed to Algolia. diff --git a/packages/gatsby-theme-aio/package.json b/packages/gatsby-theme-aio/package.json index 4d113b293b..e077717f62 100644 --- a/packages/gatsby-theme-aio/package.json +++ b/packages/gatsby-theme-aio/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/gatsby-theme-aio", - "version": "4.11.0", + "version": "4.14.13", "description": "The Adobe I/O theme for building markdown powered sites", "main": "index.js", "license": "Apache-2.0", @@ -133,7 +133,7 @@ "penpal": "^6.2.2", "postcss": "^8.4.16", "preact": "^10.11.0", - "preact-render-to-string": "^5.2.4", + "preact-render-to-string": "^6.5.5", "prism-react-renderer": "1.3.5", "prop-types": "^15.8.1", "react-helmet": "^6.1.0", diff --git a/packages/gatsby-theme-aio/src/components/Accordion/index.js b/packages/gatsby-theme-aio/src/components/Accordion/index.js index 9676e53f21..a684b78d6f 100644 --- a/packages/gatsby-theme-aio/src/components/Accordion/index.js +++ b/packages/gatsby-theme-aio/src/components/Accordion/index.js @@ -57,6 +57,10 @@ const AccordionItem = ({ header, slot_id, isOpen = false, children, isChevronIco onClick={toggleOpen} onKeyDown={toggleOpen} css={css` + text-transform: var(--spectrum-body-m-text-transform, none) !important; + font-size: var(--spectrum-body-m-text-size, var(--spectrum-global-dimension-font-size-300)) !important; + color: var(--spectrum-listitem-m-text-color-hover, var(--spectrum-alias-text-color)); + font-weight: var(--spectrum-global-font-weight-bold) !important; width: 100% !important; justify-content: ${(position === "right" && isChevronIcon) && "space-between !important"}; flex-direction: ${(position === "right" && isChevronIcon) && "row-reverse !important"}; diff --git a/packages/gatsby-theme-aio/src/components/Footer/index.js b/packages/gatsby-theme-aio/src/components/Footer/index.js index 62f4731cc4..c72e179b91 100644 --- a/packages/gatsby-theme-aio/src/components/Footer/index.js +++ b/packages/gatsby-theme-aio/src/components/Footer/index.js @@ -39,6 +39,20 @@ const { APIs, services, community, support, developer, legal, allAPIs } = { path: '/document-services/homepage' } ], + services: [ + { + title: 'Adobe Cloud Manager', + path: '/experience-cloud/cloud-manager' + }, + { + title: 'Adobe Analytics', + path: '/analytics-apis/docs/2.0' + }, + { + title: 'App Builder', + path: '/app-builder' + } + ], community: [ { title: 'Adobe Tech Blog', @@ -58,7 +72,7 @@ const { APIs, services, community, support, developer, legal, allAPIs } = { }, { title: 'Community Forums', - path: 'https://adobe.com/communities/index.html' + path: 'https://community.adobe.com/' } ], support: [ @@ -84,6 +98,10 @@ const { APIs, services, community, support, developer, legal, allAPIs } = { title: 'Open source at Adobe', path: '/open' }, + { + title: 'Download SDKs', + path: '/console/downloads' + }, { title: 'Authentication', path: '/developer-console/docs/guides/authentication' @@ -251,6 +269,15 @@ const Footer = ({ hasSideNav = false }) => ( ))} + {services.map(({ title, path }, i) => ( +
  • + + + {title} + + +
  • + ))}
  • diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/Card/AccessToken.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/AccessToken.js new file mode 100644 index 0000000000..75b4202879 --- /dev/null +++ b/packages/gatsby-theme-aio/src/components/GetCredential/Card/AccessToken.js @@ -0,0 +1,142 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import { ProgressCircle } from "../../ProgressCircle"; +import { Button } from "../../Button"; +import { CopyIcon } from '../Icons'; +import { Toast } from '../../Toast'; +import GetCredentialContext from '../GetCredentialContext'; +import { generateToken, getCredentialSecrets } from '../Service'; +import { ActionButton } from '../../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/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardAPIKey.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardAPIKey.js new file mode 100644 index 0000000000..cb8243b4d2 --- /dev/null +++ b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardAPIKey.js @@ -0,0 +1,10 @@ +import React from 'react' +import ShowCard from './ShowCard'; + +const CardAPIKey = ({ cardAPIKey, apiKey }) => { + return ( + + ) +} + +export { CardAPIKey }; diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardAllowedOrigins.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardAllowedOrigins.js new file mode 100644 index 0000000000..0ba25ac27c --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardClientDetails.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardClientDetails.js new file mode 100644 index 0000000000..0eae54b4a4 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardClientId.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardClientId.js new file mode 100644 index 0000000000..f8a024fb10 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardClientSecret.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardClientSecret.js new file mode 100644 index 0000000000..b9a93a9491 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardImsOrgID.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardImsOrgID.js new file mode 100644 index 0000000000..7b42d88442 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardOrganizationName.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardOrganizationName.js new file mode 100644 index 0000000000..7ed147c6ff --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardScopes.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/CardScopes.js new file mode 100644 index 0000000000..9c370c71fa --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Card/DevConsoleLink.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/DevConsoleLink.js new file mode 100644 index 0000000000..11b62cc821 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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 ( + <> +
    + + ); +}; + +export { DevConsoleLink }; diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/Card/ShowCard.js b/packages/gatsby-theme-aio/src/components/GetCredential/Card/ShowCard.js new file mode 100644 index 0000000000..53a3d2849a --- /dev/null +++ b/packages/gatsby-theme-aio/src/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 "../../ActionButton"; +import { ProgressCircle } from "../../ProgressCircle"; +import { Toast } from '../../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/packages/gatsby-theme-aio/src/components/GetCredential/ChangeOrganization.js b/packages/gatsby-theme-aio/src/components/GetCredential/ChangeOrganization.js deleted file mode 100644 index d458938cd3..0000000000 --- a/packages/gatsby-theme-aio/src/components/GetCredential/ChangeOrganization.js +++ /dev/null @@ -1,224 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { css } from "@emotion/react"; -import { getOrganization, MAX_MOBILE_WIDTH, MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH, MIN_TABLET_SCREEB_WIDTH } from './FormFields'; -import { Picker as SelectBox } from "../Picker" - -const ChangeOrganization = ({ setModalOpen, redirectToBeta, setRedirectBetaProgram, setAlertShow, setOrganization, setOrganizationValue }) => { - - const [organization, setOrgans] = useState([]); - const [selectedIndex, setSelectedIndex] = useState(); - - useEffect(() => { - - const orgInfo = localStorage?.getItem('OrgInfo'); - getOrganization().then((data) => { - setOrgans(data); - data?.map((value, index) => { - const orgData = JSON.parse(orgInfo); - if (value?.id == orgData?.id) { - setSelectedIndex(index) - } - }) - }); - document.documentElement.style.overflow = 'hidden'; - - }, []); - - const handleRedirect = () => { - setAlertShow(true); - handleModal(); - organization.forEach((organs, index) => { - if (index === selectedIndex) { - setOrganizationValue(organs) - } - }) - }; - - const handleModal = () => { - setRedirectBetaProgram(false); - setModalOpen(false); - document.documentElement.style.overflow = 'scroll'; - }; - - useEffect(() => { - setOrganization(true); - organization.forEach((organs, index) => { - if (index === selectedIndex) { - const orgData = { - "id": organs?.id, - "name": organs?.name, - "orgLen": organization?.length, - "type": organs?.type - } - localStorage.setItem('OrgInfo', JSON.stringify(orgData)); - } - }) - }, [selectedIndex]) - - return ( - <> - {!redirectToBeta && -
    - {organization?.length === 0 ? -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - : -
    -
    -
    -

    Change organization

    -
    -
    -
    -
    - An organization is the entity that functions like a log-in company that spans all Adobe products and applications. Most often, an organization is your company name.However, a company can have many organizations. Change the organization here. -
    -
    -
    -

    - Organization -

    -
    - -
    div > .spectrum-Picker { - width: 100% !important; - height: 20px; - } - - & > div > div { - width: 86%; - left: 9%; - height : 40%; - - @media screen and (min-width:${MIN_MOBILE_WIDTH}) and (max-width:${MAX_MOBILE_WIDTH}){ - width: 82%; - left: 15%; - } - - @media screen and (min-width:${MIN_TABLET_SCREEB_WIDTH}) and (max-width:${MAX_TABLET_SCREEN_WIDTH}){ - width: 91%; - left: 7%; - } - - } - - & > div > .spectrum-Picker-popover > ul > li > div > div { - margin : 0 ; - } - - & > div > .spectrum-Picker-popover > ul > li > div > div > svg { - @media screen and (min-width:${MIN_MOBILE_WIDTH}) and (max-width:${MAX_TABLET_SCREEN_WIDTH}){ - margin: 3px; - padding: 0; - } - } - - padding: 5px; - border-radius: 3px; - border: 1px solid #D0D0D0 !important; - - ` } - > - { - return { - title: organs?.name, - selected: k === selectedIndex - } - })} - onChange={(index) => { - setSelectedIndex(index); - }} - /> -
    - -
    -
    - Can't find your organization? - Learn more about organizations. -
    -
    -
    - -
    - - -
    -
    -
    -
    - } -
    - } - - ) -} - -export { ChangeOrganization }; - diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/CredentialDetailsCard.js b/packages/gatsby-theme-aio/src/components/GetCredential/CredentialDetailsCard.js new file mode 100644 index 0000000000..54031b31df --- /dev/null +++ b/packages/gatsby-theme-aio/src/components/GetCredential/CredentialDetailsCard.js @@ -0,0 +1,247 @@ +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]) + + return ( + <> +
    +
    +
    +
    + +
    +

    {credentialName}

    +
    + {productList && } +
    +
    +
    + { + bool && +
    + {isCollapse ? : } +
    + } +
    + + {isCollapse && + <> +
    +
    + {accessToken && } + + {devConsoleLinkHeading && ( + + )} + + {clientDetails && ( + + )} + +
    + {nextButtonLabel && + + + } + {developerConsoleManage && + +
    +
    {developerConsoleManage}
    +
    + +
    +
    +
    + } +
    +
    + + } +
    +
    + + ); +}; diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/CredentialForm.js b/packages/gatsby-theme-aio/src/components/GetCredential/CredentialForm.js index 4cb17d01ab..dd9086bcbb 100644 --- a/packages/gatsby-theme-aio/src/components/GetCredential/CredentialForm.js +++ b/packages/gatsby-theme-aio/src/components/GetCredential/CredentialForm.js @@ -1,73 +1,78 @@ import React, { useContext, useEffect, useState } from 'react'; -import { css } from "@emotion/react"; +import { css } from '@emotion/react'; +import classNames from 'classnames'; import '@spectrum-css/contextualhelp/dist/index-vars.css'; -import classNames from "classnames"; -import { MyCredential } from './MyCredential'; -import { Loading } from "./Loading"; -import { IllustratedMessage } from "./IllustratedMessage"; -import { ChangeOrganization } from './ChangeOrganization'; -import { JoinBetaProgram } from './JoinBetaProgram'; -import { AlertIcon, FormFields, getOrganization, MAX_TABLET_SCREEN_WIDTH, MIN_MOBILE_WIDTH } from './FormFields'; -import { ContextHelp } from './ContextHelp'; import { Toast } from '../Toast'; -import { NoDeveloperAccessError } from './NoDeveloperAccessError'; -import Context from '../Context'; +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 hostnameRegex = /^(localhost:\d{1,5}|(\*\.|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+(\.[a-zA-Z]{2,})+)|\*|(\*\.[a-zA-Z0-9-]+(\.[a-zA-Z]{2,})+))$/; const credentialNameRegex = /^(?=[A-Za-z0-9\s]{6,}$)[A-Za-z0-9\s]*$/; -const CredentialForm = ({ formProps, credentialType, service }) => { +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 [showCreateForm, setShowCreateForm] = useState(true); + const [errResp, setErrorResp] = useState(''); const [showCredential, setShowCredential] = useState(false); const [formField, setFormField] = useState([]); - const [formData, setFormData] = useState({}); - const [modalOpen, setModalOpen] = useState(false); - const [redirectToBeta, setRedirectBetaProgram] = useState(false); const [isValid, setIsValid] = useState(false); + const [isMyCredential, setIsMyCredential] = useState(false); + const [isShow, setIsShow] = useState(false); const [alertShow, setAlertShow] = useState(false); - const [organizationChange, setOrganization] = useState(false); - const [organization, setOrganizationValue] = useState({}); - const [showOrganization, setShowOrganization] = useState(true); - const [emailID, setEmailID] = useState(''); - const { ims } = useContext(Context); + 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 getValueFromLocalStorage = async () => { - const orgInfo = localStorage?.getItem('OrgInfo'); - const getOrgs = await getOrganization(setOrganizationValue); - if (orgInfo === null) { - if (getOrgs?.length === 1) { - setShowOrganization(false); - } - } - else if (getOrgs) { - const orgData = JSON.parse(orgInfo); - setShowOrganization(orgData.orgLen === 1 ? false : true); - setOrganizationValue(orgData); - } - if (!getOrgs) { - setOrganizationValue({}); - setShowCreateForm(false) - } - } + 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 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 }) })); + 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 }; }); @@ -75,97 +80,98 @@ const CredentialForm = ({ formProps, credentialType, service }) => { 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 })); + setFormData(prevData => ({ + ...prevData, + Download: downloadObj.selectOptions[0]?.title, + zipUrl: downloadObj.selectOptions[0]?.href, + })); } } + if (productsObj?.productList.length) { + fields[Product] = productsObj; + } - setFormField(fields); - getValueFromLocalStorage(); + const isCredential = previousProjectDetail?.count ? true : false; + if (isCredential) { + setIsMyCredential(true); + } else { + setIsMyCredential(false); + } - } + setFormField(fields); + }; useEffect(() => { - setTimeout(() => { - setOrganization(false); - }, 8000); - }, [organizationChange]) + if (window.adobeIMS?.isSignedInUser()) { + setTimeout(() => { + setLoading(false); + }, 1000); + } else { + setLoading(true); + } + }, [window.adobeIMS?.isSignedInUser()]); useEffect(() => { if (showCreateForm) setIsError(false); - }, [showCreateForm]) + }, [showCreateForm]); - useEffect(() => { - if (!showCredential && showCreateForm) { - const updateForm = { ...formData }; - for (const key in updateForm) { - updateForm[key] = '' - }; - setFormData(updateForm); - setAlertShow(false); - } - }, [showCredential]) - useEffect(() => { initialLoad(); }, []); + useEffect(() => { + initialLoad(); + }, []); useEffect(() => { - if (!organization) { - setOrganizationValue(undefined); - setShowCreateForm(false); - setIsError(true) - } - else if (organization && Object.keys(organization)?.length !== 0) { - setShowCreateForm(true) - } - else { - if (Object.keys(organization)?.length === 0) { - setOrganizationValue(undefined); - setShowCreateForm(false); + 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']) } } - }, [organization]) - - useEffect(() => { - if (isError) { - const updateForm = { ...formData }; - for (const key in updateForm) { - updateForm[key] = '' - }; - setFormData(updateForm); + else { + isAllowedOriginsValid = true; } - }, [isError]) - - useEffect(() => { - const requiredFields = Array.from(credentialForm?.children || []).filter(child => child?.props?.required || child.type === CredentialName)?.map(child => child.type) - const isValidCredentialName = credentialNameRegex.test(formData.CredentialName) && formData['CredentialName']?.length >= 6; - const isCheckAllowedOrgins = requiredFields.filter((data) => data.name === "AllowedOrigins") - const validateAllowedOrigins = formData['AllowedOrigins']?.split(',').map((data) => hostnameRegex.test(data.trim())); - const isAllowedOriginsValid = isCheckAllowedOrgins ? validateAllowedOrigins?.every((value) => value === true) && formData["AllowedOrigins"] !== undefined && formData["AllowedOrigins"]?.length !== 0 : true; - - const isValid = isValidCredentialName && isAllowedOriginsValid && formData.Agree === true; + setIsAllowedOriginsValid(isAllowedOriginsValid) + const isValid = isValidCredentialName && (!isCheckAllowedOrgins || isAllowedOriginsValid) && formData.Agree === true; setIsValid(isValid); }, [formData]); - useEffect(() => { - (async () => { - if (ims && ims.isSignedInUser()) { - const profile = await ims.getProfile(); - setEmailID(profile?.email); - } - })(); - }, [ims]) - const handleChange = (e, type) => { - const value = (type === "Downloads" || type === "Agree") ? e.target.checked : e.target.value; + 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" && formData['Downloads']) { - const selectedData = formField?.[Download]?.selectOptions.find(data => data.title === e.target.value); + 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; @@ -176,379 +182,255 @@ const CredentialForm = ({ formProps, credentialType, service }) => { 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 = { - name: formData["CredentialName"], - platform: credentialType, + projectName: formData['CredentialName'], description: 'created for get credential', - domain: formData["AllowedOrigins"], - services: [{ sdkCode: service }], + metadata: { + domain: formData['AllowedOrigins'], + }, + orgId: selectedOrganization.code, + apis, }; try { - const response = await fetch(`/console/api/organizations/${organization?.id}/integrations/adobeid`, { - method: "POST", + 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, + '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(); + const resResp = await response?.json(); - if (response.status === 200) { + if (response.ok) { setResponse(resResp); setShowCredential(true); setAlertShow(true); - } else if (resResp?.messages) { - setAlertShow(true); - setIsValid(false); - setErrorResp(resResp?.messages[0]?.message); - setShowCreateForm(true); + setLoading(false); + } else { + const jsonString = resResp.errors[0].message.match(/\((\{.*\})\)/)[1]; + const errorDetails = JSON.parse(jsonString); + handleErrors(errorDetails.messages[0].message) } } catch (error) { - setIsError(true); - } finally { + setShowCreateForm(true); setLoading(false); + setAlertShow(true); + setErrorResp(error.message); + setIsError(true); } }; - const sideObject = formField?.[Side]; + 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 ( <> - {!redirectToBeta && showCreateForm && !loading && organization && + {showCreateForm && !loading && (
    + data-cy="credential-form" + >
    - {credentialForm?.title &&

    {credentialForm?.title}

    } - {credentialForm?.paragraph && + height: 100%; + text-align: left; + @media screen and (min-width: ${MIN_MOBILE_WIDTH}) and (max-width: ${MAX_TABLET_SCREEN_WIDTH}) { + padding: 0; + width: 100%; + } + `}> +
    + {credentialForm?.title && ( +

    + {credentialForm?.title} +

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

    {credentialForm?.paragraph}

    + )}

    - {credentialForm?.paragraph} + className="spectrum-Body spectrum-Body--sizeS" + css={css` + color: var(--spectrum-global-color-gray-800); + display: inline-flex; + `} + onClick={() => setIsShow(true)}> + {selectedOrganization.type === "developer" ? + "You're creating this credential in your personal developer organization" : + <>You're creating this credential in [{selectedOrganization?.name}].} +

    - } -

    - You're creating this credential in {organization?.type === "developer" ? "in your personal developer organization" : [{organization?.name}] }. - {showOrganization && - } -

    +
    + `}>
    + `}>
    - {credentialName && } - {allowedOrigins && } - {downloads && download && } - {formData['Downloads'] && download && } -
    - handleChange(e, 'Agree')} /> -

    {`By checking this box, you agree to `} - Adobe Developer Terms of Use. -

    -
    - + flex-direction: column; + `}> + {credentialName && ( + + )} + {allowedOrigins && ( + + )} + {downloads && download && ( + + )} + {formData['Downloads'] && download && ( + + )} + + {adobeDeveloperConsole && ( + + )} +
    - {sideObject ? : null} + {sideObject ? ( + + ) : null}
    -

    - Have existing credentials? - - Go to Developer Console - -

    - } - - {alertShow && + )} + {alertShow && ( <> - {organizationChange ? - : + { } - } - {loading && !showCredential && } - {modalOpen && ( - )} - {isError && !showCreateForm && !showCredential && } - {showCredential && !showCreateForm && } - {redirectToBeta && } - {!showCreateForm && !organization && !isError && } - - - ) -} - -const Side = ({ side }) => (side); - -const CredentialName = ({ nameProps, isFormValue, formData, handleChange }) => { - const inValidName = !credentialNameRegex.test(formData['CredentialName']) - const isRed = formData["CredentialName"]?.length !== 0 && inValidName; - return ( - -
    - handleChange(e, "CredentialName")} - placeholder={nameProps?.placeholder} - maxLength={nameProps?.range} + {isError && !showCreateForm && !showCredential && ( + + )} + {showCredential && !showCreateForm && ( + - -
    -
    - ) -} - -const AllowedOrigins = ({ originsProps, isFormValue, type, formData, handleChange }) => { - - const validateAllowedOrigins = formData['AllowedOrigins']?.split(',').map((data) => hostnameRegex.test(data.trim())); - const isAllowedOriginsValid = validateAllowedOrigins?.every((value) => value === true); - const isRed = formData["AllowedOrigins"] !== undefined && !isAllowedOriginsValid && formData["AllowedOrigins"]?.length !== 0; - - return ( - - - - ) -} - -const Downloads = ({ downloadsProp, handleChange, formData }) => { - const { label, contextHelpLabelForLink, contextHelpLink, contextHelpText, contextHelp, contextHelpHeading } = downloadsProp; - - return ( -
    - handleChange(e, "Downloads")} checked={formData['Downloads']} /> -

    {label}

    -
    - {contextHelp && } -
    -
    - ) -} - -const Download = ({ downloadProp, formData, isFormValue, handleChange }) => { - return ( - <> - { - downloadProp?.selectOptions?.length > 1 && - - - - } - - - ) -} - -const SideContent = ({ sideContent }) => { - return ( - <> -
    -
    - -
    + )} - ) -} + ); +}; -export { CredentialForm, Side, CredentialName, AllowedOrigins, Downloads, Download, SideContent }; \ No newline at end of file +export { CredentialForm }; diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/ErrorBoundary.js b/packages/gatsby-theme-aio/src/components/GetCredential/ErrorBoundary.js index 2982f334b2..b66f2abdc2 100644 --- a/packages/gatsby-theme-aio/src/components/GetCredential/ErrorBoundary.js +++ b/packages/gatsby-theme-aio/src/components/GetCredential/ErrorBoundary.js @@ -20,7 +20,6 @@ class ErrorBoundary extends Component { }); } - render() { if (this.state.hasError) { return ; diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/Form/AdobeDeveloperConsole.js b/packages/gatsby-theme-aio/src/components/GetCredential/Form/AdobeDeveloperConsole.js new file mode 100644 index 0000000000..b4005617d4 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Form/AllowedOrigins.js b/packages/gatsby-theme-aio/src/components/GetCredential/Form/AllowedOrigins.js new file mode 100644 index 0000000000..60aa82518e --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Form/CreateCredential.js b/packages/gatsby-theme-aio/src/components/GetCredential/Form/CreateCredential.js new file mode 100644 index 0000000000..3b38120ad2 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Form/CredentialName.js b/packages/gatsby-theme-aio/src/components/GetCredential/Form/CredentialName.js new file mode 100644 index 0000000000..eafbb7a25d --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Form/Download.js b/packages/gatsby-theme-aio/src/components/GetCredential/Form/Download.js new file mode 100644 index 0000000000..59e0de6f85 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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 '../../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/packages/gatsby-theme-aio/src/components/GetCredential/Form/Downloads.js b/packages/gatsby-theme-aio/src/components/GetCredential/Form/Downloads.js new file mode 100644 index 0000000000..13b511deaf --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/Form/SideContent.js b/packages/gatsby-theme-aio/src/components/GetCredential/Form/SideContent.js new file mode 100644 index 0000000000..6b5f102ed9 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/FormFields.js b/packages/gatsby-theme-aio/src/components/GetCredential/FormFields.js index a3d992f3b0..8ee080778d 100644 --- a/packages/gatsby-theme-aio/src/components/GetCredential/FormFields.js +++ b/packages/gatsby-theme-aio/src/components/GetCredential/FormFields.js @@ -17,8 +17,8 @@ export const FormFields = ({ isFormValue, fields, children, formData, isRed }) = width: 100%; `} > -
    - {label &&
    + `}> - {card?.children ? : null} + {card?.children ? ( + + ) : null}
    - {isCopiedTooltip && } + {isCopiedTooltip && ( + + )}
    - ) -} + ); +}; -export { MyCredential } +export { MyCredential }; diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/NoDeveloperAccessError.js b/packages/gatsby-theme-aio/src/components/GetCredential/NoDeveloperAccessError.js deleted file mode 100644 index b71e2dd3e0..0000000000 --- a/packages/gatsby-theme-aio/src/components/GetCredential/NoDeveloperAccessError.js +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useContext } from 'react'; -import Context from '../Context'; -import { css } from "@emotion/react"; - - -const NoDeveloperAccessError = ({ developerAccessError, title, emailID }) => { - - const { ims } = useContext(Context); - - return ( - <> - {title &&

    {title}

    } -

    - To create credentials, you need developer role permissions for the [Adobe Express Embed SDK]. -

    - - {developerAccessError && - -

    - {developerAccessError?.docsLinkText} -

    -
    - } -
    -

    - You're currently signed in as [{emailID}] -

    -

    - Have a personal account? Try to log in with that account to access your personal developer organization. -

    -
    - - - ) -} - -export { NoDeveloperAccessError }; \ No newline at end of file diff --git a/packages/gatsby-theme-aio/src/components/GetCredential/Organization.js b/packages/gatsby-theme-aio/src/components/GetCredential/Organization.js new file mode 100644 index 0000000000..6daa86f5c0 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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 '../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/packages/gatsby-theme-aio/src/components/GetCredential/PreviousCredential.js b/packages/gatsby-theme-aio/src/components/GetCredential/PreviousCredential.js new file mode 100644 index 0000000000..b062bf563c --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/PreviousProject.js b/packages/gatsby-theme-aio/src/components/GetCredential/PreviousProject.js new file mode 100644 index 0000000000..6d5520d7f3 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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 '../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/packages/gatsby-theme-aio/src/components/GetCredential/Products.js b/packages/gatsby-theme-aio/src/components/GetCredential/Products.js new file mode 100644 index 0000000000..333c5c2149 --- /dev/null +++ b/packages/gatsby-theme-aio/src/components/GetCredential/Products.js @@ -0,0 +1,241 @@ +import React, { useRef, useState, useEffect } from 'react'; +import { css } from '@emotion/react'; +import { ActionButton } from "../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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetails.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetails.js new file mode 100644 index 0000000000..5b95bd8004 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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 "../../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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsEdgeCase.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsEdgeCase.js new file mode 100644 index 0000000000..592986032f --- /dev/null +++ b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsEdgeCase.js @@ -0,0 +1,131 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import { Button } from "../../Button"; +import { Organization } from '../Organization'; + +import GetCredentialContext from '../GetCredentialContext'; +import Context from '../../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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsNoProduct.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsNoProduct.js new file mode 100644 index 0000000000..ff0329356e --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotMember.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotMember.js new file mode 100644 index 0000000000..46831b8d65 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotSignUp.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsNotSignUp.js new file mode 100644 index 0000000000..9d0df0cd04 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsType1User.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/OrganizationAccessDetailsType1User.js new file mode 100644 index 0000000000..b614a83af1 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/RequestAccess.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/RequestAccess.js new file mode 100644 index 0000000000..6c316bc337 --- /dev/null +++ b/packages/gatsby-theme-aio/src/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/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/RequestAccessModal.js b/packages/gatsby-theme-aio/src/components/GetCredential/RequestAccess/RequestAccessModal.js new file mode 100644 index 0000000000..d6854a195c --- /dev/null +++ b/packages/gatsby-theme-aio/src/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 "../../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; + } + `}> +