├── .distignore ├── .distinclude ├── .eslintrc.js ├── .github └── workflows │ ├── cloudflare-clear-cache.yml │ ├── create-milestones.yml │ ├── cypress-matrix.yml │ ├── cypress-tests-beta.yml │ ├── cypress.yml │ ├── delete-release.yml │ ├── eslint.yml │ ├── lint.yml │ ├── performance-cron.yml │ ├── satis-webhook.yml │ ├── upload-artifact-on-push.yml │ └── upload-asset-on-release.yml ├── .gitignore ├── .newfold-project.json ├── .npmrc ├── .nvmrc ├── .wp-env.json ├── LICENSE.md ├── README.md ├── assets ├── images │ ├── coming-soon.png │ ├── cs-bluehost-bg.jpg │ └── ecomm-addon-ctb-icon.png ├── styles │ └── coming-soon.css └── svg │ ├── bluehost-logo.svg │ ├── coming-soon.svg │ ├── settings.svg │ ├── web-hosting.svg │ └── website-content.svg ├── bluehost-wordpress-plugin.php ├── bootstrap.php ├── composer.json ├── composer.lock ├── cypress.config.js ├── inc ├── Admin.php ├── Data.php ├── LoginRedirect.php ├── RestApi │ ├── CachingController.php │ ├── SettingsController.php │ └── rest-api.php ├── YoastAI.php ├── alt-experience │ ├── index.html │ └── init.php ├── base.php ├── jetpack.php ├── partners.php ├── plugin-nfd-compat-check.php ├── plugin-php-compat-check.php ├── settings.php ├── updates.php └── upgrades │ ├── 1.4.3.php │ ├── 2.0.php │ ├── 2.11.0.php │ ├── 2.12.10.php │ ├── 2.12.7.php │ ├── 2.13.2.php │ ├── 2.2.2.php │ ├── 2.2.3.php │ ├── 2.5.0.php │ ├── 2.7.0.php │ ├── 3.0.10.php │ ├── 3.1.1.php │ ├── 3.3.3.php │ └── index.php ├── languages └── wp-plugin-bluehost.pot ├── package-lock.json ├── package.json ├── phpcs.xml ├── phpunit.xml ├── postcss.config.js ├── scripts ├── test-clean-module └── utils ├── set-latest-wp-version.js ├── set-version-bump.js ├── src ├── app │ ├── components │ │ ├── app-nav │ │ │ ├── index.js │ │ │ └── logo.js │ │ ├── errorCard │ │ │ ├── index.js │ │ │ └── stylesheet.scss │ │ ├── icons │ │ │ ├── BluehostIcon.js │ │ │ ├── HelpIcon.svg │ │ │ ├── WordPressIcon.js │ │ │ └── index.js │ │ ├── index.js │ │ ├── notifications │ │ │ └── index.js │ │ ├── site-info │ │ │ └── index.js │ │ └── webinars-banner │ │ │ └── index.js │ ├── data │ │ ├── help.js │ │ ├── routes.js │ │ └── store.js │ ├── images │ │ ├── loading-logo.svg │ │ ├── section-home-help-me.svg │ │ ├── section-home-welcome.svg │ │ └── webinars-vector.svg │ ├── index.js │ ├── pages │ │ ├── admin │ │ │ └── index.js │ │ ├── ecommerce │ │ │ ├── page.js │ │ │ └── styles.scss │ │ ├── help │ │ │ └── index.js │ │ ├── home │ │ │ ├── accountCard.js │ │ │ ├── freeAddonsSection.js │ │ │ ├── helpCard.js │ │ │ ├── index.js │ │ │ └── welcomeSection.js │ │ ├── marketplace │ │ │ └── index.js │ │ ├── pages-and-posts │ │ │ ├── ProductsPages.js │ │ │ ├── blogPosts.js │ │ │ ├── bookingAndAppointments.js │ │ │ ├── ecommAddonCTB.js │ │ │ ├── index.js │ │ │ └── sitePages.js │ │ ├── performance │ │ │ └── index.js │ │ ├── settings │ │ │ ├── automaticUpdates.js │ │ │ ├── comingSoon.js │ │ │ ├── commentSettings.js │ │ │ ├── contentSettings.js │ │ │ ├── helpCenterSettings.js │ │ │ ├── index.js │ │ │ ├── performanceFeatureSettings.js │ │ │ ├── socialMediaAccounts.js │ │ │ ├── stagingFeatureSettings.js │ │ │ └── wonderBlocksSettings.js │ │ ├── solutions │ │ │ ├── index.js │ │ │ └── style.scss │ │ └── staging │ │ │ ├── index.js │ │ │ └── stylesheet.scss │ ├── stylesheet.scss │ ├── tailwind.pcss │ └── util │ │ ├── helpers.js │ │ └── hooks │ │ ├── index.js │ │ ├── useContainerBlockTarget.js │ │ └── useHandlePageLoad.js ├── index.js └── webpack-public-path.js ├── tailwind.config.js ├── tests ├── cypress │ ├── fixtures │ │ ├── plugin-notifications.json │ │ ├── plugin-products.json │ │ ├── webinars-inactive.json │ │ ├── webinars-past.json │ │ └── webinars.json │ ├── integration │ │ ├── help.cy.js │ │ ├── home.cy.js │ │ ├── navigation.cy.js │ │ ├── pages-and-posts.cy.js │ │ ├── settings.cy.js │ │ └── version-check.cy.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ └── index.js └── phpunit │ ├── ExampleTest.php │ └── bootstrap.php └── webpack.config.js /.distignore: -------------------------------------------------------------------------------- 1 | # Directories 2 | .git 3 | .github 4 | /bin 5 | node_modules 6 | /scripts 7 | /src 8 | /tests 9 | 10 | # Files 11 | /.* 12 | cypress.config.js 13 | set-latest-wp-version.js 14 | webpack.config.js 15 | tailwind.config.js 16 | postcss.config.js 17 | 18 | # File Types 19 | /*.json 20 | /*.lock 21 | /*.log 22 | /*.md 23 | /*.sql 24 | /*.tar.gz 25 | /*.xml 26 | /*.yml 27 | /*.zip 28 | /*.scss 29 | 30 | # BEGIN NEWFOLD LABS RULES 31 | vendor/newfold-labs/*/src 32 | vendor/newfold-labs/*/source 33 | vendor/newfold-labs/*/tests 34 | vendor/newfold-labs/*/.* 35 | vendor/newfold-labs/*/*.xml 36 | vendor/newfold-labs/*/*.json 37 | vendor/newfold-labs/*/*.lock 38 | vendor/newfold-labs/*/*.scss 39 | vendor/newfold-labs/*/*.md 40 | vendor/newfold-labs/*/*.config.js 41 | vendor/newfold-labs/*/LICENSE 42 | -------------------------------------------------------------------------------- /.distinclude: -------------------------------------------------------------------------------- 1 | # Directories 2 | 3 | # Files 4 | /LICENSE.md 5 | 6 | # File Types 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 'plugin:@wordpress/eslint-plugin/recommended' ], 3 | settings: { 4 | 'import/resolver': { 5 | alias: { 6 | map: [ 7 | [ 'App', './src/app' ], 8 | [ 'Assets', './aseets' ], 9 | [ '@modules', './vendor/newfold-labs' ], 10 | ], 11 | extensions: [ '.js', '.jsx', '.json' ], 12 | }, 13 | }, 14 | }, 15 | globals: { 16 | __: true, 17 | _camelCase: true, 18 | _filter: true, 19 | _n: true, 20 | classNames: true, 21 | useContext: true, 22 | useEffect: true, 23 | useState: true, 24 | }, 25 | rules: { 26 | 'import/no-unresolved': [ 27 | 'error', 28 | { ignore: [ '^App/', '^Assets/' ] }, 29 | ], 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /.github/workflows/cloudflare-clear-cache.yml: -------------------------------------------------------------------------------- 1 | name: Cloudflare Clear Cache 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | clear-cache: 12 | name: Clear the Cloudflare cache 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Clear cache for release API 16 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' }} 17 | run: | 18 | curl -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/purge_cache" \ 19 | -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ 20 | -H "Content-Type: application/json" \ 21 | --data '{"files":["https://hiive.cloud/workers/release-api/plugins/bluehost/bluehost-wordpress-plugin"]}' 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/create-milestones.yml: -------------------------------------------------------------------------------- 1 | name: Create Milestone 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | due_date: 7 | description: "Provide the due date for the milestone (format: YYYY-MM-DD)" 8 | required: false 9 | default: "" 10 | schedule: 11 | - cron: '0 12 * * 3' # Every Wednesday at noon UTC 12 | 13 | permissions: 14 | issues: write 15 | contents: read 16 | 17 | jobs: 18 | create-milestone: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Create Milestone 23 | run: | 24 | #!/bin/bash 25 | 26 | # Explicitly set the timezone to UTC 27 | export TZ=UTC 28 | 29 | echo "Debug: Manually Entered Due Date: ${{ github.event.inputs.due_date }}" 30 | 31 | # Get the input date from workflow_dispatch (if provided) or use default logic 32 | if [ -n "${{ github.event.inputs.due_date }}" ]; then 33 | # Use the provided date from workflow_dispatch input 34 | DUE_DATE="${{ github.event.inputs.due_date }}" 35 | echo "Manual input provided. Using input date: $DUE_DATE" 36 | else 37 | # No manual input, use the default: 2 weeks from now 38 | DUE_DATE=$(date -u -d '2 weeks' '+%Y-%m-%d') 39 | echo "No manual input. Using default date 2 weeks from now: $DUE_DATE" 40 | fi 41 | 42 | # Set the date format for the milestone title and due date 43 | DATE=$(date -u -d "$DUE_DATE" '+%B %-d, %Y') # Human-readable date 44 | PR_DEADLINE=$(date -u -d "$DUE_DATE - 1 week" '+%B %-d, %Y') # Date 1 week before 45 | DATE_ISO8601=$(date -u -d "$DUE_DATE" '+%Y-%m-%dT23:59:59Z') # ISO8601 format 46 | 47 | # Prepare the description for the milestone 48 | DESCRIPTION="The release is scheduled for $DATE. PRs must be submitted and made mergeable by EOD $PR_DEADLINE (one week before release). This means they should have tests passing and merge conflicts resolved. The description for the PR must include details of what is included in the PR and how to test it, and preferably new tests supporting the update.\n\nAny PRs submitted after the deadline will not be included in the release but go in the next release. This includes PRs with tests failing, no description, no screenshots/video demonstrating the update, sufficient Cypress tests, no link to associated JIRA ticket, etc. If no mergeable PRs are submitted for a release, there will be no release that week (unless there is a need for an out-of-cycle release)." 49 | 50 | # Debugging outputs 51 | echo "Debug: Human-readable Date: $DATE" 52 | echo "Debug: ISO8601 Date: $DATE_ISO8601" 53 | echo "Debug: Description content: $DESCRIPTION" 54 | 55 | # Prepare the JSON payload for the API request 56 | PAYLOAD="{\"title\":\"$DATE Release\", \"description\":\"$DESCRIPTION\", \"due_on\":\"$DATE_ISO8601\", \"state\":\"open\"}" 57 | 58 | # Debugging the JSON payload 59 | echo "Debug: Payload: $PAYLOAD" 60 | 61 | # Perform the curl request and capture the response 62 | RESPONSE=$(curl -s -w "%{http_code}" -o response_body.txt -X POST \ 63 | -H "User-Agent: GitHub-API-Request" \ 64 | -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 65 | -H "Accept: application/vnd.github.v3+json" \ 66 | https://api.github.com/repos/${{ github.repository }}/milestones \ 67 | -d "$PAYLOAD") 68 | 69 | # Capture HTTP status code and response body 70 | HTTP_STATUS=$(tail -n1 <<< "$RESPONSE") 71 | RESPONSE_BODY=$(cat response_body.txt) 72 | 73 | # Debugging the HTTP status and response body 74 | echo "Debug: HTTP Status: $HTTP_STATUS" 75 | echo "Debug: Response Body: $RESPONSE_BODY" 76 | 77 | # If the request fails, exit with an error 78 | if [[ "$HTTP_STATUS" -ge 400 ]]; then 79 | echo "Error: Failed to create a milestone." 80 | exit 1 81 | fi 82 | 83 | echo "Milestone created successfully." 84 | echo "Response body: $RESPONSE_BODY" 85 | -------------------------------------------------------------------------------- /.github/workflows/cypress-matrix.yml: -------------------------------------------------------------------------------- 1 | name: Cypress Test Matrix 2 | 3 | on: 4 | pull_request: 5 | types: [ opened, edited, reopened, ready_for_review, synchronize ] 6 | branches: 7 | - main 8 | - master 9 | workflow_dispatch: 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | name: Run Cypress Test Matrix 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 60 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | phpVersion: 25 | - '7.3' 26 | - '7.4' 27 | - '8.0' 28 | - '8.1' 29 | - '8.2' 30 | - '8.3' 31 | wpVersion: 32 | - '6.5' 33 | - '6.6' 34 | - '6.7' 35 | 36 | steps: 37 | 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | 41 | - name: Setup PHP 42 | uses: shivammathur/setup-php@v2 43 | with: 44 | php-version: '8.1' 45 | coverage: none 46 | tools: composer, cs2pr 47 | 48 | - name: Setup workflow context 49 | id: workflow 50 | working-directory: ${{ runner.temp }} 51 | env: 52 | REPO: ${{ github.repository }} 53 | run: | 54 | mkdir dist 55 | echo "DIST=${PWD}/dist" >> $GITHUB_OUTPUT 56 | echo "PACKAGE=${REPO##*/}" >> $GITHUB_OUTPUT 57 | 58 | - name: Use Node.js 20.x 59 | uses: actions/setup-node@v4 60 | with: 61 | node-version: 20.x 62 | cache: 'npm' 63 | 64 | - name: Get Composer cache directory 65 | id: composer-cache 66 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 67 | 68 | - name: Cache Composer vendor directory 69 | uses: actions/cache@v4 70 | with: 71 | path: ${{ steps.composer-cache.outputs.dir }} 72 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 73 | restore-keys: | 74 | ${{ runner.os }}-composer- 75 | 76 | - name: Show versions 77 | run: | 78 | php --version 79 | composer --version 80 | node --version 81 | npm --version 82 | 83 | - name: Validate composer.json and composer.lock 84 | run: composer validate 85 | 86 | - name: Install PHP Dependencies 87 | run: composer install --no-progress --no-dev --optimize-autoloader --prefer-dist 88 | 89 | - name: Setup Registry 90 | run: printf "\n//npm.pkg.github.com/:_authToken=${{ secrets.NEWFOLD_ACCESS_TOKEN }}" >> .npmrc 91 | 92 | - name: NPM Install 93 | run: npm install --legacy-peer-deps 94 | 95 | - name: Build JavaScript 96 | run: npm run build 97 | 98 | - name: Create Distribution Copy 99 | run: rsync -r --include-from=.distinclude --exclude-from=.distignore . ${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }} 100 | 101 | - name: List Distribution Files 102 | working-directory: ${{ steps.workflow.outputs.DIST }} 103 | run: find . 104 | 105 | - name: Configure WordPress 106 | run: | 107 | echo '{"core": "WordPress/WordPress#tags/${{ matrix.wpVersion }}","phpVersion": "${{ matrix.phpVersion }}","plugins": [ "${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }}" ] }' > .wp-env.override.json 108 | 109 | - name: Configure Cypress 110 | run: | 111 | echo '{"wpVersion": "${{ matrix.wpVersion }}","phpVersion": "${{ matrix.phpVersion }}"}' > cypress.env.json 112 | 113 | - name: Install WordPress 114 | uses: nick-fields/retry@v3 115 | with: 116 | timeout_minutes: 4 117 | max_attempts: 3 118 | command: npx wp-env start --debug 119 | 120 | - name: Run Cypress Tests 121 | if: ${{ github.repository != 'bluehost/bluehost-wordpress-plugin' || github.actor == 'dependabot[bot]' }} 122 | run: npm run test:e2e -- --browser chrome 123 | 124 | - name: Run Cypress Tests 125 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' && github.actor != 'dependabot[bot]' }} 126 | run: npm run test:e2e -- --browser chrome --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --tag "bluehost,php-${{ matrix.phpVersion }},wp-${{ matrix.wpVersion }}" 127 | 128 | - name: Store screenshots of test failures 129 | if: ${{ failure() }} 130 | uses: actions/upload-artifact@v4 131 | with: 132 | name: screenshots 133 | path: ./tests/cypress/screenshots 134 | 135 | - name: Output debug.log file contents 136 | if: ${{ always() }} 137 | continue-on-error: true 138 | run: npx wp-env run wordpress "cat /var/www/html/wp-content/debug.log" 139 | -------------------------------------------------------------------------------- /.github/workflows/cypress-tests-beta.yml: -------------------------------------------------------------------------------- 1 | name: Cypress Tests Beta Only 2 | 3 | on: 4 | schedule: 5 | # Runs "Every Monday 6am UTC" 6 | - cron: '0 6 * * 1' 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | test: 15 | name: Run Cypress Beta Only Tests 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 45 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: '8.1' 27 | coverage: none 28 | tools: composer, cs2pr 29 | 30 | - name: Setup workflow context 31 | id: workflow 32 | working-directory: ${{ runner.temp }} 33 | env: 34 | REPO: ${{ github.repository }} 35 | run: | 36 | mkdir dist 37 | echo "DIST=${PWD}/dist" >> $GITHUB_OUTPUT 38 | echo "PACKAGE=${REPO##*/}" >> $GITHUB_OUTPUT 39 | - name: Use Node.js 20.x 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: 20.x 43 | cache: 'npm' 44 | 45 | - name: Get Composer cache directory 46 | id: composer-cache 47 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 48 | 49 | - name: Cache Composer vendor directory 50 | uses: actions/cache@v4 51 | with: 52 | path: ${{ steps.composer-cache.outputs.dir }} 53 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 54 | restore-keys: | 55 | ${{ runner.os }}-composer- 56 | - name: Show versions 57 | run: | 58 | php --version 59 | composer --version 60 | node --version 61 | npm --version 62 | - name: Validate composer.json and composer.lock 63 | run: composer validate 64 | 65 | - name: Install PHP Dependencies 66 | run: composer install --no-progress --no-dev --optimize-autoloader --prefer-dist 67 | 68 | - name: Setup Registry 69 | run: printf "\n//npm.pkg.github.com/:_authToken=${{ secrets.NEWFOLD_ACCESS_TOKEN }}" >> .npmrc 70 | 71 | - name: NPM Install 72 | run: npm install --legacy-peer-deps 73 | 74 | - name: Build JavaScript 75 | run: npm run build 76 | 77 | - name: Create Distribution Copy 78 | run: rsync -r --include-from=.distinclude --exclude-from=.distignore . ${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }} 79 | 80 | - name: List Distribution Files 81 | working-directory: ${{ steps.workflow.outputs.DIST }} 82 | run: find . 83 | 84 | - name: Fetch WordPress beta version 85 | run: | 86 | wp_beta_zip=$(node -e " 87 | const fetch = require('node-fetch'); 88 | fetch('https://api.wordpress.org/core/version-check/1.7/?channel=beta') 89 | .then(response => response.json()) 90 | .then(data => data.offers[0].download); 91 | ") 92 | echo $wp_beta_zip 93 | 94 | - name: Configure WordPress 95 | run: | 96 | echo '{ "core" : "'"${wp_beta_zip}"'" , "plugins":[ "${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }}" ] }' > .wp-env.override.json 97 | 98 | - name: Install WordPress 99 | run: npx wp-env start --debug 100 | 101 | - name: Run Cypress Tests 102 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' }} 103 | run: npm run test:e2e -- --browser chrome 104 | 105 | - name: Store screenshots of test failures 106 | if: failure() 107 | uses: actions/upload-artifact@v4 108 | with: 109 | name: screenshots 110 | path: ./tests/cypress/screenshots 111 | 112 | - name: Output debug.log file contents 113 | if: always() 114 | continue-on-error: true 115 | run: npx wp-env run wordpress "cat /var/www/html/wp-content/debug.log" 116 | -------------------------------------------------------------------------------- /.github/workflows/cypress.yml: -------------------------------------------------------------------------------- 1 | name: Cypress Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - trunk 9 | - develop 10 | - release/* 11 | - feature/* 12 | - add/* 13 | - update/* 14 | - fix/* 15 | - try/* 16 | pull_request: 17 | types: [ opened, edited, reopened, ready_for_review ] 18 | workflow_dispatch: 19 | 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | test: 26 | name: Run Cypress Tests 27 | runs-on: ubuntu-latest 28 | timeout-minutes: 45 29 | steps: 30 | 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Setup PHP 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: '8.1' 38 | coverage: none 39 | tools: composer, cs2pr 40 | 41 | - name: Setup workflow context 42 | id: workflow 43 | working-directory: ${{ runner.temp }} 44 | env: 45 | REPO: ${{ github.repository }} 46 | run: | 47 | mkdir dist 48 | echo "DIST=${PWD}/dist" >> $GITHUB_OUTPUT 49 | echo "PACKAGE=${REPO##*/}" >> $GITHUB_OUTPUT 50 | 51 | - name: Use Node.js 20.x 52 | uses: actions/setup-node@v4 53 | with: 54 | node-version: 20.x 55 | cache: 'npm' 56 | 57 | - name: Get Composer cache directory 58 | id: composer-cache 59 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 60 | 61 | - name: Cache Composer vendor directory 62 | uses: actions/cache@v4 63 | with: 64 | path: ${{ steps.composer-cache.outputs.dir }} 65 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 66 | restore-keys: | 67 | ${{ runner.os }}-composer- 68 | 69 | - name: Show versions 70 | run: | 71 | php --version 72 | composer --version 73 | node --version 74 | npm --version 75 | 76 | - name: Validate composer.json and composer.lock 77 | run: composer validate 78 | 79 | - name: Install PHP Dependencies 80 | run: composer install --no-progress --no-dev --optimize-autoloader --prefer-dist 81 | 82 | - name: Setup Registry 83 | run: printf "\n//npm.pkg.github.com/:_authToken=${{ secrets.NEWFOLD_ACCESS_TOKEN }}" >> .npmrc 84 | 85 | - name: NPM Install 86 | run: npm install --legacy-peer-deps 87 | 88 | - name: Build JavaScript 89 | run: npm run build 90 | 91 | - name: Create Distribution Copy 92 | run: rsync -r --include-from=.distinclude --exclude-from=.distignore . ${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }} 93 | 94 | - name: List Distribution Files 95 | working-directory: ${{ steps.workflow.outputs.DIST }} 96 | run: find . 97 | 98 | - name: Configure WordPress 99 | run: echo '{"plugins":["${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }}"]}' > .wp-env.override.json 100 | 101 | - name: Install WordPress 102 | uses: nick-fields/retry@v3 103 | with: 104 | timeout_minutes: 4 105 | max_attempts: 3 106 | command: npx wp-env start --debug 107 | 108 | - name: Run Cypress Tests 109 | if: ${{ github.repository != 'bluehost/bluehost-wordpress-plugin' || github.actor == 'dependabot[bot]' }} 110 | run: npm run test:e2e -- --browser chrome 111 | 112 | - name: Run Cypress Tests 113 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' && github.actor != 'dependabot[bot]' }} 114 | run: npm run test:e2e -- --browser chrome --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --tag bluehost 115 | 116 | - name: Store screenshots of test failures 117 | if: failure() 118 | uses: actions/upload-artifact@v4 119 | with: 120 | name: screenshots 121 | path: ./tests/cypress/screenshots 122 | 123 | - name: Output debug.log file contents 124 | if: always() 125 | continue-on-error: true 126 | run: npx wp-env run wordpress "cat /var/www/html/wp-content/debug.log" 127 | -------------------------------------------------------------------------------- /.github/workflows/delete-release.yml: -------------------------------------------------------------------------------- 1 | name: Delete Release 2 | 3 | on: 4 | release: 5 | types: 6 | - deleted 7 | 8 | jobs: 9 | delete: 10 | name: On Delete Release 11 | runs-on: ubuntu-latest 12 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' }} 13 | steps: 14 | 15 | - name: Clear cache for release API 16 | run: | 17 | curl -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/purge_cache" \ 18 | -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ 19 | -H "Content-Type: application/json" \ 20 | --data '{"files":["https://hiive.cloud/workers/release-api/plugins/bluehost/bluehost-wordpress-plugin"]}' 21 | -------------------------------------------------------------------------------- /.github/workflows/eslint.yml: -------------------------------------------------------------------------------- 1 | name: ESLint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - trunk 9 | - develop 10 | - release/* 11 | - feature/* 12 | - add/* 13 | - update/* 14 | - fix/* 15 | - try/*' 16 | paths: 17 | - 'src/**/*.js' 18 | pull_request: 19 | types: [ opened, edited, reopened, ready_for_review ] 20 | paths: 21 | - 'src/**/*.js' 22 | workflow_dispatch: 23 | 24 | concurrency: 25 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | ESLint: 30 | runs-on: ubuntu-latest 31 | steps: 32 | 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | 36 | - name: Setup PHP 37 | uses: shivammathur/setup-php@v2 38 | with: 39 | php-version: '8.1' 40 | coverage: none 41 | tools: composer, cs2pr 42 | 43 | - name: Setup workflow context 44 | id: workflow 45 | working-directory: ${{ runner.temp }} 46 | env: 47 | REPO: ${{ github.repository }} 48 | run: | 49 | mkdir dist 50 | echo "DIST=${PWD}/dist" >> $GITHUB_OUTPUT 51 | echo "PACKAGE=${REPO##*/}" >> $GITHUB_OUTPUT 52 | 53 | - name: Use Node.js 20.x 54 | uses: actions/setup-node@v4 55 | with: 56 | node-version: 20.x 57 | cache: 'npm' 58 | 59 | - name: Get Composer cache directory 60 | id: composer-cache 61 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 62 | 63 | - name: Cache Composer vendor directory 64 | uses: actions/cache@v4 65 | with: 66 | path: ${{ steps.composer-cache.outputs.dir }} 67 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 68 | restore-keys: | 69 | ${{ runner.os }}-composer- 70 | 71 | - name: Show versions 72 | run: | 73 | php --version 74 | composer --version 75 | node --version 76 | npm --version 77 | 78 | - name: Validate composer.json and composer.lock 79 | run: composer validate 80 | 81 | - name: Install PHP Dependencies 82 | run: composer install --no-progress --no-dev --optimize-autoloader --prefer-dist 83 | 84 | - name: Setup Registry 85 | run: printf "\n//npm.pkg.github.com/:_authToken=${{ secrets.NEWFOLD_ACCESS_TOKEN }}" >> .npmrc 86 | 87 | - name: NPM Install 88 | run: npm ci --legacy-peer-deps 89 | 90 | - name: ESLint 91 | run: npm run lint:js 92 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | paths: 7 | - '**.php' 8 | pull_request: 9 | types: [opened, edited, reopened, ready_for_review] 10 | paths: 11 | - '**.php' 12 | workflow_dispatch: 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | phpcs: 20 | name: Run PHP Code Sniffer 21 | runs-on: ubuntu-latest 22 | steps: 23 | 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | 27 | # User PHP 7.4 here for compatibility with the WordPress codesniffer rules. 28 | - name: Setup PHP 29 | uses: shivammathur/setup-php@v2 30 | with: 31 | php-version: '7.4' 32 | coverage: none 33 | tools: composer, cs2pr 34 | 35 | - uses: technote-space/get-diff-action@v6 36 | with: 37 | SUFFIX_FILTER: .php 38 | 39 | - name: Get Composer cache directory 40 | id: composer-cache 41 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 42 | if: "!! env.GIT_DIFF" 43 | 44 | - name: Cache Composer vendor directory 45 | uses: actions/cache@v4 46 | with: 47 | path: ${{ steps.composer-cache.outputs.dir }} 48 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 49 | restore-keys: | 50 | ${{ runner.os }}-composer- 51 | if: "!! env.GIT_DIFF" 52 | 53 | - name: Validate composer.json and composer.lock 54 | run: composer validate 55 | if: "!! env.GIT_DIFF" 56 | 57 | - name: Install dependencies 58 | run: composer install --no-progress --optimize-autoloader --prefer-dist 59 | if: "!! env.GIT_DIFF" 60 | 61 | - name: Detecting PHP Code Standards Violations 62 | run: vendor/bin/phpcs --standard=phpcs.xml -s ${{ env.GIT_DIFF }} 63 | if: "!! env.GIT_DIFF" 64 | -------------------------------------------------------------------------------- /.github/workflows/performance-cron.yml: -------------------------------------------------------------------------------- 1 | name: Visit Performance Test Site Every Thirty Minutes 2 | 3 | on: 4 | schedule: 5 | # Runs "every 30 minutes" (see https://crontab.guru) 6 | - cron: '*/30 * * * *' 7 | workflow_dispatch: 8 | jobs: 9 | visit-site: 10 | name: Visit the performance test site 11 | runs-on: ubuntu-latest 12 | environment: Performance Test Site 13 | steps: 14 | - name: Login 15 | run: | 16 | curl --cookie-jar cookies.txt --form log="${{ secrets.USERNAME }}" --form pwd="${{ secrets.PASSWORD }}" ${{ vars.BASE_URL }}/wp-login.php 17 | - name: Visit the posts page (3x) 18 | run: | 19 | curl -b cookies.txt ${{ vars.BASE_URL }}/wp-admin/edit.php -v 20 | sleep 5 21 | curl -b cookies.txt ${{ vars.BASE_URL }}/wp-admin/edit.php -v 22 | sleep 5 23 | curl -b cookies.txt ${{ vars.BASE_URL }}/wp-admin/edit.php -v 24 | -------------------------------------------------------------------------------- /.github/workflows/satis-webhook.yml: -------------------------------------------------------------------------------- 1 | name: Trigger Satis Build 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | webhook: 10 | name: Send Webhook 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Set Package 15 | id: package 16 | env: 17 | REPO: ${{ github.repository }} 18 | run: echo "PACKAGE=${REPO##*/}" >> $GITHUB_OUTPUT 19 | 20 | - name: Set Version 21 | id: tag 22 | run: echo "VERSION=${GITHUB_REF##*/}" >> $GITHUB_OUTPUT 23 | 24 | - name: Repository Dispatch 25 | uses: peter-evans/repository-dispatch@v3 26 | with: 27 | token: ${{ secrets.SATIS_WEBHOOK_TOKEN }} 28 | repository: bluehost/satis 29 | event-type: 'Trigger Satis Build' 30 | client-payload: >- 31 | { 32 | "vendor": "${{ github.repository_owner }}", 33 | "package": "${{ steps.package.outputs.PACKAGE }}", 34 | "version": "${{ steps.tag.outputs.VERSION }}" 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/upload-artifact-on-push.yml: -------------------------------------------------------------------------------- 1 | name: Build Plugin 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - develop 7 | - release/* 8 | - feature/* 9 | - add/* 10 | - update/* 11 | - fix/* 12 | - try/* 13 | - master-2.x 14 | - develop-2.x 15 | workflow_dispatch: 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | build: 23 | name: On Push 24 | runs-on: ubuntu-latest 25 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' }} 26 | steps: 27 | 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | 31 | - name: Validate version number 32 | if: ${{ (github.repository == 'bluehost/bluehost-wordpress-plugin') }} 33 | run: | 34 | pluginHeaderVersion=`grep "Version:" bluehost-wordpress-plugin.php | grep -Eo "[0-9\.]*"` 35 | pluginConstantVersion=`grep "'BLUEHOST_PLUGIN_VERSION'" bluehost-wordpress-plugin.php | grep -Eo "[0-9\.]*"` 36 | pluginPackageVersion=`grep '"version"' package.json | grep -Eo "[0-9\.]*"` 37 | echo "Plugin header version: $pluginHeaderVersion" 38 | echo "Plugin constant version: $pluginConstantVersion" 39 | echo "Plugin package version: $pluginPackageVersion" 40 | [[ "$pluginPackageVersion" == "$pluginHeaderVersion" ]] || exit 1 41 | [[ "$pluginPackageVersion" == "$pluginConstantVersion" ]] || exit 1 42 | 43 | - name: Setup PHP 44 | uses: shivammathur/setup-php@v2 45 | with: 46 | php-version: '8.1' 47 | coverage: none 48 | tools: composer, cs2pr 49 | 50 | - name: Setup workflow context 51 | id: workflow 52 | working-directory: ${{ runner.temp }} 53 | env: 54 | REPO: ${{ github.repository }} 55 | run: | 56 | mkdir dist 57 | echo "DIST=${PWD}/dist" >> $GITHUB_OUTPUT 58 | echo "PACKAGE=${REPO##*/}" >> $GITHUB_OUTPUT 59 | 60 | - name: Use Node.js 20.x 61 | uses: actions/setup-node@v4 62 | with: 63 | node-version: 20.x 64 | cache: 'npm' 65 | 66 | - name: Get Composer cache directory 67 | id: composer-cache 68 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 69 | 70 | - name: Cache Composer vendor directory 71 | uses: actions/cache@v4 72 | with: 73 | path: ${{ steps.composer-cache.outputs.dir }} 74 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 75 | restore-keys: | 76 | ${{ runner.os }}-composer- 77 | 78 | - name: Show versions 79 | run: | 80 | php --version 81 | composer --version 82 | node --version 83 | npm --version 84 | 85 | - name: Validate composer.json and composer.lock 86 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' }} 87 | run: composer validate 88 | 89 | - name: Install PHP Dependencies 90 | run: composer install --no-progress --no-dev --optimize-autoloader --prefer-dist 91 | 92 | - name: Setup Registry 93 | run: printf "\n//npm.pkg.github.com/:_authToken=${{ secrets.NEWFOLD_ACCESS_TOKEN }}" >> .npmrc 94 | 95 | - name: NPM Install 96 | run: npm install --legacy-peer-deps 97 | 98 | - name: Build JavaScript 99 | run: npm run build 100 | 101 | - name: Prepare files 102 | run: rsync -r --include-from=.distinclude --exclude-from=.distignore . ${{ steps.workflow.outputs.DIST }} 103 | 104 | - name: List Files 105 | working-directory: ${{ steps.workflow.outputs.DIST }} 106 | run: find . 107 | 108 | - uses: actions/upload-artifact@v4 109 | with: 110 | name: ${{ steps.workflow.outputs.PACKAGE }} 111 | path: ${{ steps.workflow.outputs.DIST }} 112 | -------------------------------------------------------------------------------- /.github/workflows/upload-asset-on-release.yml: -------------------------------------------------------------------------------- 1 | name: Package Plugin 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | env: 9 | VERSION: ${GITHUB_REF#refs/tags/*} 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | name: On Release 18 | runs-on: ubuntu-latest 19 | steps: 20 | 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Validate version number 25 | if: ${{ (github.repository == 'bluehost/bluehost-wordpress-plugin') && (github.event.release.prerelease == false) }} 26 | run: | 27 | taggedVersion=${{ env.VERSION }} 28 | pluginHeaderVersion=`grep "Version:" bluehost-wordpress-plugin.php | grep -Eo "[0-9\.]*"` 29 | pluginConstantVersion=`grep "'BLUEHOST_PLUGIN_VERSION'" bluehost-wordpress-plugin.php | grep -Eo "[0-9\.]*"` 30 | pluginPackageVersion=`grep '"version"' package.json | grep -Eo "[0-9\.]*"` 31 | echo "Tagged version: $taggedVersion" 32 | echo "Plugin header version: $pluginHeaderVersion" 33 | echo "Plugin constant version: $pluginConstantVersion" 34 | echo "Plugin package version: $pluginPackageVersion" 35 | [[ "$taggedVersion" == "$pluginHeaderVersion" ]] || exit 1 36 | [[ "$taggedVersion" == "$pluginConstantVersion" ]] || exit 1 37 | [[ "$taggedVersion" == "$pluginPackageVersion" ]] || exit 1 38 | 39 | - name: Setup PHP 40 | uses: shivammathur/setup-php@v2 41 | with: 42 | php-version: '8.1' 43 | coverage: none 44 | tools: composer, cs2pr 45 | 46 | - name: Setup workflow context 47 | id: workflow 48 | working-directory: ${{ runner.temp }} 49 | env: 50 | REPO: ${{ github.repository }} 51 | run: | 52 | mkdir dist 53 | echo "DIST=${PWD}/dist" >> $GITHUB_OUTPUT 54 | echo "PACKAGE=${REPO##*/}" >> $GITHUB_OUTPUT 55 | 56 | - name: Use Node.js 20.x 57 | uses: actions/setup-node@v4 58 | with: 59 | node-version: 20.x 60 | cache: 'npm' 61 | 62 | - name: Get Composer cache directory 63 | id: composer-cache 64 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 65 | 66 | - name: Cache Composer vendor directory 67 | uses: actions/cache@v4 68 | with: 69 | path: ${{ steps.composer-cache.outputs.dir }} 70 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 71 | restore-keys: | 72 | ${{ runner.os }}-composer- 73 | 74 | - name: Show versions 75 | run: | 76 | php --version 77 | composer --version 78 | node --version 79 | npm --version 80 | 81 | - name: Validate composer.json and composer.lock 82 | run: composer validate 83 | 84 | - name: Install PHP Dependencies 85 | run: composer install --no-progress --no-dev --optimize-autoloader --prefer-dist 86 | 87 | - name: Setup Registry 88 | run: printf "\n//npm.pkg.github.com/:_authToken=${{ secrets.NEWFOLD_ACCESS_TOKEN }}" >> .npmrc 89 | 90 | - name: NPM Install 91 | run: npm install --legacy-peer-deps 92 | 93 | - name: Validate WP Versions 94 | if: ${{ (github.repository == 'bluehost/bluehost-wordpress-plugin') && (github.event.release.prerelease == false) }} 95 | run: | 96 | pluginHeaderTestedVersion=`grep "Tested up to:" bluehost-wordpress-plugin.php | grep -Eo "[0-9\.]*"` 97 | wpEnvVersion=`grep "WordPress/WordPress#tags/" .wp-env.json | grep -Eo "[0-9\.]*"` 98 | echo "Plugin header tested version: $pluginHeaderTestedVersion" 99 | echo "wp-env version: $wpEnvVersion" 100 | [[ "$wpEnvVersion" == "$pluginHeaderTestedVersion" ]] || exit 1 101 | 102 | - name: Build JavaScript 103 | run: npm run build 104 | 105 | - name: Create Distribution Copy 106 | run: rsync -r --include-from=.distinclude --exclude-from=.distignore . ${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }} 107 | 108 | - name: List Distribution Files 109 | working-directory: ${{ steps.workflow.outputs.DIST }} 110 | run: find . 111 | 112 | - name: Create Zip 113 | working-directory: ${{ steps.workflow.outputs.DIST }} 114 | run: zip -r ${{ steps.workflow.outputs.PACKAGE }}.zip . 115 | 116 | - name: Upload Release Asset 117 | env: 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | run: | 120 | gh release upload "${{ github.event.release.tag_name }}" ${{ steps.workflow.outputs.DIST }}/${{ steps.workflow.outputs.PACKAGE }}.zip 121 | 122 | - name: Clear cache for release API 123 | if: ${{ github.repository == 'bluehost/bluehost-wordpress-plugin' }} 124 | run: | 125 | curl -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/purge_cache" \ 126 | -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ 127 | -H "Content-Type: application/json" \ 128 | --data '{"files":["https://hiive.cloud/workers/release-api/plugins/bluehost/bluehost-wordpress-plugin"]}' 129 | 130 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | /vendor 3 | 4 | #node_modules 5 | /node_modules 6 | 7 | # System and IDE files 8 | .DS_Store 9 | .idea 10 | 11 | # Cypress 12 | cypress.env.json 13 | /tests/cypress/screenshots 14 | /tests/cypress/videos 15 | 16 | # Built-app files 17 | /.docs 18 | /build/* 19 | /wp-plugin-bluehost 20 | /bluehost-wordpress-plugin 21 | 22 | # File Types 23 | *.log 24 | *.sql 25 | *.zip 26 | -------------------------------------------------------------------------------- /.newfold-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bluehost WordPress Plugin", 3 | "description": "WordPress plugin that integrates your WordPress site with the Bluehost control panel, including performance, security, and update features.", 4 | "url": "https://bluehost.com", 5 | "package": { 6 | "vendor": "newfold", 7 | "name": "wp-plugin-bluehost" 8 | }, 9 | "license": { 10 | "slug": "GPL-2.0-or-later", 11 | "name": "GPL 2.0 or later", 12 | "url": "https://www.gnu.org/licenses/gpl-2.0.html" 13 | }, 14 | "author": { 15 | "name": "Evan Mullins", 16 | "url": "https://evanmullins.com" 17 | }, 18 | "text_domain": "wp-plugin-bluehost", 19 | "namespace": "Bluehost", 20 | "prefixes": { 21 | "long": "WPPluginBluehost", 22 | "short": "wppbh", 23 | "function": "wppbh_", 24 | "constant": "WPPBH_", 25 | "meta": "wppbh_", 26 | "slug": "wppbh-" 27 | } 28 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @newfold-labs:registry=https://npm.pkg.github.com/ 2 | legacy-peer-deps=true -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": "WordPress/WordPress#tags/6.7.1", 3 | "config": { 4 | "WP_DEBUG": true, 5 | "WP_DEBUG_LOG": true, 6 | "WP_DEBUG_DISPLAY": false, 7 | "FS_METHOD": "direct" 8 | }, 9 | "phpVersion": "8.1", 10 | "plugins": [ 11 | "." 12 | ], 13 | "mappings": { 14 | "wp-content/plugins/wpforms-lite": "https://downloads.wordpress.org/plugin/wpforms-lite.latest-stable.zip" 15 | }, 16 | "themes": [ 17 | "https://downloads.wordpress.org/theme/yith-wonder.latest-stable.zip" 18 | ], 19 | "port": 8882, 20 | "testsPort": 8883, 21 | "env": { 22 | "tests": { 23 | "config": { 24 | "WP_SITEURL": "localhost:8881", 25 | "WP_TESTS_DOMAIN": "localhost:8881", 26 | "WP_TESTS_TITLE": "Bluehost WordPress Plugin", 27 | "WP_TESTS_BINARY": "php" 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /assets/images/coming-soon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluehost/bluehost-wordpress-plugin/fc48c1b74a0de94fb91fee91e3265774447df6ee/assets/images/coming-soon.png -------------------------------------------------------------------------------- /assets/images/cs-bluehost-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluehost/bluehost-wordpress-plugin/fc48c1b74a0de94fb91fee91e3265774447df6ee/assets/images/cs-bluehost-bg.jpg -------------------------------------------------------------------------------- /assets/images/ecomm-addon-ctb-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluehost/bluehost-wordpress-plugin/fc48c1b74a0de94fb91fee91e3265774447df6ee/assets/images/ecomm-addon-ctb-icon.png -------------------------------------------------------------------------------- /assets/svg/bluehost-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bluehost-wordpress-plugin.php: -------------------------------------------------------------------------------- 1 | min_php_version = '7.1'; 62 | $plugin_check->min_wp_version = '6.0'; 63 | 64 | $plugin_check->check_plugin_requirements(); 65 | } 66 | 67 | // Check NFD plugin incompatibilities 68 | require_once BLUEHOST_PLUGIN_DIR . '/inc/plugin-nfd-compat-check.php'; 69 | $nfd_plugins_check = new NFD_Plugin_Compat_Check( BLUEHOST_PLUGIN_FILE ); 70 | // Defer to Incompatible plugin, self-deactivate 71 | $nfd_plugins_check->incompatible_plugins = array(); 72 | // Deactivate legacy plugin 73 | $nfd_plugins_check->legacy_plugins = array( 74 | 'The MOJO Marketplace' => 'mojo-marketplace-wp-plugin/mojo-marketplace.php', 75 | 'The MOJO Plugin' => 'wp-plugin-mojo/wp-plugin-mojo.php', 76 | 'The HostGator Plugin' => 'wp-plugin-hostgator/wp-plugin-hostgator.php', 77 | 'The Web.com Plugin' => 'wp-plugin-web/wp-plugin-web.php', 78 | 'The Crazy Domains Plugin' => 'wp-plugin-web/wp-plugin-crazy-domains.php', 79 | ); 80 | // Check plugin requirements 81 | $pass_nfd_check = $nfd_plugins_check->check_plugin_requirements(); 82 | 83 | // Check PHP version before initializing to prevent errors if plugin is incompatible. 84 | if ( $pass_nfd_check && version_compare( PHP_VERSION, '7.1', '>=' ) ) { 85 | require __DIR__ . '/bootstrap.php'; 86 | } 87 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bluehost/bluehost-wordpress-plugin", 3 | "description": "WordPress plugin that integrates your WordPress site with the Bluehost control panel, including performance, security, and update features.", 4 | "type": "wordpress-plugin", 5 | "license": [ 6 | "GPL-2.0-or-later" 7 | ], 8 | "abandoned": "newfold-labs/wp-plugin-bluehost", 9 | "authors": [ 10 | { 11 | "name": "Evan Mullins", 12 | "homepage": "https://evanmullins.com" 13 | } 14 | ], 15 | "config": { 16 | "optimize-autoloader": true, 17 | "sort-packages": true, 18 | "platform": { 19 | "php": "7.3.0" 20 | }, 21 | "platform-check": false, 22 | "allow-plugins": { 23 | "dealerdirect/phpcodesniffer-composer-installer": true 24 | }, 25 | "preferred-install": { 26 | "newfold-labs/*": "source", 27 | "*": "dist" 28 | } 29 | }, 30 | "repositories": { 31 | "newfold": { 32 | "type": "composer", 33 | "url": "https://newfold-labs.github.io/satis/", 34 | "only": [ 35 | "newfold-labs/*" 36 | ] 37 | }, 38 | "instawp": { 39 | "type": "vcs", 40 | "url": "git@github.com:InstaWP/connect-helpers.git", 41 | "only": [ 42 | "instawp/*" 43 | ] 44 | } 45 | }, 46 | "scripts": { 47 | "fix": "vendor/bin/phpcbf --standard=phpcs.xml .", 48 | "lint": "vendor/bin/phpcs --standard=phpcs.xml -s .", 49 | "i18n-pot": "vendor/bin/wp i18n make-pot . ./languages/wp-plugin-bluehost.pot --headers='{\"Report-Msgid-Bugs-To\":\"https://github.com/bluehost/bluehost-wordpress-plugin/issues\",\"POT-Creation-Date\":\"2023-03-08T20:13:41+00:00\"}' --exclude=assets,tests,src", 50 | "i18n-po": "vendor/bin/wp i18n update-po ./languages/wp-plugin-bluehost.pot ./languages", 51 | "i18n-mo": "vendor/bin/wp i18n make-mo ./languages", 52 | "i18n-json": "rm -f languages/*.json && vendor/bin/wp i18n make-json ./languages --no-purge --pretty-print", 53 | "i18n": [ 54 | "@i18n-pot", 55 | "@i18n-po", 56 | "@i18n-mo", 57 | "@i18n-json" 58 | ] 59 | }, 60 | "scripts-descriptions": { 61 | "fix": "Automatically fix coding standards issues where possible.", 62 | "lint": "Check files against coding standards.", 63 | "i18n": "Generate new language files.", 64 | "i18n-pot": "Generate a .pot file for translation.", 65 | "i18n-po": "Update existing .po files.", 66 | "i18n-mo": "Generate new language .mo files.", 67 | "i18n-json": "Generate new language .json files." 68 | }, 69 | "require-dev": { 70 | "newfold-labs/wp-php-standards": "^1.2.4", 71 | "roave/security-advisories": "dev-latest", 72 | "wp-cli/i18n-command": "^2.6.3", 73 | "wp-phpunit/wp-phpunit": "^6.7.1" 74 | }, 75 | "require": { 76 | "newfold-labs/wp-module-activation": "^1.0.6", 77 | "newfold-labs/wp-module-atomic": "^1.3.0", 78 | "newfold-labs/wp-module-coming-soon": "^1.3.5", 79 | "newfold-labs/wp-module-context": "^1.0.1", 80 | "newfold-labs/wp-module-data": "^2.6.9", 81 | "newfold-labs/wp-module-deactivation": "^1.3.0", 82 | "newfold-labs/wp-module-ecommerce": "^1.5.1", 83 | "newfold-labs/wp-module-facebook": "^1.1.2", 84 | "newfold-labs/wp-module-features": "^1.4.2", 85 | "newfold-labs/wp-module-global-ctb": "^1.0.14", 86 | "newfold-labs/wp-module-help-center": "^2.2.3", 87 | "newfold-labs/wp-module-installer": "^1.3.0", 88 | "newfold-labs/wp-module-loader": "^1.0.12", 89 | "newfold-labs/wp-module-marketplace": "^2.5.0", 90 | "newfold-labs/wp-module-migration": "^1.1.0", 91 | "newfold-labs/wp-module-notifications": "^1.6.6", 92 | "newfold-labs/wp-module-onboarding": "^2.5.7", 93 | "newfold-labs/wp-module-onboarding-data": "^1.2.4", 94 | "newfold-labs/wp-module-patterns": "^2.8.1", 95 | "newfold-labs/wp-module-performance": "^2.4.1", 96 | "newfold-labs/wp-module-pls": "^1.0.1", 97 | "newfold-labs/wp-module-runtime": "^1.1.3", 98 | "newfold-labs/wp-module-secure-passwords": "^1.1.1", 99 | "newfold-labs/wp-module-solutions": "^1.0.8", 100 | "newfold-labs/wp-module-sso": "^1.0.8", 101 | "newfold-labs/wp-module-staging": "^2.1.3", 102 | "wp-forge/wp-update-handler": "^1.0.2", 103 | "wp-forge/wp-upgrade-handler": "^1.0" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require( 'cypress' ); 2 | const { phpVersion, core } = require( './.wp-env.json' ); 3 | const wpVersion = /[^/]*$/.exec( core )[ 0 ]; 4 | 5 | module.exports = defineConfig( { 6 | projectId: 'h78f39', 7 | env: { 8 | wpUsername: 'admin', 9 | wpPassword: 'password', 10 | wpVersion, 11 | phpVersion, 12 | pluginId: 'bluehost', 13 | appId: 'wppbh', 14 | pluginSlug: 'bluehost-wordpress-plugin', 15 | }, 16 | downloadsFolder: 'tests/cypress/downloads', 17 | fixturesFolder: 'tests/cypress/fixtures', 18 | screenshotsFolder: 'tests/cypress/screenshots', 19 | video: true, 20 | videosFolder: 'tests/cypress/videos', 21 | chromeWebSecurity: false, 22 | viewportWidth: 1024, 23 | viewportHeight: 768, 24 | blockHosts: [ 25 | '*doubleclick.net', 26 | '*jnn-pa.googleapis.com', 27 | '*youtube.com', 28 | ], 29 | e2e: { 30 | setupNodeEvents( on, config ) { 31 | // Ensure that the base URL is always properly set. 32 | if ( config.env && config.env.baseUrl ) { 33 | config.baseUrl = config.env.baseUrl; 34 | } 35 | 36 | // Ensure that we have a semantically correct WordPress version number for comparisons. 37 | if ( config.env.wpVersion ) { 38 | if ( config.env.wpVersion.split( '.' ).length !== 3 ) { 39 | config.env.wpSemverVersion = `${ config.env.wpVersion }.0`; 40 | } else { 41 | config.env.wpSemverVersion = config.env.wpVersion; 42 | } 43 | } 44 | 45 | // Ensure that we have a semantically correct PHP version number for comparisons. 46 | if ( config.env.phpVersion ) { 47 | if ( config.env.phpVersion.split( '.' ).length !== 3 ) { 48 | config.env.phpSemverVersion = `${ config.env.phpVersion }.0`; 49 | } else { 50 | config.env.phpSemverVersion = config.env.phpVersion; 51 | } 52 | } 53 | 54 | // Tests require Wondor Theme, exclude if not supported due to WP or PHP versions 55 | if ( ! supportsWonderTheme( config.env ) ) { 56 | config.excludeSpecPattern = config.excludeSpecPattern.concat( [ 57 | 'vendor/newfold-labs/wp-module-onboarding/tests/cypress/integration/**', // Onboarding requires Wonder Theme 58 | 'vendor/newfold-labs/wp-module-ecommerce/tests/cypress/integration/Home/ecommerce-next-steps.cy.js', // Requires Onboarding 59 | ] ); 60 | } 61 | 62 | // Tests requires Woo, so exclude if not supported due to WP or PHP versions 63 | if ( ! supportsWoo( config.env ) ) { 64 | config.excludeSpecPattern = config.excludeSpecPattern.concat( [ 65 | 'vendor/newfold-labs/wp-module-ecommerce/tests/cypress/integration/Site-Capabilities/**', 66 | 'vendor/newfold-labs/wp-module-ecommerce/tests/cypress/integration/Home/homePageWithWoo.cy.js', 67 | 'vendor/newfold-labs/wp-module-coming-soon/tests/cypress/integration/coming-soon-woo.cy.js', 68 | ] ); 69 | } 70 | 71 | // Test requires Jetpack, so exclude if not supported due to WP or PHP versions 72 | if ( ! supportsJetpack( config.env ) ) { 73 | config.excludeSpecPattern = config.excludeSpecPattern.concat( [ 74 | 'vendor/newfold-labs/wp-module-solutions/tests/cypress/integration/wp-plugins-installation-jetpack.cy.js', 75 | ] ); 76 | } 77 | 78 | return config; 79 | }, 80 | baseUrl: 'http://localhost:8882', 81 | specPattern: [ 82 | 'tests/cypress/integration/**/*.cy.{js,jsx,ts,tsx}', 83 | 'vendor/newfold-labs/**/tests/cypress/integration/**/*.cy.{js,jsx,ts,tsx}', 84 | ], 85 | supportFile: 'tests/cypress/support/index.js', 86 | testIsolation: false, 87 | excludeSpecPattern: [ 88 | 'vendor/newfold-labs/**/tests/cypress/integration/wp-module-support/*.cy.js', // skip any module's wp-module-support files 89 | 'vendor/newfold-labs/wp-module-migration/**/*.cy.js', // temporarily skip the broken migration test 90 | ], 91 | experimentalRunAllSpecs: true, 92 | }, 93 | retries: 1, 94 | experimentalMemoryManagement: true, 95 | } ); 96 | 97 | // Check against plugin support at https://wordpress.org/plugins/woocommerce/ 98 | const supportsWoo = ( env ) => { 99 | const semver = require( 'semver' ); 100 | if ( 101 | semver.satisfies( env.wpSemverVersion, '>=6.6.0' ) && 102 | semver.satisfies( env.phpSemverVersion, '>=7.4.0' ) 103 | ) { 104 | return true; 105 | } 106 | return false; 107 | }; 108 | // Check against plugin support at https://wordpress.org/plugins/jetpack/ 109 | const supportsJetpack = ( env ) => { 110 | const semver = require( 'semver' ); 111 | if ( 112 | semver.satisfies( env.wpSemverVersion, '>=6.6.0' ) && 113 | semver.satisfies( env.phpSemverVersion, '>=7.2.0' ) 114 | ) { 115 | return true; 116 | } 117 | return false; 118 | }; 119 | // Check against theme support at https://github.com/newfold-labs/yith-wonder/blob/master/style.css 120 | const supportsWonderTheme = ( env ) => { 121 | const semver = require( 'semver' ); 122 | if ( 123 | semver.satisfies( env.wpSemverVersion, '>=6.5.0' ) && 124 | semver.satisfies( env.phpSemverVersion, '>=7.0.0' ) 125 | ) { 126 | return true; 127 | } 128 | return false; 129 | }; 130 | -------------------------------------------------------------------------------- /inc/Data.php: -------------------------------------------------------------------------------- 1 | array( 26 | 'url' => BLUEHOST_BUILD_URL, 27 | 'version' => BLUEHOST_PLUGIN_VERSION, 28 | 'assets' => BLUEHOST_PLUGIN_URL . 'assets/', 29 | 'brand' => $bluehost_module_container->plugin()->brand, 30 | ), 31 | ); 32 | 33 | return $runtime; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /inc/LoginRedirect.php: -------------------------------------------------------------------------------- 1 | has_cap( 'manage_options' ); 112 | } 113 | 114 | /** 115 | * Check if the current redirect is to the Bluehost plugin. 116 | * 117 | * @param string $redirect The current redirect URL. 118 | * 119 | * @return bool 120 | */ 121 | public static function is_bluehost_redirect( $redirect ) { 122 | return false !== strpos( $redirect, admin_url( 'admin.php?page=bluehost' ) ); 123 | } 124 | 125 | /** 126 | * Get the Bluehost dashboard URL. 127 | * 128 | * @return string 129 | */ 130 | public static function get_bluehost_dashboard_url() { 131 | return admin_url( 'admin.php?page=bluehost#/home' ); 132 | } 133 | } 134 | 135 | LoginRedirect::init(); 136 | -------------------------------------------------------------------------------- /inc/RestApi/CachingController.php: -------------------------------------------------------------------------------- 1 | namespace, 34 | '/caching', 35 | array( 36 | 'methods' => \WP_REST_Server::DELETABLE, 37 | 'callback' => array( $this, 'purge_all' ), 38 | 'permission_callback' => array( $this, 'check_permission' ), 39 | ) 40 | ); 41 | } 42 | 43 | /** 44 | * Clears the entire cache 45 | */ 46 | public function purge_all() { 47 | 48 | container()->get( 'cachePurger' )->purgeAll(); 49 | 50 | return array( 51 | 'status' => 'success', 52 | 'message' => 'Cache purged', 53 | ); 54 | } 55 | 56 | /** 57 | * Check permissions for route. 58 | * 59 | * @return bool|\WP_Error 60 | */ 61 | public function check_permission() { 62 | if ( ! current_user_can( 'manage_options' ) ) { 63 | return new \WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to access this endpoint.', 'wp-plugin-bluehost' ), array( 'status' => rest_authorization_required_code() ) ); 64 | } 65 | 66 | return true; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /inc/RestApi/rest-api.php: -------------------------------------------------------------------------------- 1 | register_routes(); 28 | } 29 | } 30 | 31 | add_action( 'rest_api_init', __NAMESPACE__ . '\\init_rest_api' ); 32 | -------------------------------------------------------------------------------- /inc/YoastAI.php: -------------------------------------------------------------------------------- 1 | 'event', 59 | 'ec' => 'plugin_status', 60 | 'ea' => 'installed', 61 | 'el' => 'Install date: ' . get_option( 'mm_install_date', date( 'M d, Y' ) ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date 62 | 'keep' => false, 63 | ); 64 | $events = get_option( 'mm_cron', array() ); 65 | $events['hourly'][ $event['ea'] ] = $event; 66 | update_option( 'mm_cron', $events ); 67 | } 68 | if ( ! bluehost_has_plugin_install_date() ) { 69 | $date = false; 70 | if ( ! empty( $install_date ) ) { 71 | $date = \DateTime::createFromFormat( 'M d, Y', $install_date ); 72 | } 73 | bluehost_set_plugin_install_date( $date ? $date->format( 'U' ) : gmdate( 'U' ) ); 74 | } 75 | } 76 | 77 | add_action( 'admin_init', __NAMESPACE__ . '\\bluehost_setup' ); 78 | 79 | 80 | /** 81 | * Filter the date used in data module 82 | * 83 | * @param string $install_date value from hook 84 | * @return int 85 | */ 86 | function bluehost_install_date_filter( $install_date ) { 87 | return bluehost_get_plugin_install_date(); 88 | } 89 | add_filter( 'nfd_install_date_filter', __NAMESPACE__ . '\\bluehost_install_date_filter' ); 90 | -------------------------------------------------------------------------------- /inc/jetpack.php: -------------------------------------------------------------------------------- 1 | nfd_data_queue_lock 12 | if ( get_option( 'bh_data_queue_lock' ) ) { 13 | add_option( 'nfd_data_queue_lock', get_option( 'bh_data_queue_lock' ) ); 14 | delete_option( 'bh_data_queue_lock' ); 15 | } 16 | 17 | // option: bh_data_queue > nfd_data_queue 18 | if ( get_option( 'bh_data_queue' ) ) { 19 | add_option( 'nfd_data_queue', get_option( 'bh_data_queue' ) ); 20 | delete_option( 'bh_data_queue' ); 21 | } 22 | 23 | // option: bh_data_connection_attempts > nfd_data_connection_attempts 24 | if ( get_option( 'bh_data_connection_attempts' ) ) { 25 | add_option( 'nfd_data_connection_attempts', get_option( 'bh_data_connection_attempts' ) ); 26 | delete_option( 'bh_data_connection_attempts' ); 27 | } 28 | 29 | // option: bh_data_token > nfd_data_token 30 | if ( get_option( 'bh_data_token' ) ) { 31 | add_option( 'nfd_data_token', get_option( 'bh_data_token' ) ); 32 | delete_option( 'bh_data_token' ); 33 | } 34 | -------------------------------------------------------------------------------- /inc/upgrades/2.12.10.php: -------------------------------------------------------------------------------- 1 | helpers->options->get( 'enable_ai_generator', null ) !== null ) { 12 | yoastseo()->helpers->options->set( 'enable_ai_generator', true ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /inc/upgrades/3.3.3.php: -------------------------------------------------------------------------------- 1 | purgeAll(); 11 | -------------------------------------------------------------------------------- /inc/upgrades/index.php: -------------------------------------------------------------------------------- 1 | =20", 32 | "npm": ">=10" 33 | }, 34 | "dependencies": { 35 | "@heroicons/react": "^2.2.0", 36 | "@newfold/ui-component-library": "^1.1.1", 37 | "@newfold/wp-module-ecommerce": "^1.5.1", 38 | "@newfold/wp-module-facebook": "^1.1.2", 39 | "@newfold/wp-module-runtime": "^1.1.3", 40 | "@reduxjs/toolkit": "^2.5.0", 41 | "@wordpress/compose": "^7.16.0", 42 | "@wordpress/dom-ready": "^4.16.0", 43 | "@wordpress/element": "^6.15.1", 44 | "@wordpress/html-entities": "^4.16.0", 45 | "@wordpress/i18n": "^5.16.0", 46 | "@wordpress/icons": "^10.16.0", 47 | "@wordpress/url": "^4.16.0", 48 | "classnames": "^2.5.1", 49 | "html-react-parser": "^5.2.2", 50 | "jquery": "^3.7.1", 51 | "lodash": "^4.17.21", 52 | "react": "^18.2.0", 53 | "react-error-boundary": "^5.0.0", 54 | "react-router-dom": "^7.1.3", 55 | "react-use": "^17.6.0", 56 | "semver": "^7.6.3" 57 | }, 58 | "devDependencies": { 59 | "@tailwindcss/forms": "^0.5.10", 60 | "@testing-library/cypress": "^10.0.3", 61 | "@wordpress/env": "^10.16.0", 62 | "@wordpress/eslint-plugin": "^22.1.1", 63 | "@wordpress/scripts": "^27", 64 | "cypress": "^14.0.0", 65 | "cypress-axe": "^1.5.0", 66 | "eslint-import-resolver-alias": "^1.1.2", 67 | "eslint-plugin-import": "^2.29.1", 68 | "node-fetch": "^2.7.0", 69 | "tailwindcss": "^3.4.17" 70 | }, 71 | "scripts": { 72 | "build": "NODE_ENV=production wp-scripts build", 73 | "check-engines": "wp-scripts check-engines", 74 | "check-licenses": "wp-scripts check-licenses --production", 75 | "create:dev": "rm -rf ./bluehost-wordpress-plugin ./bluehost-wordpress-plugin.zip && npm run create:dist && npm run create:zip", 76 | "create:dist": "rsync -r --include-from=.distinclude --exclude-from=.distignore . ./bluehost-wordpress-plugin", 77 | "create:zip": "cd ./bluehost-wordpress-plugin && zip -r -9 ../bluehost-wordpress-plugin.zip . && ls -lh ../bluehost-wordpress-plugin.zip", 78 | "cypress": "npm cypress open", 79 | "develop": "npm run start", 80 | "develop:analyzer": "npm run start:analyzer", 81 | "i18n": "wpi18n addtextdomain && composer run i18n", 82 | "lint:css": "wp-scripts lint-style '**/*.css'", 83 | "lint:js": "wp-scripts lint-js ./src", 84 | "lint:js:fix": "wp-scripts lint-js ./src --fix", 85 | "lint:pkg-json": "wp-scripts lint-pkg-json", 86 | "lint:yml": "yamllint --ignore=node_modules/** --ignore=vendor/** **/*.yml", 87 | "log:watch": "wp-env run wordpress 'tail -f /var/www/html/wp-content/debug.log'", 88 | "php-deps": "composer install --no-dev --optimize-autoloader", 89 | "postprepare": "npm run set-wp-version", 90 | "prebuild:cleanup": "rm -rf ./build ./bluehost-wordpress-plugin ./bluehost-wordpress-plugin.zip ./vendor", 91 | "set-version-bump": "node ./set-version-bump.js && npm i && rm -rf ./build && npm run build && composer run i18n", 92 | "set-wp-version": "node ./set-latest-wp-version.js", 93 | "simulate-runner-build": "npm run prebuild:cleanup && npm i --legacy-peer-deps && npm run php-deps && npm run build && npm run create:dist && npm run create:zip", 94 | "srb": "npm run simulate-runner-build", 95 | "start": "NODE_ENV=develop wp-scripts start", 96 | "start:analyzer": "npm run start --webpack-bundle-analyzer", 97 | "storybook:dev": "start-storybook -c ./storybook", 98 | "storybook:build": "build-storybook -c ./storybook -o ./.docs", 99 | "test:e2e": "npx cypress run", 100 | "test:unit": "wp-scripts test-unit-js", 101 | "test:clean-module": "bash scripts/test-clean-module" 102 | } 103 | } -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | /assets 7 | /build 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | tests/phpunit 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require( 'postcss-import' ), 4 | require( 'tailwindcss/nesting' ), 5 | require( 'tailwindcss' ), 6 | ...require( '@wordpress/postcss-plugins-preset' ), 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /scripts/test-clean-module: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script runs Cypress clean tests (reset db before test) for a specific module. 4 | 5 | # Source utils 6 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 7 | cd "$SCRIPT_DIR" 8 | source ./utils 9 | 10 | # Project root 11 | cd ../ 12 | 13 | # Check if a module is provided 14 | if [ -z "$1" ]; then 15 | print_error "No module provided." 16 | echo "Usage: npm run test:clean-module -- " 17 | exit 1 18 | fi 19 | 20 | MODULE=$1 21 | MODULE_DIR="vendor/newfold-labs/$MODULE" 22 | 23 | # Check if the module exists 24 | if [ ! -d "$MODULE_DIR" ]; then 25 | print_error "Can't find $MODULE in vendor/newfold-labs." 26 | exit 1 27 | fi 28 | 29 | # Check if the module has tests 30 | if ! find "$MODULE_DIR/tests" -type f -name "*.cy.js" 2>/dev/null | grep -q .; then 31 | print_error "Can't find any test files in $MODULE_DIR." 32 | exit 1 33 | fi 34 | 35 | # Warn the user that the database will be reset 36 | confirm_prompt "${YELLOW}Running a clean test will reset the database. Proceed?${TEXT}" 37 | 38 | # Check if the answer is "y" or "Y" 39 | if [[ $REPLY =~ ^[Yy]$ ]]; then 40 | echo -e "Proceeding with tests for ${CYAN}${MODULE}${TEXT}..." 41 | 42 | # Run the commands 43 | wp-env stop 44 | wp-env clean all 45 | wp-env start 46 | npm run test:e2e -- --browser chrome --spec "vendor/(newfold-labs/$MODULE/tests/**/*.cy.js)" 47 | else 48 | echo "Cancelled." 49 | exit 0 50 | fi 51 | -------------------------------------------------------------------------------- /scripts/utils: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Colors 4 | GREEN='\033[0;32m' 5 | TEXT='\033[0m' 6 | BLUE='\033[0;34m' 7 | RED='\033[0;31m' 8 | CYAN='\033[0;36m' 9 | YELLOW='\033[0;33m' 10 | 11 | print_success() { 12 | echo -e "${GREEN}Success:${TEXT} $1" 13 | } 14 | 15 | print_error() { 16 | echo -e "${RED}Error:${TEXT} $1" 17 | } 18 | 19 | print_info() { 20 | echo -e "${CYAN}$1" 21 | } 22 | 23 | print_warning() { 24 | echo -e "${YELLOW}Warning:${TEXT} $1" 25 | } 26 | 27 | confirm_prompt() { 28 | local message=$1 29 | read -p "$(echo -e ${message}) (y/n): " -n 1 -r 30 | echo # Move to a new line 31 | [[ $REPLY =~ ^[Yy]$ ]] # Returns 0 if y/Y is entered, 1 otherwise 32 | } 33 | -------------------------------------------------------------------------------- /set-latest-wp-version.js: -------------------------------------------------------------------------------- 1 | const fs = require( 'fs' ); 2 | const fetch = require( 'node-fetch' ); 3 | const wpEnv = require( './.wp-env.json' ); 4 | 5 | fetch( 'https://api.wordpress.org/core/stable-check/1.0/' ) 6 | .then( ( res ) => res.json() ) 7 | .then( ( json ) => { 8 | const wpVersion = Object.keys( json )[ Object.keys( json ).length - 1 ]; 9 | wpEnv.core = `WordPress/WordPress#tags/${ wpVersion }`; 10 | fs.writeFile( 11 | './.wp-env.json', 12 | JSON.stringify( wpEnv, null, 2 ), 13 | 'utf8', 14 | ( err ) => { 15 | if ( err ) { 16 | console.log( 17 | 'An error occurred while writing latest WordPress version to .wp-env.json file.' 18 | ); 19 | return console.log( err ); 20 | } 21 | console.log( 22 | `The .wp-env.json file was updated with the latest WordPress version (${ wpVersion }).` 23 | ); 24 | } 25 | ); 26 | } ); 27 | -------------------------------------------------------------------------------- /set-version-bump.js: -------------------------------------------------------------------------------- 1 | const fs = require( 'fs' ); 2 | const semver = require( 'semver' ); 3 | const packagefile = './package.json'; 4 | const pluginfile = './bluehost-wordpress-plugin.php'; 5 | 6 | if ( fs.existsSync( packagefile ) && fs.existsSync( pluginfile ) ) { 7 | const packageData = require( packagefile ); 8 | const currentVersion = packageData.version; 9 | let type = process.argv[ 2 ]; 10 | if ( ! [ 'major', 'minor', 'patch' ].includes( type ) ) { 11 | type = 'patch'; 12 | } 13 | 14 | const newVersion = semver.inc( packageData.version, type ); 15 | packageData.version = newVersion; 16 | fs.writeFileSync( packagefile, JSON.stringify( packageData, null, 4 ) ); 17 | 18 | fs.readFile( pluginfile, 'utf8', function ( err, data ) { 19 | if ( err ) { 20 | return console.log( err ); 21 | } 22 | const result = data.replaceAll( currentVersion, newVersion ); 23 | 24 | fs.writeFile( pluginfile, result, 'utf8', function ( err ) { 25 | if ( err ) { 26 | return console.log( err ); 27 | } 28 | } ); 29 | } ); 30 | 31 | console.log( 'Version updated', currentVersion, '=>', newVersion ); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/components/app-nav/logo.js: -------------------------------------------------------------------------------- 1 | import { Button } from '@wordpress/components'; 2 | import { Title } from '@newfold/ui-component-library'; 3 | import { ReactComponent as Brand } from 'Assets/svg/bluehost-logo.svg'; 4 | import { delay } from 'lodash'; 5 | 6 | const Mark = () => { 7 | const defocus = () => { 8 | const button = document.querySelector( '.logo-mark' ); 9 | delay( () => { 10 | if ( null !== button ) { 11 | button.blur(); 12 | } 13 | }, 500 ); 14 | }; 15 | return ( 16 | 61 | 75 | 76 | 77 | 78 | ); 79 | }; 80 | -------------------------------------------------------------------------------- /src/app/data/help.js: -------------------------------------------------------------------------------- 1 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 2 | import { getPlatformBaseUrl } from '../util/helpers'; 3 | 4 | const getSupportPhoneNumber = () => { 5 | const brand = NewfoldRuntime.plugin.brand; 6 | 7 | if ( brand === 'Bluehost_India' ) { 8 | return '1800-419-4426'; 9 | } 10 | 11 | return '888-401-4678'; 12 | }; 13 | const help = [ 14 | { 15 | name: 'phone', 16 | title: __( 'Phone', 'wp-plugin-bluehost' ), 17 | // eslint-disable-next-line @wordpress/i18n-no-variables 18 | description: __( 19 | `Contact one of our friendly Customer Care Specialists, as we are waiting to help at ${ getSupportPhoneNumber() }. Open 24 hours - 7 days.`, 20 | 'wp-plugin-bluehost' 21 | ), 22 | icon: false, 23 | cta: __( 'Call Us', 'wp-plugin-bluehost' ), 24 | url: 'tel:' + getSupportPhoneNumber(), 25 | }, 26 | { 27 | name: 'chat', 28 | title: __( 'Chat', 'wp-plugin-bluehost' ), 29 | description: __( 30 | 'Chat with one of our friendly Customer Care Specialists, as we are waiting to help. Open 24 hours - 7 days.', 31 | 'wp-plugin-bluehost' 32 | ), 33 | icon: false, 34 | cta: __( 'Live Chat', 'wp-plugin-bluehost' ), 35 | url: 36 | getPlatformBaseUrl() + 37 | '/contact/?utm_campaign=&utm_content=help_chat_link&utm_term=live_chat&utm_medium=brand_plugin&utm_source=wp-admin/admin.php?page=bluehost#/help', 38 | }, 39 | { 40 | name: 'twitter', 41 | title: __( 'Tweet', 'wp-plugin-bluehost' ), 42 | description: __( 43 | 'Find our team at @bluehost for updates on our products and support from our team.', 44 | 'wp-plugin-bluehost' 45 | ), 46 | icon: false, 47 | cta: __( 'Tweet Us', 'wp-plugin-bluehost' ), 48 | url: 'https://twitter.com/bluehost', 49 | }, 50 | { 51 | name: 'youtube', 52 | title: __( 'YouTube', 'wp-plugin-bluehost' ), 53 | description: __( 54 | 'Find tutorials, answers, interviews and guides on our YouTube channel.', 55 | 'wp-plugin-bluehost' 56 | ), 57 | icon: false, 58 | cta: __( 'Watch Now', 'wp-plugin-bluehost' ), 59 | url: 'http://www.youtube.com/user/bluehost', 60 | }, 61 | { 62 | name: 'kb', 63 | title: __( 'Knowledge Base', 'wp-plugin-bluehost' ), 64 | description: __( 65 | "Articles, guides, how-tos, instructions, and answers to our client's most frequently asked questions.", 66 | 'wp-plugin-bluehost' 67 | ), 68 | icon: false, 69 | cta: __( 'Visit Knowledge Base', 'wp-plugin-bluehost' ), 70 | url: 71 | getPlatformBaseUrl() + 72 | '/help/?utm_campaign=&utm_content=help_help_link&utm_term=we_can_help&utm_medium=brand_plugin&utm_source=wp-admin/admin.php?page=bluehost#/help', 73 | }, 74 | { 75 | name: 'resources', 76 | title: __( 'Resources', 'wp-plugin-bluehost' ), 77 | description: __( 78 | 'Boost your online knowledge and get ahead of the competition.', 79 | 'wp-plugin-bluehost' 80 | ), 81 | icon: false, 82 | cta: __( 'Explore Resources', 'wp-plugin-bluehost' ), 83 | url: 84 | getPlatformBaseUrl() + 85 | '/blog/?utm_campaign=&utm_content=help_kb_link&utm_term=find_answers&utm_medium=brand_plugin&utm_source=wp-admin/admin.php?page=bluehost#/help', 86 | }, 87 | { 88 | name: 'events', 89 | title: __( 'Events and Webinars', 'wp-plugin-bluehost' ), 90 | description: __( 91 | 'Team Bluehost organizes multiple webinars and events throughout the year. We are also sponsors and speak at most WordCamps across the world. Join us at our next event!', 92 | 'wp-plugin-bluehost' 93 | ), 94 | icon: false, 95 | cta: __( 'More Info', 'wp-plugin-bluehost' ), 96 | url: 'https://www.bluehost.com/blog/events/?utm_campaign=&utm_content=help_blog_link&utm_term=learn_stuff&utm_medium=brand_plugin&utm_source=wp-admin', 97 | }, 98 | { 99 | name: 'website', 100 | title: __( 'Bluehost Website', 'wp-plugin-bluehost' ), 101 | description: __( 102 | 'Not finding what you need? Visit our website for more information about our products and services.', 103 | 'wp-plugin-bluehost' 104 | ), 105 | icon: false, 106 | cta: __( 'Go to Bluehost', 'wp-plugin-bluehost' ), 107 | url: getPlatformBaseUrl(), 108 | }, 109 | ]; 110 | 111 | export default help; 112 | -------------------------------------------------------------------------------- /src/app/data/routes.js: -------------------------------------------------------------------------------- 1 | import { 2 | HomeIcon, 3 | ShoppingBagIcon, 4 | WrenchScrewdriverIcon, 5 | BoltIcon, 6 | AdjustmentsHorizontalIcon, 7 | BuildingStorefrontIcon, 8 | DocumentDuplicateIcon, 9 | PuzzlePieceIcon, 10 | } from '@heroicons/react/24/outline'; 11 | import { ReactComponent as HelpIcon } from '../components/icons/HelpIcon.svg'; 12 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 13 | import { getMarketplaceSubnavRoutes } from '@modules/wp-module-marketplace/components/marketplaceSubnav'; 14 | import { Route, Routes } from 'react-router-dom'; 15 | import Home from '../pages/home'; 16 | import PagesAndPosts from '../pages/pages-and-posts'; 17 | import Store from '../pages/ecommerce/page'; 18 | import Marketplace from '../pages/marketplace'; 19 | import Solutions from '../pages/solutions'; 20 | import Performance from '../pages/performance'; 21 | import Settings from '../pages/settings'; 22 | import Staging from '../pages/staging'; 23 | import Help from '../pages/help'; 24 | import Admin from '../pages/admin'; 25 | 26 | const addPartialMatch = ( prefix, path ) => 27 | prefix === path ? `${ prefix }/*` : path; 28 | 29 | const HelpCenterAI = ( e ) => { 30 | e.preventDefault(); 31 | window.newfoldEmbeddedHelp.toggleNFDLaunchedEmbeddedHelp(); 32 | }; 33 | 34 | export const AppRoutes = () => { 35 | return ( 36 | 37 | { routes.map( 38 | ( page ) => 39 | true === page.condition && ( 40 | } 48 | /> 49 | ) 50 | ) } 51 | } /> 52 | 56 |

57 | { __( 58 | "There's nothing here!", 59 | 'wp-plugin-bluehost' 60 | ) } 61 |

62 | 63 | } 64 | /> 65 |
66 | ); 67 | }; 68 | 69 | const topRoutePaths = [ 70 | '/home', 71 | '/pages-and-posts', 72 | '/store', 73 | '/marketplace', 74 | '/my_plugins_and_tools', 75 | '/performance', 76 | '/settings', 77 | '/staging', 78 | ]; 79 | const utilityRoutePaths = [ '/help' ]; 80 | 81 | export const routes = [ 82 | { 83 | name: '/home', 84 | title: __( 'Home', 'wp-plugin-bluehost' ), 85 | Component: Home, 86 | Icon: HomeIcon, 87 | condition: true, 88 | }, 89 | { 90 | name: '/pages-and-posts', 91 | title: __( 'Pages & Posts', 'wp-plugin-bluehost' ), 92 | Component: PagesAndPosts, 93 | Icon: DocumentDuplicateIcon, 94 | condition: true, 95 | }, 96 | { 97 | name: '/store', 98 | title: __( 'Store', 'wp-plugin-bluehost' ), 99 | Component: Store, 100 | Icon: BuildingStorefrontIcon, 101 | subRoutes: [ 102 | { 103 | name: '/store/products', 104 | title: __( 'Products & Services', 'wp-plugin-bluehost' ), 105 | }, 106 | NewfoldRuntime.hasCapability( 'hasYithExtended' ) || 107 | NewfoldRuntime.hasCapability( 'canAccessGlobalCTB' ) 108 | ? { 109 | name: '/store/sales_discounts', 110 | title: __( 'Sales & Promotions', 'wp-plugin-bluehost' ), 111 | } 112 | : null, 113 | NewfoldRuntime.hasCapability( 'hasYithExtended' ) && 114 | NewfoldRuntime.hasCapability( 'hasEcomdash' ) 115 | ? { 116 | name: '/store/sales_channel', 117 | title: __( 'Sales Channel', 'wp-plugin-bluehost' ), 118 | } 119 | : null, 120 | NewfoldRuntime.isWoo 121 | ? { 122 | name: '/store/payments', 123 | title: __( 'Payments', 'wp-plugin-bluehost' ), 124 | } 125 | : null, 126 | { 127 | name: '/store/details', 128 | title: __( 'Store Details', 'wp-plugin-bluehost' ), 129 | }, 130 | ].filter( Boolean ), 131 | condition: true, 132 | }, 133 | { 134 | name: '/marketplace', 135 | title: __( 'Marketplace', 'wp-plugin-bluehost' ), 136 | Component: Marketplace, 137 | Icon: ShoppingBagIcon, 138 | subRoutes: await getMarketplaceSubnavRoutes(), 139 | condition: true, 140 | }, 141 | NewfoldRuntime.hasCapability( 'hasSolution' ) && { 142 | name: '/my_plugins_and_tools', 143 | title: __( 'My Plugins & Tools', 'wp-plugin-bluehost' ), 144 | Component: Solutions, 145 | Icon: PuzzlePieceIcon, 146 | condition: true, 147 | }, 148 | { 149 | name: '/performance', 150 | title: __( 'Performance', 'wp-plugin-bluehost' ), 151 | Component: Performance, 152 | Icon: BoltIcon, 153 | condition: await window.NewfoldFeatures.isEnabled( 'performance' ), 154 | }, 155 | { 156 | name: '/settings', 157 | title: __( 'Settings', 'wp-plugin-bluehost' ), 158 | Component: Settings, 159 | Icon: AdjustmentsHorizontalIcon, 160 | condition: true, 161 | }, 162 | { 163 | name: '/staging', 164 | title: __( 'Staging', 'wp-plugin-bluehost' ), 165 | Component: Staging, 166 | Icon: WrenchScrewdriverIcon, 167 | condition: await window.NewfoldFeatures.isEnabled( 'staging' ), 168 | }, 169 | { 170 | name: '/help', 171 | title: __( 'Help with WordPress', 'wp-plugin-bluehost' ), 172 | Component: Help, 173 | Icon: HelpIcon, 174 | condition: true, 175 | action: NewfoldRuntime.hasCapability( 'canAccessHelpCenter' ) 176 | ? HelpCenterAI 177 | : false, 178 | }, 179 | { 180 | name: '/admin', 181 | title: __( 'Admin', 'wp-plugin-bluehost' ), 182 | Component: Admin, 183 | condition: true, 184 | }, 185 | ]; 186 | 187 | export const topRoutes = _filter( routes, ( route ) => 188 | topRoutePaths.includes( route.name ) 189 | ); 190 | 191 | export const utilityRoutes = _filter( routes, ( route ) => 192 | utilityRoutePaths.includes( route.name ) 193 | ); 194 | 195 | export default AppRoutes; 196 | -------------------------------------------------------------------------------- /src/app/data/store.js: -------------------------------------------------------------------------------- 1 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 2 | import { createContext, useMemo } from '@wordpress/element'; 3 | import apiFetch from '@wordpress/api-fetch'; 4 | 5 | const DEFAULT = { 6 | store: {}, 7 | setStore: () => {}, 8 | }; 9 | 10 | const AppStore = createContext( DEFAULT ); 11 | 12 | export const bluehostApiFetchSettings = async ( options = {} ) => { 13 | return await apiFetch( { 14 | url: NewfoldRuntime.createApiUrl( '/bluehost/v1/settings' ), 15 | ...options, 16 | } ); 17 | }; 18 | 19 | export const reformStore = ( store, endpoint, response ) => { 20 | return { 21 | ...store, 22 | [ _camelCase( endpoint ) ]: response, 23 | }; 24 | }; 25 | 26 | export const AppStoreProvider = ( { children } ) => { 27 | const [ booted, setBooted ] = useState( false ); 28 | const [ hasError, setError ] = useState( false ); 29 | const [ store, setStore ] = useState( {} ); 30 | 31 | const contextStore = useMemo( 32 | () => ( { store, setStore, booted, setBooted, hasError, setError } ), 33 | [ store, booted, hasError ] 34 | ); 35 | 36 | useEffect( () => { 37 | if ( false === booted ) { 38 | bluehostApiFetchSettings() 39 | .then( ( settings ) => { 40 | setStore( { 41 | ...store, 42 | ...settings, 43 | features: window.NewfoldFeatures.features, 44 | toggleableFeatures: window.NewfoldFeatures.togglable, 45 | } ); 46 | setBooted( true ); 47 | } ) 48 | .catch( ( error ) => { 49 | setError( error ); 50 | } ); 51 | } 52 | // eslint-disable-next-line react-hooks/exhaustive-deps 53 | }, [] ); 54 | 55 | return ( 56 | 57 | { ' ' } 58 | { children }{ ' ' } 59 | 60 | ); 61 | }; 62 | 63 | export default AppStore; 64 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | import './stylesheet.scss'; 2 | import './tailwind.pcss'; 3 | 4 | import AppStore, { AppStoreProvider } from './data/store'; 5 | import { useLocation, HashRouter as Router } from 'react-router-dom'; 6 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 7 | import { SnackbarList, Spinner } from '@wordpress/components'; 8 | import AppRoutes from './data/routes'; 9 | import ErrorCard from './components/errorCard'; 10 | import { useDispatch, useSelect } from '@wordpress/data'; 11 | import { useEffect } from 'react'; 12 | import { ErrorBoundary } from 'react-error-boundary'; 13 | // eslint-disable-next-line import/no-unresolved 14 | import { store as noticesStore } from '@wordpress/notices'; 15 | import { kebabCase, filter } from 'lodash'; 16 | import { useHandlePageLoad } from './util/hooks'; 17 | import { Root } from '@newfold/ui-component-library'; 18 | import { AppNav } from './components/app-nav'; 19 | import { SiteInfoBar } from './components/site-info'; 20 | import { NotificationFeed } from './components/notifications'; 21 | 22 | // component sourced from module 23 | import { default as NewfoldNotifications } from '@modules/wp-module-notifications/assets/js/components/notifications/'; 24 | // to pass to notifications module 25 | import apiFetch from '@wordpress/api-fetch'; 26 | import { addQueryArgs } from '@wordpress/url'; 27 | import { useState } from '@wordpress/element'; 28 | 29 | const Notices = () => { 30 | const notices = useSelect( 31 | ( select ) => 32 | select( noticesStore ) 33 | .getNotices() 34 | .filter( ( notice ) => notice.type === 'snackbar' ), 35 | [] 36 | ); 37 | const { removeNotice } = useDispatch( noticesStore ); 38 | 39 | return ( 40 | 45 | ); 46 | }; 47 | 48 | const AppBody = ( props ) => { 49 | const location = useLocation(); 50 | const hashedPath = '#' + location.pathname; 51 | const { booted, hasError } = useContext( AppStore ); 52 | 53 | useHandlePageLoad(); 54 | 55 | return ( 56 |
66 | 79 |
80 |
81 | }> 82 | { hasError && } 83 | 84 | { ( true === booted && ) || 85 | ( ! hasError && ) } 86 | 87 |
88 |
89 | 90 |
91 | { 'undefined' !== typeof noticesStore && } 92 |
93 |
94 | ); 95 | }; 96 | 97 | export const App = () => ( 98 | 99 | 100 | 101 | 102 |
103 | 104 | 105 |
106 |
107 |
108 |
109 |
110 | ); 111 | 112 | export default App; 113 | -------------------------------------------------------------------------------- /src/app/pages/admin/index.js: -------------------------------------------------------------------------------- 1 | import HelpCenterSettings from '../settings/helpCenterSettings'; 2 | import WonderBlocksSettings from '../settings/wonderBlocksSettings'; 3 | import StagingFeatureSettings from '../settings/stagingFeatureSettings'; 4 | import PerformanceFeatureSettings from '../settings/performanceFeatureSettings'; 5 | import { Container, Page } from '@newfold/ui-component-library'; 6 | 7 | const Admin = () => { 8 | return ( 9 | 10 | 11 | 19 | 20 | 25 | 29 | 30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 |
39 |
40 | ); 41 | }; 42 | 43 | export default Admin; 44 | -------------------------------------------------------------------------------- /src/app/pages/ecommerce/page.js: -------------------------------------------------------------------------------- 1 | import './styles.scss'; 2 | import { useContext } from '@wordpress/element'; 3 | import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; 4 | import { Page } from '@newfold/ui-component-library'; 5 | import { NewfoldECommerce } from '@newfold/wp-module-ecommerce'; 6 | import '@newfold/wp-module-ecommerce/bluehost.css'; 7 | import AppStore from 'App/data/store'; 8 | import { 9 | bluehostSettingsApiFetch, 10 | comingSoonAdminbarToggle, 11 | } from 'App/util/helpers'; 12 | import { useNotification } from 'App/components/notifications'; 13 | 14 | const ECommerce = () => { 15 | const { store, setStore } = useContext( AppStore ); 16 | const [ params ] = useSearchParams(); 17 | const location = useLocation(); 18 | const navigate = useNavigate(); 19 | const notify = useNotification(); 20 | const state = { 21 | wp: { comingSoon: store?.comingSoon }, 22 | params, 23 | location: location.pathname, 24 | }; 25 | const wpModules = { navigate, notify }; 26 | 27 | const actions = { 28 | toggleComingSoon: () => 29 | bluehostSettingsApiFetch( 30 | { comingSoon: ! store.comingSoon }, 31 | // eslint-disable-next-line no-console 32 | console.error, 33 | // eslint-disable-next-line no-unused-vars 34 | ( response ) => { 35 | setStore( { 36 | ...store, 37 | comingSoon: ! store.comingSoon, 38 | } ); 39 | comingSoonAdminbarToggle( ! store.comingSoon ); 40 | } 41 | ), 42 | }; 43 | 44 | return ( 45 | 46 | 51 | 52 | ); 53 | }; 54 | 55 | export default ECommerce; 56 | -------------------------------------------------------------------------------- /src/app/pages/ecommerce/styles.scss: -------------------------------------------------------------------------------- 1 | .nfd-root { 2 | .nfd-modal.no-overlay { 3 | .nfd-modal__overlay { 4 | display: none; 5 | } 6 | } 7 | .nfd-feature-upsell.hide-html { 8 | .nfd-flex { 9 | background-color: rgb(255 255 255 / 0.5); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/app/pages/help/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Card, 4 | Container, 5 | Page, 6 | Title, 7 | } from '@newfold/ui-component-library'; 8 | import help from 'App/data/help'; 9 | 10 | const HelpCard = ( { item } ) => { 11 | return ( 12 | 13 | 14 | 15 | { item.title } 16 | 17 |

{ item.description }

18 |
19 | 20 | 21 | 30 | 31 |
32 | ); 33 | }; 34 | 35 | const Help = () => { 36 | const renderHelpCards = () => { 37 | const helpItems = help; 38 | 39 | return ( 40 |
41 | { helpItems.map( ( item ) => ( 42 | 43 | ) ) } 44 |
45 | ); 46 | }; 47 | return ( 48 | 49 | 50 | 57 | 58 | { renderHelpCards() } 59 | 60 | 61 | ); 62 | }; 63 | 64 | export default Help; 65 | -------------------------------------------------------------------------------- /src/app/pages/home/accountCard.js: -------------------------------------------------------------------------------- 1 | import { 2 | CpuChipIcon, 3 | CreditCardIcon, 4 | EnvelopeIcon, 5 | GiftIcon, 6 | IdentificationIcon, 7 | ShieldCheckIcon, 8 | } from '@heroicons/react/24/outline'; 9 | import { Card, Title } from '@newfold/ui-component-library'; 10 | import { 11 | addUtmParams, 12 | getPlatformPathUrl, 13 | getPlatformBaseUrl, 14 | isJarvis, 15 | } from '../../util/helpers'; 16 | import classNames from 'classnames'; 17 | 18 | const base = [ 19 | { 20 | icon: CpuChipIcon, 21 | id: 'account_link', 22 | href: addUtmParams( getPlatformPathUrl( 'hosting/list', 'app' ) ), 23 | label: isJarvis() 24 | ? __( 'Hosting', 'bluehost-wordpress-plugin' ) 25 | : __( 'Control Panel', 'bluehost-wordpress-plugin' ), 26 | color: 'nfd-fill-gray', 27 | }, 28 | { 29 | icon: GiftIcon, 30 | id: 'products_link', 31 | href: addUtmParams( 32 | getPlatformPathUrl( 'renewal-center', 'account_center#products' ) 33 | ), 34 | label: isJarvis() 35 | ? __( 'Renewal Center', 'bluehost-wordpress-plugin' ) 36 | : __( 'Products', 'bluehost-wordpress-plugin' ), 37 | color: 'nfd-fill-primary-dark', 38 | }, 39 | { 40 | icon: CreditCardIcon, 41 | id: 'billing_link', 42 | href: addUtmParams( 43 | getPlatformPathUrl( 'billing-center', 'account_center#billing' ) 44 | ), 45 | label: isJarvis() 46 | ? __( 'Payment Methods', 'bluehost-wordpress-plugin' ) 47 | : __( 'Billing', 'bluehost-wordpress-plugin' ), 48 | color: 'nfd-fill-primary', 49 | }, 50 | { 51 | icon: EnvelopeIcon, 52 | id: 'mail_link', 53 | href: addUtmParams( getPlatformPathUrl( 'home', 'app#/email-office' ) ), 54 | label: isJarvis() 55 | ? __( 'Mail', 'bluehost-wordpress-plugin' ) 56 | : __( 'Mail & Office', 'bluehost-wordpress-plugin' ), 57 | color: 'nfd-fill-[#5b5b5b]', 58 | }, 59 | { 60 | icon: ShieldCheckIcon, 61 | id: 'security_link', 62 | href: addUtmParams( 63 | getPlatformPathUrl( 'account-center', 'account_center#security' ) 64 | ), 65 | label: __( 'Security', 'bluehost-wordpress-plugin' ), 66 | color: 'nfd-fill-[#17b212]', 67 | }, 68 | { 69 | icon: IdentificationIcon, 70 | id: 'validation_token_link', 71 | href: isJarvis() 72 | ? addUtmParams( getPlatformPathUrl( 'account-center' ) ) 73 | : addUtmParams( getPlatformBaseUrl( '/cgi/token' ) ), 74 | label: isJarvis() 75 | ? __( 'Profile', 'bluehost-wordpress-plugin' ) 76 | : __( 'Validation Token', 'bluehost-wordpress-plugin' ), 77 | color: 'nfd-fill-[#f89c24]', 78 | }, 79 | ]; 80 | 81 | const AccountCard = ( { props } ) => { 82 | return ( 83 | 84 | 85 | Bluehost Account 86 | 114 | 115 | 116 | ); 117 | }; 118 | 119 | export default AccountCard; 120 | -------------------------------------------------------------------------------- /src/app/pages/home/freeAddonsSection.js: -------------------------------------------------------------------------------- 1 | import { FreePlugins } from '@newfold/wp-module-ecommerce'; 2 | import { useNotification } from 'App/components/notifications'; 3 | 4 | export const FreeAddonsSection = ( { props } ) => { 5 | const notify = useNotification(); 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/pages/home/helpCard.js: -------------------------------------------------------------------------------- 1 | import { Button, Card, Title } from '@newfold/ui-component-library'; 2 | import SupportIllustration from 'App/images/section-home-help-me.svg'; 3 | 4 | const HelpCard = ( {} ) => { 5 | return ( 6 | 7 | 8 | Need some help? 9 |
10 | Help Agent Illustration 15 |

16 | { __( 17 | 'From DIY to full-service help.', 18 | 'wp-plugin-bluehost' 19 | ) } 20 |

21 |

22 | { __( 23 | 'Call or chat 24/7 for support or let our experts build your site for you.', 24 | 'wp-plugin-bluehost' 25 | ) } 26 |

27 | 35 |
36 |
37 |
38 | ); 39 | }; 40 | 41 | export default HelpCard; 42 | -------------------------------------------------------------------------------- /src/app/pages/home/index.js: -------------------------------------------------------------------------------- 1 | import { Container, Page } from '@newfold/ui-component-library'; 2 | import WebinarsBanner from 'App/components/webinars-banner'; 3 | import AccountCard from './accountCard'; 4 | import HelpCard from './helpCard'; 5 | import WelcomeSection from './welcomeSection'; 6 | import { WPSolutionsBanner } from '@newfold/wp-module-ecommerce'; 7 | 8 | const Home = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 |
23 | ); 24 | }; 25 | 26 | export default Home; 27 | -------------------------------------------------------------------------------- /src/app/pages/home/welcomeSection.js: -------------------------------------------------------------------------------- 1 | import { useContext } from '@wordpress/element'; 2 | import { useUpdateEffect } from 'react-use'; 3 | import { OnboardingScreen } from '@newfold/wp-module-ecommerce'; 4 | import AppStore from 'App/data/store'; 5 | import { bluehostSettingsApiFetch } from 'App/util/helpers'; 6 | import { useNotification } from 'App/components/notifications'; 7 | import { comingSoonAdminbarToggle } from '../../util/helpers'; 8 | 9 | const WelcomeSection = ( {} ) => { 10 | const { store, setStore } = useContext( AppStore ); 11 | const notify = useNotification(); 12 | 13 | const toggleComingSoon = () => 14 | bluehostSettingsApiFetch( 15 | { comingSoon: ! store.comingSoon }, 16 | // eslint-disable-next-line no-console 17 | console.error, 18 | () => setStore( { ...store, comingSoon: ! store.comingSoon } ) 19 | ); 20 | 21 | useUpdateEffect( () => { 22 | comingSoonAdminbarToggle( store.comingSoon ); 23 | }, [ store.comingSoon ] ); 24 | 25 | return ( 26 | 31 | ); 32 | }; 33 | 34 | export default WelcomeSection; 35 | -------------------------------------------------------------------------------- /src/app/pages/marketplace/index.js: -------------------------------------------------------------------------------- 1 | import apiFetch from '@wordpress/api-fetch'; 2 | import { useState, useEffect } from '@wordpress/element'; 3 | import { useLocation, useMatch, useNavigate } from 'react-router-dom'; 4 | import { Container, Page } from '@newfold/ui-component-library'; 5 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 6 | // component sourced from marketplace module 7 | import { default as NewfoldMarketplace } from '@modules/wp-module-marketplace/components/'; 8 | 9 | const MarketplacePage = () => { 10 | // constants to pass to module 11 | const moduleConstants = { 12 | supportsCTB: true, 13 | text: { 14 | title: __( 'Marketplace', 'bluehost-wordpress-plugin' ), 15 | subTitle: __( 16 | 'Explore our featured collection of tools and services.', 17 | 'bluehost-wordpress-plugin' 18 | ), 19 | error: __( 20 | 'Oops, there was an error loading the marketplace, please try again later.', 21 | 'bluehost-wordpress-plugin' 22 | ), 23 | noProducts: __( 24 | 'Sorry, no marketplace items. Please, try again later.', 25 | 'bluehost-wordpress-plugin' 26 | ), 27 | loadMore: __( 'Load More', 'bluehost-wordpress-plugin' ), 28 | productPage: { 29 | error: { 30 | title: __( 31 | 'Oops! Something Went Wrong', 32 | 'bluehost-wordpress-plugin' 33 | ), 34 | description: __( 35 | 'An error occurred while loading the content. Please try again later.', 36 | 'bluehost-wordpress-plugin' 37 | ), 38 | }, 39 | }, 40 | }, 41 | }; 42 | 43 | // methods to pass to module 44 | const moduleMethods = { 45 | apiFetch, 46 | classNames, 47 | useState, 48 | useEffect, 49 | useLocation, 50 | useMatch, 51 | useNavigate, 52 | NewfoldRuntime, 53 | }; 54 | 55 | return ( 56 | 57 | 62 | 66 | 67 | 68 | ); 69 | }; 70 | 71 | export default MarketplacePage; 72 | -------------------------------------------------------------------------------- /src/app/pages/pages-and-posts/ProductsPages.js: -------------------------------------------------------------------------------- 1 | import { Button, Card, Title } from '@newfold/ui-component-library'; 2 | 3 | const ProductsPages = () => { 4 | return ( 5 | 11 | 12 | 19 | 20 | 26 | 27 | 28 | { __( 'Products', 'wp-plugin-bluehost' ) } 29 | 30 |

31 | { __( 32 | 'Showcase physical and digital goods, product variations, and custom configurations.', 33 | 'wp-plugin-bluehost' 34 | ) } 35 |

36 |
37 | 45 |
46 | ); 47 | }; 48 | 49 | export default ProductsPages; 50 | -------------------------------------------------------------------------------- /src/app/pages/pages-and-posts/blogPosts.js: -------------------------------------------------------------------------------- 1 | import { Button, Card, Title } from '@newfold/ui-component-library'; 2 | 3 | const BlogPosts = () => { 4 | return ( 5 | 11 | 12 | 19 | 20 | 26 | 27 | 28 | { __( 'Blog Posts', 'wp-plugin-bluehost' ) } 29 | 30 |

31 | { __( 32 | 'Add a new blog post or edit your existing posts.', 33 | 'wp-plugin-bluehost' 34 | ) } 35 |

36 |
37 | 45 |
46 | ); 47 | }; 48 | 49 | export default BlogPosts; 50 | -------------------------------------------------------------------------------- /src/app/pages/pages-and-posts/bookingAndAppointments.js: -------------------------------------------------------------------------------- 1 | import { Button, Card, Title } from '@newfold/ui-component-library'; 2 | 3 | const BookingAndAppointments = () => { 4 | return ( 5 | 11 | 12 | 19 | 20 | 26 | 27 | 28 | { __( 'Bookings & Appointments', 'wp-plugin-bluehost' ) } 29 | 30 |

31 | { __( 32 | 'Add your list of services, setup a booking calendar, and edit your bookings form.', 33 | 'wp-plugin-bluehost' 34 | ) } 35 |

36 |
37 | 45 |
46 | ); 47 | }; 48 | 49 | export default BookingAndAppointments; 50 | -------------------------------------------------------------------------------- /src/app/pages/pages-and-posts/ecommAddonCTB.js: -------------------------------------------------------------------------------- 1 | import { Button, Card, Title } from '@newfold/ui-component-library'; 2 | import ecommAddonIconUrl from 'Assets/images/ecomm-addon-ctb-icon.png'; 3 | 4 | const EcommAddonCTB = () => { 5 | const EcommAddonCtbIcon = () => ( 6 | { 12 | ); 13 | 14 | return ( 15 | 21 | 22 | 23 | 24 | { __( 'Transform your store!', 'wp-plugin-bluehost' ) } 25 | 26 |

27 | { __( 28 | 'Our eCommerce bundle includes a comprehensive suite of advanced tools designed to boost the performance of your WooCommerce store.', 29 | 'wp-plugin-bluehost' 30 | ) } 31 |

32 |
33 | 40 |
41 | ); 42 | }; 43 | 44 | export default EcommAddonCTB; 45 | -------------------------------------------------------------------------------- /src/app/pages/pages-and-posts/index.js: -------------------------------------------------------------------------------- 1 | import { Alert, Container, Page } from '@newfold/ui-component-library'; 2 | import { useContext } from '@wordpress/element'; 3 | 4 | import SitePages from './sitePages'; 5 | import BlogPosts from './blogPosts'; 6 | import BookingAndAppointments from './bookingAndAppointments'; 7 | import ProductsPages from './ProductsPages'; 8 | import AppStore from '../../data/store'; 9 | import EcommAddonCTB from './ecommAddonCTB'; 10 | 11 | const PagesAndPosts = () => { 12 | const { store } = useContext( AppStore ); 13 | 14 | return ( 15 | 19 | 20 | 28 | 29 | { __( 30 | 'Your site is not live.', 31 | 'wp-plugin-bluehost' 32 | ) } 33 | 34 | 35 | ) : ( 36 | 40 | 41 | { __( 42 | 'Your site is live.', 43 | 'wp-plugin-bluehost' 44 | ) } 45 | 46 | 47 | ) 48 | } 49 | className={ 'wppbh-app-settings-header' } 50 | > 51 | { store?.comingSoon ? ( 52 |

53 | { __( 54 | 'Visitors to your site will see your "Coming Soon" page and not your actual site. Visit', 55 | 'wp-plugin-bluehost' 56 | ) }{ ' ' } 57 | 58 | { __( '"Settings"', 'wp-plugin-bluehost' ) } 59 | { ' ' } 60 | { __( 61 | 'to set your site live.', 62 | 'wp-plugin-bluehost' 63 | ) } 64 |

65 | ) : ( 66 |

67 | { __( 68 | 'Visitors to your site will see all your publicly published pages.', 69 | 'wp-plugin-bluehost' 70 | ) } 71 |

72 | ) } 73 |
74 | 75 |
76 | 77 | 78 | { window.NewfoldRuntime.isWoocommerceActive && ( 79 | 80 | ) } 81 | { window.NewfoldRuntime.capabilities.hasYithExtended && 82 | window.NewfoldRuntime.isWoocommerceActive ? ( 83 | 84 | ) : ( 85 | 86 | ) } 87 |
88 |
89 |
90 | ); 91 | }; 92 | 93 | export default PagesAndPosts; 94 | -------------------------------------------------------------------------------- /src/app/pages/pages-and-posts/sitePages.js: -------------------------------------------------------------------------------- 1 | import { Button, Card, Title } from '@newfold/ui-component-library'; 2 | 3 | const SitePages = () => { 4 | return ( 5 | 11 | 12 | 19 | 20 | 26 | 27 | 28 | { __( 'Site Pages', 'wp-plugin-bluehost' ) } 29 | 30 |

31 | { __( 32 | 'Edit your homepage and other existing pages or add new pages to your site.', 33 | 'wp-plugin-bluehost' 34 | ) } 35 |

36 |
37 | 47 |
48 | ); 49 | }; 50 | 51 | export default SitePages; 52 | -------------------------------------------------------------------------------- /src/app/pages/performance/index.js: -------------------------------------------------------------------------------- 1 | import apiFetch from '@wordpress/api-fetch'; 2 | import { useState, useEffect, useContext, Fragment } from '@wordpress/element'; 3 | import { useUpdateEffect } from 'react-use'; 4 | import { Container, Page } from '@newfold/ui-component-library'; 5 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 6 | import AppStore from '../../data/store'; 7 | import { 8 | bluehostSettingsApiFetch as newfoldSettingsApiFetch, 9 | bluehostPurgeCacheApiFetch as newfoldPurgeCacheApiFetch, 10 | } from 'App/util/helpers'; 11 | import { useNotification } from 'App/components/notifications'; 12 | 13 | import { default as NewfoldPerformance } from '@modules/wp-module-performance/components/performance/'; 14 | 15 | const PerformancePage = () => { 16 | // constants to pass to module 17 | const moduleConstants = {}; 18 | 19 | // methods to pass to module 20 | const moduleMethods = { 21 | apiFetch, 22 | useState, 23 | useEffect, 24 | useContext, 25 | NewfoldRuntime, 26 | useNotification, 27 | newfoldSettingsApiFetch, 28 | newfoldPurgeCacheApiFetch, 29 | useUpdateEffect, 30 | AppStore, 31 | }; 32 | 33 | const moduleComponents = { 34 | Fragment, 35 | }; 36 | 37 | return ( 38 | 39 | 40 | 48 | 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default PerformancePage; 59 | -------------------------------------------------------------------------------- /src/app/pages/settings/comingSoon.js: -------------------------------------------------------------------------------- 1 | import { useState } from '@wordpress/element'; 2 | import { useUpdateEffect } from 'react-use'; 3 | import { Alert, Container, ToggleField } from '@newfold/ui-component-library'; 4 | import AppStore from '../../data/store'; 5 | import { 6 | bluehostSettingsApiFetch, 7 | comingSoonAdminbarToggle, 8 | } from '../../util/helpers'; 9 | import { useNotification } from 'App/components/notifications'; 10 | 11 | const ComingSoon = () => { 12 | const { store, setStore } = useContext( AppStore ); 13 | const [ comingSoon, setComingSoon ] = useState( store.comingSoon ); 14 | const [ isError, setError ] = useState( false ); 15 | 16 | const notify = useNotification(); 17 | 18 | const getComingSoonNoticeTitle = () => { 19 | return comingSoon 20 | ? __( 'Coming soon activated', 'wp-plugin-bluehost' ) 21 | : __( 'Coming soon deactivated', 'wp-plugin-bluehost' ); 22 | }; 23 | 24 | const getComingSoonNoticeText = () => { 25 | return comingSoon 26 | ? __( 27 | 'Coming soon page is active. Site requires login.', 28 | 'wp-plugin-bluehost' 29 | ) 30 | : __( 31 | 'Coming soon page is not active. Site is live to visitors.', 32 | 'wp-plugin-bluehost' 33 | ); 34 | }; 35 | 36 | const toggleComingSoon = () => { 37 | bluehostSettingsApiFetch( 38 | { comingSoon: ! comingSoon }, 39 | setError, 40 | // eslint-disable-next-line no-unused-vars 41 | ( response ) => { 42 | setComingSoon( ! comingSoon ); 43 | } 44 | ); 45 | }; 46 | 47 | const notifySuccess = () => { 48 | notify.push( 'coming-soon-toggle-notice', { 49 | title: getComingSoonNoticeTitle(), 50 | description: { getComingSoonNoticeText() }, 51 | variant: 'success', 52 | autoDismiss: 5000, 53 | } ); 54 | }; 55 | 56 | useUpdateEffect( () => { 57 | setStore( { 58 | ...store, 59 | comingSoon, 60 | } ); 61 | 62 | notifySuccess(); 63 | comingSoonAdminbarToggle( comingSoon ); 64 | }, [ comingSoon ] ); 65 | 66 | const getComingSoonSectionTitle = () => { 67 | const getStatus = () => { 68 | return comingSoon ? ( 69 | 70 | { __( 'Not Live', 'wp-plugin-bluehost' ) } 71 | 72 | ) : ( 73 | 74 | { __( 'Live', 'wp-plugin-bluehost' ) } 75 | 76 | ); 77 | }; 78 | 79 | return ( 80 | 81 | { __( 'Site Status', 'wp-plugin-bluehost' ) }: { getStatus() } 82 | 83 | ); 84 | }; 85 | 86 | const getComingSoonSectionDescription = () => { 87 | return comingSoon 88 | ? __( 89 | 'Turn off your "Coming Soon" page when you are ready to launch your website.', 90 | 'wp-plugin-bluehost' 91 | ) 92 | : __( 93 | 'Turn on your "Coming Soon" page when you need to make major changes to your website.', 94 | 'wp-plugin-bluehost' 95 | ); 96 | }; 97 | 98 | return ( 99 | 103 |
104 | { 113 | toggleComingSoon(); 114 | } } 115 | /> 116 | 117 | { comingSoon && ( 118 | 119 | { __( 120 | 'Your website is currently displaying a "Coming Soon" page.', 121 | 'wp-plugin-bluehost' 122 | ) } 123 | 124 | ) } 125 | 126 | { isError && ( 127 | 128 | { __( 129 | 'Oops! Something went wrong. Please try again.', 130 | 'wp-plugin-bluehost' 131 | ) } 132 | 133 | ) } 134 |
135 |
136 | ); 137 | }; 138 | 139 | export default ComingSoon; 140 | -------------------------------------------------------------------------------- /src/app/pages/settings/contentSettings.js: -------------------------------------------------------------------------------- 1 | /* global sprintf */ 2 | import { useState } from '@wordpress/element'; 3 | import { useUpdateEffect } from 'react-use'; 4 | import { Alert, Container, SelectField } from '@newfold/ui-component-library'; 5 | import AppStore from 'App/data/store'; 6 | import { bluehostSettingsApiFetch } from 'App/util/helpers'; 7 | import { useNotification } from 'App/components/notifications'; 8 | 9 | const EmptyTrash = ( { setError, notify } ) => { 10 | const { store, setStore } = useContext( AppStore ); 11 | const [ emptyTrashDays, setNumEmptyTrashDays ] = useState( 12 | store.emptyTrashDays 13 | ); 14 | let numTrashWeeks = Math.floor( emptyTrashDays / 7 ); 15 | 16 | const emptyTrashNoticeTitle = () => { 17 | return __( 'Trash setting saved', 'wp-plugin-bluehost' ); 18 | }; 19 | 20 | const emptyTrashNoticeText = () => { 21 | return sprintf( 22 | //translators: %s: number of weeks. `The trash will automatically empty every ${numTrashWeeks} weeks.` 23 | _n( 24 | 'The trash will automatically empty every %s week.', 25 | 'The trash will automatically empty every %s weeks.', 26 | parseInt( numTrashWeeks ), 27 | 'wp-plugin-bluehost' 28 | ), 29 | numTrashWeeks 30 | ); 31 | }; 32 | 33 | const handleEmptyTrashDaysChange = ( value ) => { 34 | bluehostSettingsApiFetch( 35 | { emptyTrashDays: value }, 36 | setError, 37 | // eslint-disable-next-line no-unused-vars 38 | ( response ) => { 39 | setNumEmptyTrashDays( value ); 40 | } 41 | ); 42 | }; 43 | 44 | const notifySuccess = () => { 45 | notify.push( 'empty-trash-notice', { 46 | title: emptyTrashNoticeTitle(), 47 | description: { emptyTrashNoticeText() }, 48 | variant: 'success', 49 | autoDismiss: 5000, 50 | } ); 51 | }; 52 | 53 | useUpdateEffect( () => { 54 | setStore( { 55 | ...store, 56 | emptyTrashDays, 57 | } ); 58 | numTrashWeeks = Math.floor( emptyTrashDays / 7 ); 59 | 60 | notifySuccess(); 61 | }, [ emptyTrashDays ] ); 62 | 63 | return ( 64 | 82 | ); 83 | }; 84 | 85 | const ContentSettings = () => { 86 | const [ isError, setError ] = useState( false ); 87 | 88 | const notify = useNotification(); 89 | return ( 90 | 97 |
98 | 99 | 100 | { isError && ( 101 | 102 | { __( 103 | 'Oops! Something went wrong. Please try again.', 104 | 'wp-plugin-bluehost' 105 | ) } 106 | 107 | ) } 108 |
109 |
110 | ); 111 | }; 112 | 113 | export default ContentSettings; 114 | -------------------------------------------------------------------------------- /src/app/pages/settings/helpCenterSettings.js: -------------------------------------------------------------------------------- 1 | import { useState } from '@wordpress/element'; 2 | import { useUpdateEffect } from 'react-use'; 3 | import { Alert, ToggleField } from '@newfold/ui-component-library'; 4 | import AppStore from '../../data/store'; 5 | import { featureToggle, updateUI } from '../../util/helpers'; 6 | import { useNotification } from 'App/components/notifications'; 7 | 8 | const HelpCenterSettings = ( { forceShow = false } ) => { 9 | const { store, setStore } = useContext( AppStore ); 10 | const [ helpCenter, setHelpCenter ] = useState( store.features.helpCenter ); 11 | const [ helpCenterLocked, setHelpCenterLocked ] = useState( 12 | ! store.toggleableFeatures.helpCenter 13 | ); 14 | const [ isError, setError ] = useState( false ); 15 | const notify = useNotification(); 16 | 17 | const getHelpCenterNoticeTitle = () => { 18 | return helpCenter 19 | ? __( 'Help Center Enabled', 'wp-plugin-bluehost' ) 20 | : __( 'Help Center Disabled', 'wp-plugin-bluehost' ); 21 | }; 22 | const getHelpCenterNoticeText = () => { 23 | return helpCenter 24 | ? __( 25 | 'Reload the page to access the Help Center.', 26 | 'wp-plugin-bluehost' 27 | ) 28 | : __( 29 | 'The Help Center will no longer display.', 30 | 'wp-plugin-bluehost' 31 | ); 32 | }; 33 | 34 | const toggleHelpCenter = () => { 35 | featureToggle( 'helpCenter', ( response ) => { 36 | // console.log( response ); 37 | if ( response.success ) { 38 | setHelpCenter( ! helpCenter ); 39 | } else { 40 | setHelpCenterLocked( true ); 41 | setError( true ); 42 | notifyError(); 43 | } 44 | } ); 45 | }; 46 | 47 | const notifyError = () => { 48 | notify.push( 'feature-toggle-notice', { 49 | title: __( 'Sorry, that is not allowed.', 'wp-plugin-bluehost' ), 50 | description: __( 51 | 'This feature cannot currently be modified.', 52 | 'wp-plugin-bluehost' 53 | ), 54 | variant: 'error', 55 | } ); 56 | }; 57 | 58 | const notifySuccess = ( renderTitle, renderDescription ) => { 59 | notify.push( 'feature-toggle-notice', { 60 | title: renderTitle(), 61 | description: renderDescription(), 62 | variant: 'success', 63 | autoDismiss: 5000, 64 | } ); 65 | }; 66 | 67 | useUpdateEffect( () => { 68 | setStore( { 69 | ...store, 70 | helpCenter, 71 | } ); 72 | notifySuccess( getHelpCenterNoticeTitle, getHelpCenterNoticeText ); 73 | updateUI( '#wp-admin-bar-help-center', helpCenter ); 74 | }, [ helpCenter ] ); 75 | 76 | return ( 77 |
78 | { ( ! helpCenterLocked || forceShow ) && ( 79 | 90 | ) } 91 | 92 | { isError && ( 93 | 94 | { __( 95 | 'Oops! Something went wrong. Please try again.', 96 | 'wp-plugin-bluehost' 97 | ) } 98 | 99 | ) } 100 |
101 | ); 102 | }; 103 | 104 | export default HelpCenterSettings; 105 | -------------------------------------------------------------------------------- /src/app/pages/settings/index.js: -------------------------------------------------------------------------------- 1 | import { Container, Page } from '@newfold/ui-component-library'; 2 | import useContainerBlockIsTarget from 'App/util/hooks/useContainerBlockTarget'; 3 | import ComingSoon from './comingSoon'; 4 | import AutomaticUpdates from './automaticUpdates'; 5 | import HelpCenterSettings from './helpCenterSettings'; 6 | import WonderBlocksSettings from './wonderBlocksSettings'; 7 | import ContentSettings from './contentSettings'; 8 | import CommentSettings from './commentSettings'; 9 | import SocialMediaAccounts from './socialMediaAccounts'; 10 | 11 | const Settings = () => { 12 | return ( 13 | 14 | 15 | 24 | 25 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | 50 | 57 | 58 |
59 | 60 |
61 |
62 | 63 | 67 | 68 | 69 | 70 | 75 | 76 | 77 | 78 | 82 | 83 | 84 |
85 |
86 | ); 87 | }; 88 | 89 | export default Settings; 90 | -------------------------------------------------------------------------------- /src/app/pages/settings/performanceFeatureSettings.js: -------------------------------------------------------------------------------- 1 | import { useState } from '@wordpress/element'; 2 | import { useUpdateEffect } from 'react-use'; 3 | import { Alert, ToggleField } from '@newfold/ui-component-library'; 4 | import AppStore from '../../data/store'; 5 | import { featureToggle, updateUI } from '../../util/helpers'; 6 | import { useNotification } from 'App/components/notifications'; 7 | 8 | const PerformanceFeatureSettings = () => { 9 | const { store, setStore } = useContext( AppStore ); 10 | const [ performance, setPerformance ] = useState( 11 | store.features.performance 12 | ); 13 | const [ performanceLocked, setPerformanceLocked ] = useState( 14 | ! store.toggleableFeatures.performance 15 | ); 16 | const [ isError, setError ] = useState( false ); 17 | const notify = useNotification(); 18 | 19 | const getPerformanceNoticeTitle = () => { 20 | return performance 21 | ? __( 'Performance Enabled', 'wp-plugin-bluehost' ) 22 | : __( 'Performance Disabled', 'wp-plugin-bluehost' ); 23 | }; 24 | 25 | const getPerformanceNoticeText = () => { 26 | return performance 27 | ? __( 28 | 'You need to reload the page to manage Performance.', 29 | 'wp-plugin-bluehost' 30 | ) 31 | : __( 'Performance will no longer display.', 'wp-plugin-bluehost' ); 32 | }; 33 | 34 | const togglePerformance = () => { 35 | featureToggle( 'performance', ( response ) => { 36 | if ( response.success ) { 37 | setPerformance( ! performance ); 38 | } else { 39 | setPerformanceLocked( true ); 40 | setError( true ); 41 | notifyError(); 42 | } 43 | } ); 44 | }; 45 | 46 | const notifySuccess = ( renderTitle, renderDescription ) => { 47 | notify.push( 'feature-toggle-notice', { 48 | title: renderTitle(), 49 | description: renderDescription(), 50 | variant: 'success', 51 | autoDismiss: 5000, 52 | } ); 53 | }; 54 | 55 | const notifyError = () => { 56 | notify.push( 'feature-toggle-notice', { 57 | title: __( 'Sorry, that is not allowed.', 'wp-plugin-bluehost' ), 58 | description: __( 59 | 'This feature cannot currently be modified.', 60 | 'wp-plugin-bluehost' 61 | ), 62 | variant: 'error', 63 | } ); 64 | }; 65 | 66 | useUpdateEffect( () => { 67 | setStore( { 68 | ...store, 69 | performance, 70 | } ); 71 | notifySuccess( getPerformanceNoticeTitle, getPerformanceNoticeText ); 72 | updateUI( '.wppbh-app-navitem-Performance', performance ); 73 | }, [ performance ] ); 74 | 75 | return ( 76 |
77 | 88 | 89 | { isError && ( 90 | 91 | { __( 92 | 'Oops! Something went wrong. Please try again.', 93 | 'wp-plugin-bluehost' 94 | ) } 95 | 96 | ) } 97 |
98 | ); 99 | }; 100 | 101 | export default PerformanceFeatureSettings; 102 | -------------------------------------------------------------------------------- /src/app/pages/settings/socialMediaAccounts.js: -------------------------------------------------------------------------------- 1 | import { Button, Container } from '@newfold/ui-component-library'; 2 | import { useEffect, useState } from '@wordpress/element'; 3 | import { 4 | FacebookConnectPluginView, 5 | facebookConnectHelper, 6 | getFacebookUserProfileDetails, 7 | } from '@newfold/wp-module-facebook'; 8 | 9 | const SocialMediaAccounts = () => { 10 | const [ showModal, setShowModal ] = useState( false ); 11 | const [ fbLogin, setFbLogin ] = useState( false ); 12 | const [ loginInfo, setLoginInfo ] = useState(); 13 | let input = false; 14 | 15 | const getFbDetails = () => { 16 | getFacebookUserProfileDetails().then( ( res ) => { 17 | setFbLogin( res === 'token not found!' ? false : true ); 18 | if ( Array.isArray( res ) ) { 19 | setLoginInfo( res[ 0 ] ); 20 | } else { 21 | setLoginInfo( res ); 22 | } 23 | } ); 24 | }; 25 | 26 | useEffect( () => { 27 | getFbDetails(); 28 | }, [] ); 29 | 30 | const handleCloseModel = () => { 31 | if ( ! input ) { 32 | setShowModal( false ); 33 | } 34 | input = false; 35 | }; 36 | 37 | const handleFacebookConnect = () => { 38 | facebookConnectHelper( getFbDetails ).then( () => {} ); 39 | }; 40 | return ( 41 | 48 |
49 |

50 | { __( 51 | "Click 'Add' to connect to one of your social media accounts.", 52 | 'wp-plugin-bluehost' 53 | ) } 54 |

55 | 61 |
62 | 67 | { showModal && ( 68 | <> 69 | 127 | 128 | 129 | 130 |
131 | 132 | ) } 133 |
134 | ); 135 | }; 136 | 137 | export default SocialMediaAccounts; 138 | -------------------------------------------------------------------------------- /src/app/pages/settings/stagingFeatureSettings.js: -------------------------------------------------------------------------------- 1 | import { useState } from '@wordpress/element'; 2 | import { useUpdateEffect } from 'react-use'; 3 | import { Alert, ToggleField } from '@newfold/ui-component-library'; 4 | import AppStore from '../../data/store'; 5 | import { featureToggle, updateUI } from '../../util/helpers'; 6 | import { useNotification } from 'App/components/notifications'; 7 | 8 | const StagingFeatureSettings = () => { 9 | const { store, setStore } = useContext( AppStore ); 10 | const [ staging, setStaging ] = useState( store.features.staging ); 11 | const [ stagingLocked, setStagingLocked ] = useState( 12 | ! store.toggleableFeatures.staging 13 | ); 14 | const [ isError, setError ] = useState( false ); 15 | const notify = useNotification(); 16 | 17 | const getStagingNoticeTitle = () => { 18 | return staging 19 | ? __( 'Staging Enabled', 'wp-plugin-bluehost' ) 20 | : __( 'Staging Disabled', 'wp-plugin-bluehost' ); 21 | }; 22 | const getStagingNoticeText = () => { 23 | return staging 24 | ? __( 25 | 'You need to reload the page to manage Staging.', 26 | 'wp-plugin-bluehost' 27 | ) 28 | : __( 'Staging will no longer display.', 'wp-plugin-bluehost' ); 29 | }; 30 | 31 | const toggleStaging = () => { 32 | featureToggle( 'staging', ( response ) => { 33 | if ( response.success ) { 34 | setStaging( ! staging ); 35 | } else { 36 | setStagingLocked( true ); 37 | setError( true ); 38 | notifyError(); 39 | } 40 | } ); 41 | }; 42 | 43 | const notifyError = () => { 44 | notify.push( 'feature-toggle-notice', { 45 | title: __( 'Sorry, that is not allowed.', 'wp-plugin-bluehost' ), 46 | description: __( 47 | 'This feature cannot currently be modified.', 48 | 'wp-plugin-bluehost' 49 | ), 50 | variant: 'error', 51 | } ); 52 | }; 53 | 54 | const notifySuccess = ( renderTitle, renderDescription ) => { 55 | notify.push( 'feature-toggle-notice', { 56 | title: renderTitle(), 57 | description: renderDescription(), 58 | variant: 'success', 59 | autoDismiss: 5000, 60 | } ); 61 | }; 62 | 63 | useUpdateEffect( () => { 64 | setStore( { 65 | ...store, 66 | staging, 67 | } ); 68 | notifySuccess( getStagingNoticeTitle, getStagingNoticeText ); 69 | updateUI( '.wppbh-app-navitem-Staging', staging ); 70 | }, [ staging ] ); 71 | 72 | return ( 73 |
74 | 85 | 86 | { isError && ( 87 | 88 | { __( 89 | 'Oops! Something went wrong. Please try again.', 90 | 'wp-plugin-bluehost' 91 | ) } 92 | 93 | ) } 94 |
95 | ); 96 | }; 97 | 98 | export default StagingFeatureSettings; 99 | -------------------------------------------------------------------------------- /src/app/pages/settings/wonderBlocksSettings.js: -------------------------------------------------------------------------------- 1 | import { useState } from '@wordpress/element'; 2 | import { useUpdateEffect } from 'react-use'; 3 | import { Alert, ToggleField } from '@newfold/ui-component-library'; 4 | import AppStore from '../../data/store'; 5 | import { featureToggle } from '../../util/helpers'; 6 | import { useNotification } from 'App/components/notifications'; 7 | 8 | const WonderBlocksSettings = () => { 9 | const { store, setStore } = useContext( AppStore ); 10 | const [ wonderBlocks, setWonderBlocks ] = useState( 11 | store.features.patterns 12 | ); 13 | const [ wonderBlocksLocked, setWonderBlocksLocked ] = useState( 14 | ! store.toggleableFeatures.patterns 15 | ); 16 | const [ isError, setError ] = useState( false ); 17 | const notify = useNotification(); 18 | 19 | const getWonderBlocksNoticeTitle = () => { 20 | return wonderBlocks 21 | ? __( 'WonderBlocks Enabled', 'wp-plugin-bluehost' ) 22 | : __( 'WonderBlocks Disabled', 'wp-plugin-bluehost' ); 23 | }; 24 | const getWonderBlocksNoticeText = () => { 25 | return wonderBlocks 26 | ? __( 27 | 'Create new content to see WonderBlocks in action.', 28 | 'wp-plugin-bluehost' 29 | ) 30 | : __( 31 | 'WonderBlocks will no longer display.', 32 | 'wp-plugin-bluehost' 33 | ); 34 | }; 35 | 36 | const toggleWonderBlocks = () => { 37 | featureToggle( 'patterns', ( response ) => { 38 | // console.log( response ); 39 | if ( response.success ) { 40 | setWonderBlocks( ! wonderBlocks ); 41 | } else { 42 | setWonderBlocksLocked( true ); 43 | setError( true ); 44 | notifyError(); 45 | } 46 | } ); 47 | }; 48 | 49 | const notifyError = () => { 50 | notify.push( 'feature-toggle-notice', { 51 | title: __( 'Sorry, that is not allowed.', 'wp-plugin-bluehost' ), 52 | description: __( 53 | 'This feature cannot currently be modified.', 54 | 'wp-plugin-bluehost' 55 | ), 56 | variant: 'error', 57 | } ); 58 | }; 59 | 60 | const notifySuccess = ( renderTitle, renderDescription ) => { 61 | notify.push( 'feature-toggle-notice', { 62 | title: renderTitle(), 63 | description: renderDescription(), 64 | variant: 'success', 65 | autoDismiss: 5000, 66 | } ); 67 | }; 68 | 69 | useUpdateEffect( () => { 70 | setStore( { 71 | ...store, 72 | wonderBlocks, 73 | } ); 74 | notifySuccess( getWonderBlocksNoticeTitle, getWonderBlocksNoticeText ); 75 | }, [ wonderBlocks ] ); 76 | 77 | return ( 78 |
79 | 90 | 91 | { isError && ( 92 | 93 | { __( 94 | 'Oops! Something went wrong. Please try again.', 95 | 'wp-plugin-bluehost' 96 | ) } 97 | 98 | ) } 99 |
100 | ); 101 | }; 102 | 103 | export default WonderBlocksSettings; 104 | -------------------------------------------------------------------------------- /src/app/pages/solutions/index.js: -------------------------------------------------------------------------------- 1 | import './style.scss'; 2 | import apiFetch from '@wordpress/api-fetch'; 3 | import { useState, useEffect } from '@wordpress/element'; 4 | import { useLocation, useMatch, useNavigate } from 'react-router-dom'; 5 | import { Container, Page } from '@newfold/ui-component-library'; 6 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 7 | import { default as NewfoldEntitlements } from '@modules/wp-module-solutions/components/entitlements'; 8 | 9 | const Solutions = () => { 10 | // constants to pass to module 11 | const moduleConstants = { 12 | text: { 13 | title: __( 'Solution', 'bluehost-wordpress-plugin' ), 14 | subTitle: __( 15 | 'Explore the plugins and tools available with your solution.', 16 | 'bluehost-wordpress-plugin' 17 | ), 18 | error: __( 19 | 'Oops, there was an error loading your plugins and tools, please try again later.', 20 | 'bluehost-wordpress-plugin' 21 | ), 22 | noEntitlements: __( 23 | 'Sorry, no current plugins and tools. Please, try again later.', 24 | 'bluehost-wordpress-plugin' 25 | ), 26 | }, 27 | }; 28 | 29 | // methods to pass to module 30 | const moduleMethods = { 31 | apiFetch, 32 | classNames, 33 | useState, 34 | useEffect, 35 | useLocation, 36 | useMatch, 37 | useNavigate, 38 | NewfoldRuntime, 39 | }; 40 | 41 | return ( 42 | 43 | 46 | 50 | 51 | 52 | ); 53 | }; 54 | 55 | export default Solutions; 56 | -------------------------------------------------------------------------------- /src/app/pages/solutions/style.scss: -------------------------------------------------------------------------------- 1 | .nfd-root { 2 | .nfd-modal.no-overlay { 3 | .nfd-modal__overlay { 4 | display: none; 5 | } 6 | } 7 | .nfd-feature-upsell.hide-html { 8 | .nfd-flex { 9 | background-color: rgb(255 255 255 / 0.5); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/app/pages/staging/index.js: -------------------------------------------------------------------------------- 1 | import './stylesheet.scss'; 2 | import { useState, useEffect } from '@wordpress/element'; 3 | import apiFetch from '@wordpress/api-fetch'; 4 | import { NewfoldRuntime } from '@newfold/wp-module-runtime'; 5 | import { useNotification } from 'App/components/notifications'; 6 | // component sourced from staging module 7 | import { default as NewfoldStaging } from '@modules/wp-module-staging/components/staging/'; 8 | 9 | const Staging = () => { 10 | // constants to pass to module 11 | const moduleConstants = { 12 | text: { 13 | title: __( 'Staging', 'bluehost-wordpress-plugin' ), 14 | }, 15 | }; 16 | 17 | // methods to pass to module 18 | const moduleMethods = { 19 | apiFetch, 20 | classNames, 21 | useState, 22 | useEffect, 23 | NewfoldRuntime, 24 | useNotification, 25 | }; 26 | 27 | return ( 28 | 32 | ); 33 | }; 34 | 35 | export default Staging; 36 | -------------------------------------------------------------------------------- /src/app/pages/staging/stylesheet.scss: -------------------------------------------------------------------------------- 1 | .newfold-staging-page.is-loading { 2 | position: relative; 3 | user-select: none; 4 | 5 | &::before { 6 | background: rgba(0,0,0,1); 7 | border-radius: 8px; 8 | content: ''; 9 | cursor: progress; 10 | display: block; 11 | height: 100%; 12 | opacity: .1; 13 | pointer-events: all; 14 | position: absolute; 15 | user-select: none; 16 | width: 100%; 17 | z-index: 1; 18 | } 19 | } 20 | .newfold-staging-page.is-thinking { 21 | position: relative; 22 | user-select: none; 23 | 24 | &::before { 25 | background: rgba(255,255,255,1); 26 | border-radius: 8px; 27 | content: ''; 28 | cursor: progress; 29 | display: block; 30 | height: 100%; 31 | opacity: .75; 32 | pointer-events: all; 33 | position: absolute; 34 | user-select: none; 35 | width: 100%; 36 | z-index: 1; 37 | } 38 | 39 | &::after{ 40 | background-image: url(../../images/loading-logo.svg); 41 | background-position: center center; 42 | background-size: 200px auto; 43 | background-repeat: no-repeat; 44 | content: ''; 45 | cursor: progress; 46 | display: block; 47 | height: 100%; 48 | opacity: 0.75; 49 | pointer-events: all; 50 | position: absolute; 51 | user-select: none; 52 | width: 100%; 53 | z-index: 2; 54 | } 55 | } -------------------------------------------------------------------------------- /src/app/util/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { default as useHandlePageLoad } from './useHandlePageLoad.js'; 2 | -------------------------------------------------------------------------------- /src/app/util/hooks/useContainerBlockTarget.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from '@wordpress/element'; 2 | 3 | /** 4 | * Checks if the container block is the target element based on the query param. 5 | * If true, the hook will return that boolean value for a few seconds. 6 | * The hook will set the value to false and remove the query param. 7 | * 8 | * @param {string} id - the container block id, used to check if it's the target. 9 | * @return {boolean} - true if the container block is the target. 10 | */ 11 | const useContainerBlockIsTarget = ( id ) => { 12 | const [ isTarget, setIsTarget ] = useState( false ); 13 | const searchParams = new URLSearchParams( window.location.search ); 14 | 15 | useEffect( () => { 16 | if ( 17 | searchParams.has( 'nfd-target' ) && 18 | searchParams.get( 'nfd-target' ) === id 19 | ) { 20 | setIsTarget( true ); 21 | 22 | setTimeout( () => { 23 | setIsTarget( false ); 24 | removeTargetQueryParam(); 25 | }, 9500 ); 26 | } 27 | // eslint-disable-next-line react-hooks/exhaustive-deps 28 | }, [ searchParams ] ); 29 | 30 | /* 31 | * Remove the 'nfd-target={id}' query param from the URL 32 | */ 33 | const removeTargetQueryParam = () => { 34 | searchParams.delete( 'nfd-target' ); 35 | const currentURL = window.location.href; 36 | const updatedURL = currentURL.replace( `&nfd-target=${ id }`, '' ); 37 | window.history.replaceState( null, null, updatedURL ); 38 | }; 39 | 40 | return isTarget; 41 | }; 42 | 43 | export default useContainerBlockIsTarget; 44 | -------------------------------------------------------------------------------- /src/app/util/hooks/useHandlePageLoad.js: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router-dom'; 2 | 3 | /** 4 | * Scrolls to top of page on route change. 5 | * @return {boolean} - true 6 | */ 7 | const useHandlePageLoad = () => { 8 | const location = useLocation(); 9 | const routeContents = document.querySelector( '.wppbh-app-body-inner' ); 10 | 11 | useEffect( () => { 12 | window.scrollTo( 0, 0 ); 13 | if ( routeContents ) { 14 | routeContents.focus( { preventScroll: true } ); 15 | } 16 | // eslint-disable-next-line react-hooks/exhaustive-deps 17 | }, [ location.pathname ] ); 18 | 19 | return true; 20 | }; 21 | 22 | export default useHandlePageLoad; 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* Use PHP-provided URL to current version's build directory instead of root */ 2 | import './webpack-public-path'; 3 | 4 | import domReady from '@wordpress/dom-ready'; 5 | import { createRoot, render } from '@wordpress/element'; 6 | import App from './app'; 7 | 8 | const WP_ADM_PAGE_ROOT_ELEMENT = 'wppbh-app'; 9 | 10 | // eslint-disable-next-line no-console 11 | console.log( 12 | `%cWelcome to Bluehost! 13 | %c 14 | ██████████████████████████████████████ 15 | ███ ██ ██ ███ 16 | ███ ██ ██ ███ 17 | ███ ██ ██ ███ 18 | ███ ██ ██ ███ 19 | ██████████████████████████████████████ 20 | ███ ██ ██ ███ 21 | ███ ██ ██ ███ 22 | ███ ██ ██ ███ 23 | ███ ██ ██ ███ 24 | ██████████████████████████████████████ 25 | ███ ██ ██ ███ 26 | ███ ██ ██ ███ 27 | ███ ██ ██ ███ 28 | ███ ██ ██ ███ 29 | ██████████████████████████████████████ 30 | `, 31 | 'color: #196bde; font-size: 2em; font-weight: 900;', 32 | 'background: #fff; color: #196bde; font-weight: 400;' 33 | ); 34 | 35 | const WPPBHRender = () => { 36 | const DOM_ELEMENT = document.getElementById( WP_ADM_PAGE_ROOT_ELEMENT ); 37 | if ( null !== DOM_ELEMENT ) { 38 | if ( 'undefined' !== typeof createRoot ) { 39 | // WP 6.2+ only 40 | createRoot( DOM_ELEMENT ).render( ); 41 | } else if ( 'undefined' !== typeof render ) { 42 | render( , DOM_ELEMENT ); 43 | } 44 | } 45 | }; 46 | 47 | domReady( WPPBHRender ); 48 | -------------------------------------------------------------------------------- /src/webpack-public-path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set webpack's public path that defaults to the root directory to be the plugin's build directory 3 | * so that lazy-loading works correctly. This value is set in /includes/Data.php in runtime(). 4 | */ 5 | export default () => { 6 | if ( 7 | 'undefined' !== typeof window.NewfoldRuntime && 8 | 'plugin' in window.NewfoldRuntime 9 | ) { 10 | // eslint-disable-next-line camelcase, no-undef 11 | __webpack_public_path__ = window.NewfoldRuntime.plugin.url; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import { TAILWINDCSS_PRESET } from '@newfold/ui-component-library'; 2 | 3 | module.exports = { 4 | presets: [ TAILWINDCSS_PRESET ], 5 | content: [ 6 | // Include all JS files inside the UI library in your content. 7 | ...TAILWINDCSS_PRESET.content, 8 | './src/**/*.js', // all source files 9 | './node_modules/@newfold/wp-module-*/build/index.js', // all npmjs sourced module builds 10 | './node_modules/@newfold-labs/wp-module-*/build/index.js', // all github npm sourced module builds 11 | './vendor/newfold-labs/wp-module-*/components/**/*.js', // all composer sourced module components 12 | './vendor/newfold-labs/wp-module-*/src/components/**/*.js', // more composer sourced module components 13 | ], 14 | theme: { 15 | extend: { 16 | colors: { 17 | primary: { 18 | DEFAULT: '#196BDE', 19 | dark: '#1A4884', 20 | light: '#CCDCF4', 21 | lighter: '#949FB1', 22 | }, 23 | secondary: { 24 | DEFAULT: '#FCD34D', 25 | dark: '#E9B404', 26 | light: '#FEF6D9', 27 | lighter: '#FEF6D9', 28 | }, 29 | title: '#0F172A', 30 | body: '#4A5567', 31 | link: '#2271B1', 32 | line: '#E2E8F0', 33 | white: '#FFFFFF', 34 | offWhite: '#F0F0F5', 35 | black: '#000000', 36 | canvas: '#F1F5F9', 37 | }, 38 | }, 39 | }, 40 | plugins: [], 41 | }; 42 | -------------------------------------------------------------------------------- /tests/cypress/fixtures/plugin-notifications.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "test-1", 4 | "locations": [ 5 | { 6 | "context": "bluehost-plugin", 7 | "pages": "#/settings" 8 | } 9 | ], 10 | "expiration": 2748863456503, 11 | "content": "

Notice should only display on plugin app settings page<\/p><\/div>" 12 | }, 13 | { 14 | "id": "test-2", 15 | "locations": [ 16 | { 17 | "context": "bluehost-plugin", 18 | "pages": [ 19 | "#/home/onboarding", 20 | "#/home" 21 | ] 22 | } 23 | ], 24 | "expiration": 2749860279240, 25 | "content": "

Here is a plugin notice it should display on home and onboarding screens only! <\/p><\/div>" 26 | }, 27 | { 28 | "id": "test-3", 29 | "locations": [ 30 | { 31 | "context": "bluehost-plugin", 32 | "pages": [ 33 | "#/marketplace" 34 | ] 35 | } 36 | ], 37 | "expiration": 2749860279240, 38 | "content": "

Here is a plugin notice it should display on marketplace themes screen only! <\/p><\/div>" 39 | }, 40 | { 41 | "id": "test-4", 42 | "locations": [ 43 | { 44 | "context": "bluehost-plugin", 45 | "pages": "all" 46 | }, 47 | { 48 | "context": "wp-admin-notice", 49 | "pages": "all" 50 | } 51 | ], 52 | "expiration": 2749860279240, 53 | "content": "

Here is a notice it should display everywhere in the app and in wp-admin! <\/p><\/div>" 54 | }, 55 | { 56 | "id": "test-expired", 57 | "locations": [ 58 | { 59 | "context": "bluehost-plugin", 60 | "pages": "all" 61 | } 62 | ], 63 | "expiration": 1649860279240, 64 | "content": "

Here is an expired notice it should never display anywhere even though it has location `all` <\/p><\/div>" 65 | } 66 | ] -------------------------------------------------------------------------------- /tests/cypress/fixtures/webinars-inactive.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "title": "Maximize Productivity with Time Management", 5 | "description" : "Phasellus sagittis gravida molestie. Vivamus dictum, massa luctus tempus imperdiet, ligula leo ultrices purus, sit amet consectetur ligula turpis non nisi.", 6 | "date": "November 15, 2022", 7 | "time": "2pm - 3pm EST", 8 | "ctaText": "Sign Up Today", 9 | "link": "https://www.bluehost.com/blog/events/how-to-start-with-seo-yoast/" 10 | }, 11 | { 12 | "title": "Build your brand with WordPress", 13 | "description": "Join us for a free webinar on how to build your brand with WordPress.", 14 | "topics": [ 15 | "Explore SEO and WordPress", 16 | "Gain valuable tips for building your brand", 17 | "Content marketing", 18 | "eCommerce integration" 19 | ], 20 | "date": "August 31, 2040", 21 | "time": "1pm - 2pm EST", 22 | "ctaText": "Register Now", 23 | "link": "https://www.bluehost.com/blog/events/next-event-post" 24 | }, 25 | { 26 | "title": "Mastering the Art of Public Speaking", 27 | "topics": [ 28 | "Overcoming stage fright and nervousness", 29 | "Crafting compelling speeches and presentations", 30 | "Engaging your audience effectively", 31 | "Tips for confident and impactful public speaking" 32 | ], 33 | "date": "December 10, 2050", 34 | "time": "3pm - 4pm EST", 35 | "ctaText": "Enroll Now", 36 | "link": "https://www.bluehost.com/blog/events/yoast-seo-news-october-edition/" 37 | } 38 | ], 39 | "isActive": false 40 | } -------------------------------------------------------------------------------- /tests/cypress/fixtures/webinars-past.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "title": "Maximize Productivity with Time Management", 5 | "description" : "Phasellus sagittis gravida molestie. Vivamus dictum, massa luctus tempus imperdiet, ligula leo ultrices purus, sit amet consectetur ligula turpis non nisi.", 6 | "date": "November 15, 2022", 7 | "time": "2pm - 3pm EST", 8 | "ctaText": "Sign Up Today", 9 | "link": "https://www.bluehost.com/blog/events/how-to-start-with-seo-yoast/" 10 | }, 11 | { 12 | "title": "Build your brand with WordPress", 13 | "description": "Join us for a free webinar on how to build your brand with WordPress.", 14 | "topics": [ 15 | "Explore SEO and WordPress", 16 | "Gain valuable tips for building your brand", 17 | "Content marketing", 18 | "eCommerce integration" 19 | ], 20 | "date": "August 31, 2022", 21 | "time": "1pm - 2pm EST", 22 | "ctaText": "Register Now", 23 | "link": "https://www.bluehost.com/blog/events/next-event-post" 24 | }, 25 | { 26 | "title": "Mastering the Art of Public Speaking", 27 | "topics": [ 28 | "Overcoming stage fright and nervousness", 29 | "Crafting compelling speeches and presentations", 30 | "Engaging your audience effectively", 31 | "Tips for confident and impactful public speaking" 32 | ], 33 | "date": "December 10, 2022", 34 | "time": "3pm - 4pm EST", 35 | "ctaText": "Enroll Now", 36 | "link": "https://www.bluehost.com/blog/events/yoast-seo-news-october-edition/" 37 | } 38 | ], 39 | "isActive": true 40 | } -------------------------------------------------------------------------------- /tests/cypress/fixtures/webinars.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "title": "Maximize Productivity with Time Management", 5 | "description" : "Phasellus sagittis gravida molestie. Vivamus dictum, massa luctus tempus imperdiet, ligula leo ultrices purus, sit amet consectetur ligula turpis non nisi.", 6 | "date": "November 15, 2022", 7 | "time": "2pm - 3pm EST", 8 | "ctaText": "Sign Up Today", 9 | "link": "https://www.bluehost.com/blog/events/how-to-start-with-seo-yoast/" 10 | }, 11 | { 12 | "title": "Build your brand with WordPress", 13 | "description": "Join us for a free webinar on how to build your brand with WordPress.", 14 | "topics": [ 15 | "Explore SEO and WordPress", 16 | "Gain valuable tips for building your brand", 17 | "Content marketing", 18 | "eCommerce integration" 19 | ], 20 | "date": "August 31, 2040", 21 | "time": "1pm - 2pm EST", 22 | "ctaText": "Register Now", 23 | "link": "https://www.bluehost.com/blog/events/next-event-post" 24 | }, 25 | { 26 | "title": "Mastering the Art of Public Speaking", 27 | "topics": [ 28 | "Overcoming stage fright and nervousness", 29 | "Crafting compelling speeches and presentations", 30 | "Engaging your audience effectively", 31 | "Tips for confident and impactful public speaking" 32 | ], 33 | "date": "December 10, 2050", 34 | "time": "3pm - 4pm EST", 35 | "ctaText": "Enroll Now", 36 | "link": "https://www.bluehost.com/blog/events/yoast-seo-news-october-edition/" 37 | } 38 | ], 39 | "isActive": true 40 | } -------------------------------------------------------------------------------- /tests/cypress/integration/help.cy.js: -------------------------------------------------------------------------------- 1 | // 2 | const pluginNotificationsFixture = require( '../fixtures/plugin-notifications.json' ); 3 | const pluginProductsFixture = require( '../fixtures/plugin-products.json' ); 4 | 5 | describe( 'Help Page', { testIsolation: true }, function () { 6 | beforeEach( () => { 7 | cy.wpLogin(); 8 | cy.intercept( 9 | { 10 | method: 'GET', 11 | url: /newfold-marketplace(\/|%2F)v1(\/|%2F)marketplace/, 12 | }, 13 | pluginProductsFixture 14 | ).as( 'pluginProductsFixture' ); 15 | cy.intercept( 16 | { 17 | method: 'GET', 18 | url: /newfold-notifications(\/|%2F)v1(\/|%2F)notifications/, 19 | }, 20 | pluginNotificationsFixture 21 | ).as( 'pluginNotificationsFixture' ); 22 | cy.visit( 23 | '/wp-admin/admin.php?page=' + Cypress.env( 'pluginId' ) + '#/help', 24 | { timeout: 30000 } 25 | ); 26 | } ); 27 | 28 | it( 'Is Accessible', () => { 29 | cy.injectAxe(); 30 | cy.wait( 500 ); 31 | cy.checkA11y( '.wppbh-app-body' ); 32 | } ); 33 | 34 | it( 'Cards Each Exist', () => { 35 | cy.get( '.card-help-phone' ) 36 | .contains( 'h3', 'Phone' ) 37 | .scrollIntoView() 38 | .should( 'be.visible' ); 39 | 40 | cy.get( '.card-help-chat' ) 41 | .contains( 'h3', 'Chat' ) 42 | .scrollIntoView() 43 | .should( 'be.visible' ); 44 | 45 | cy.get( '.card-help-twitter' ) 46 | .contains( 'h3', 'Tweet' ) 47 | .scrollIntoView() 48 | .should( 'be.visible' ); 49 | 50 | cy.get( '.card-help-youtube' ) 51 | .contains( 'h3', 'YouTube' ) 52 | .scrollIntoView() 53 | .should( 'be.visible' ); 54 | 55 | cy.get( '.card-help-kb' ) 56 | .contains( 'h3', 'Knowledge Base' ) 57 | .scrollIntoView() 58 | .should( 'be.visible' ); 59 | 60 | cy.get( '.card-help-resources' ) 61 | .contains( 'h3', 'Resources' ) 62 | .scrollIntoView() 63 | .should( 'be.visible' ); 64 | 65 | cy.get( '.card-help-events' ) 66 | .contains( 'h3', 'Events and Webinars' ) 67 | .scrollIntoView() 68 | .should( 'be.visible' ); 69 | 70 | cy.get( '.card-help-website' ) 71 | .contains( 'h3', 'Bluehost Website' ) 72 | .scrollIntoView() 73 | .should( 'be.visible' ); 74 | } ); 75 | } ); 76 | -------------------------------------------------------------------------------- /tests/cypress/integration/home.cy.js: -------------------------------------------------------------------------------- 1 | // 2 | const webinarsFixture = require( '../fixtures/webinars.json' ); 3 | const webinarsPastFixture = require( '../fixtures/webinars-past.json' ); 4 | const webinarsInactiveFixture = require( '../fixtures/webinars-inactive.json' ); 5 | 6 | describe( 'Home Page', { testIsolation: true }, function () { 7 | let NewfoldRuntime; 8 | 9 | beforeEach( () => { 10 | cy.wpLogin(); 11 | cy.visit( 12 | '/wp-admin/admin.php?page=' + Cypress.env( 'pluginId' ) + '#/home' 13 | ); 14 | cy.window() 15 | .its( 'NewfoldRuntime' ) 16 | .then( ( data ) => { 17 | NewfoldRuntime = data; 18 | } ); 19 | } ); 20 | 21 | it( 'Site Info Exists', () => { 22 | cy.get( '.wppbh-app-site-info' ) 23 | .contains( 'h3', NewfoldRuntime.siteTitle ) 24 | .scrollIntoView() 25 | .should( 'be.visible' ); 26 | } ); 27 | 28 | it( 'Is Accessible', () => { 29 | cy.injectAxe(); 30 | cy.wait( 500 ); 31 | cy.checkA11y( '.wppbh-app-body' ); 32 | } ); 33 | 34 | it( 'Welcome Section Exists', () => { 35 | cy.get( '.nfd-app-section-container' ) 36 | .contains( 'h2', 'Home' ) 37 | .scrollIntoView() 38 | .should( 'be.visible' ); 39 | } ); 40 | 41 | it.skip( 'Additional Features Section Exists', () => { 42 | cy.get( '.nfd-app-section-container' ) 43 | .contains( 'h2', 'Additional Features' ) 44 | .scrollIntoView() 45 | .should( 'be.visible' ); 46 | } ); 47 | 48 | it( 'Account Section Exists', () => { 49 | cy.get( '.wppbh-account-help-section' ) 50 | .contains( 'h1', 'Bluehost Account' ) 51 | .scrollIntoView() 52 | .should( 'be.visible' ); 53 | } ); 54 | 55 | it( 'Help Section Exists', () => { 56 | cy.get( '.wppbh-account-help-section' ) 57 | .contains( 'h1', 'Need some help?' ) 58 | .scrollIntoView() 59 | .should( 'be.visible' ); 60 | } ); 61 | 62 | it( 'Webinars Section Exists and Renders Properly', () => { 63 | cy.intercept( 64 | { 65 | method: 'GET', 66 | url: 'https://cdn.hiive.space/resources/bluehost-webinars.json', 67 | }, 68 | webinarsFixture 69 | ).as( 'webinarsFixture' ); 70 | cy.reload(); 71 | cy.wait( '@webinarsFixture' ); 72 | cy.get( '.wppbh-webinars-banner-section' ) 73 | .contains( 'h2', 'Build your brand with WordPress' ) 74 | .scrollIntoView() 75 | .should( 'be.visible' ); 76 | 77 | // Title 78 | cy.get( '.wppbh-webinars-banner-section' ) 79 | .contains( 'h2', 'Build your brand with WordPress' ) 80 | .scrollIntoView() 81 | .should( 'be.visible' ); 82 | 83 | // Description 84 | cy.get( '.wppbh-webinars-banner-section p:first-of-type' ) 85 | .contains( 86 | 'Join us for a free webinar on how to build your brand with WordPress.' 87 | ) 88 | .scrollIntoView() 89 | .should( 'be.visible' ); 90 | 91 | // Topics 92 | cy.get( '.wppbh-webinars-banner-section h3' ) 93 | .contains( 'Topics:' ) 94 | .scrollIntoView() 95 | .should( 'be.visible' ); 96 | 97 | // Date 98 | cy.get( '.wppbh-webinars-banner-section' ) 99 | .contains( 'August 31, 2040' ) 100 | .scrollIntoView() 101 | .should( 'be.visible' ); 102 | 103 | // Time 104 | cy.get( '.wppbh-webinars-banner-section' ) 105 | .contains( '1pm - 2pm EST' ) 106 | .scrollIntoView() 107 | .should( 'be.visible' ); 108 | 109 | // CTA 110 | cy.get( '.wppbh-webinars-banner-section a:first-of-type' ) 111 | .contains( 'Register Now' ) 112 | .scrollIntoView() 113 | .should( 'be.visible' ) 114 | .should( 'have.attr', 'href' ) 115 | .and( 116 | 'include', 117 | 'https://www.bluehost.com/blog/events/next-event-post' 118 | ); 119 | } ); 120 | 121 | it( "Webinars Section Doesn't Render When Active Propety is False", () => { 122 | cy.intercept( 123 | { 124 | method: 'GET', 125 | url: 'https://cdn.hiive.space/resources/bluehost-webinars.json', 126 | }, 127 | webinarsInactiveFixture 128 | ).as( 'webinarsInactiveFixture' ); 129 | cy.reload(); 130 | cy.wait( '@webinarsInactiveFixture' ); 131 | cy.get( '.wppbh-webinars-banner-section' ).should( 'not.exist' ); 132 | } ); 133 | 134 | it( "Webinars Section Doesn't Render Items Are in the Past", () => { 135 | cy.intercept( 136 | { 137 | method: 'GET', 138 | url: 'https://cdn.hiive.space/resources/bluehost-webinars.json', 139 | }, 140 | webinarsPastFixture 141 | ).as( 'webinarsPastFixture' ); 142 | cy.reload(); 143 | cy.wait( '@webinarsPastFixture' ); 144 | cy.get( '.wppbh-webinars-banner-section' ).should( 'not.exist' ); 145 | } ); 146 | } ); 147 | -------------------------------------------------------------------------------- /tests/cypress/integration/navigation.cy.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | describe( 'Navigation', { testIsolation: true }, function () { 4 | beforeEach( () => { 5 | cy.wpLogin(); 6 | cy.visit( '/wp-admin/admin.php?page=' + Cypress.env( 'pluginId' ) ); 7 | } ); 8 | 9 | it( "Admin submenu shouldn't exist inside app", () => { 10 | cy.get( '#adminmenu #toplevel_page_bluehost ul.wp-submenu' ).should( 11 | 'not.exist' 12 | ); 13 | } ); 14 | 15 | it( 'Logo Links to home', () => { 16 | cy.get( '.wppbh-logo-wrap' ).click(); 17 | cy.wait( 500 ); 18 | cy.hash().should( 'eq', '#/home' ); 19 | } ); 20 | 21 | // test main nav 22 | it( 'Main nav links properly navigates', () => { 23 | cy.get( '.wppbh-app-navitem-Marketplace' ).should( 24 | 'not.have.class', 25 | 'active' 26 | ); 27 | cy.get( '.wppbh-app-navitem-Marketplace' ).click(); 28 | cy.wait( 500 ); 29 | cy.hash().should( 'eq', '#/marketplace' ); 30 | cy.get( '.wppbh-app-navitem-Marketplace' ).should( 31 | 'have.class', 32 | 'active' 33 | ); 34 | 35 | cy.get( '.wppbh-app-navitem-Performance' ).click(); 36 | cy.wait( 500 ); 37 | cy.hash().should( 'eq', '#/performance' ); 38 | cy.get( '.wppbh-app-navitem-Performance' ).should( 39 | 'have.class', 40 | 'active' 41 | ); 42 | cy.get( '.wppbh-app-navitem-Marketplace' ).should( 43 | 'not.have.class', 44 | 'active' 45 | ); 46 | 47 | cy.get( '.wppbh-app-navitem-Settings' ).click(); 48 | cy.wait( 500 ); 49 | cy.hash().should( 'eq', '#/settings' ); 50 | } ); 51 | 52 | it( 'Subnav links properly navigates', () => { 53 | cy.get( '.wppbh-app-navitem-Marketplace' ) 54 | .scrollIntoView() 55 | .should( 'not.have.class', 'active' ); 56 | cy.get( '.wppbh-app-navitem-Marketplace' ).click(); 57 | 58 | cy.wait( 500 ); 59 | cy.hash().should( 'eq', '#/marketplace' ); 60 | cy.get( '.wppbh-app-navitem-Marketplace' ).should( 61 | 'have.class', 62 | 'active' 63 | ); 64 | 65 | cy.get( '.wppbh-app-subnavitem-Services' ).click(); 66 | cy.wait( 500 ); 67 | cy.hash().should( 'eq', '#/marketplace/services' ); 68 | cy.get( '.wppbh-app-subnavitem-Services' ).should( 69 | 'have.class', 70 | 'active' 71 | ); 72 | cy.get( '.wppbh-app-navitem-Marketplace' ).should( 73 | 'have.class', 74 | 'active' 75 | ); 76 | 77 | cy.get( '.wppbh-app-subnavitem-SEO' ).click(); 78 | cy.wait( 500 ); 79 | cy.hash().should( 'eq', '#/marketplace/seo' ); 80 | cy.get( '.wppbh-app-subnavitem-SEO' ).should( 'have.class', 'active' ); 81 | cy.get( '.wppbh-app-subnavitem-Services' ).should( 82 | 'not.have.class', 83 | 'active' 84 | ); 85 | cy.get( '.wppbh-app-navitem-Marketplace' ).should( 86 | 'have.class', 87 | 'active' 88 | ); 89 | 90 | cy.get( '.wppbh-app-navitem-Performance' ).click(); 91 | cy.wait( 500 ); 92 | cy.get( '.wppbh-app-subnavitem-Services' ).should( 93 | 'not.have.class', 94 | 'active' 95 | ); 96 | cy.get( '.wppbh-app-subnavitem-SEO' ).should( 97 | 'not.have.class', 98 | 'active' 99 | ); 100 | cy.get( '.wppbh-app-navitem-Marketplace' ).should( 101 | 'not.have.class', 102 | 'active' 103 | ); 104 | } ); 105 | 106 | it( 'Admin submenu exist outside the app', () => { 107 | cy.visit( '/wp-admin/index.php' ); 108 | cy.get( '#adminmenu #toplevel_page_bluehost ul.wp-submenu' ).should( 109 | 'exist' 110 | ); 111 | cy.get( 112 | '#adminmenu #toplevel_page_bluehost ul.wp-submenu li a[href="admin.php?page=bluehost#/home"]' 113 | ).should( 'exist' ); 114 | cy.get( 115 | '#adminmenu #toplevel_page_bluehost ul.wp-submenu li a[href="admin.php?page=bluehost#/marketplace"]' 116 | ).should( 'exist' ); 117 | cy.get( 118 | '#adminmenu #toplevel_page_bluehost ul.wp-submenu li a[href="admin.php?page=bluehost#/settings"]' 119 | ).should( 'exist' ); 120 | } ); 121 | 122 | it( 'Mobile nav links dispaly and link properly on mobile', () => { 123 | cy.get( '#nfd-app-mobile-nav' ).should( 'not.exist' ); 124 | 125 | cy.viewport( 'iphone-x' ); 126 | cy.get( '#nfd-app-mobile-nav' ).should( 'be.visible' ); 127 | 128 | cy.get( '.wppbh-app-navitem-Home' ).should( 'not.exist' ); 129 | 130 | cy.get( '#nfd-app-mobile-nav' ).click(); 131 | cy.wait( 500 ); 132 | cy.get( '.wppbh-app-navitem-Home' ).should( 'be.visible' ); 133 | cy.get( 'button.nfd-modal__close-button' ).should( 'be.visible' ); 134 | cy.get( 'button.nfd-modal__close-button' ).click(); 135 | cy.get( '.wppbh-app-navitem-Home' ).should( 'not.exist' ); 136 | } ); 137 | } ); 138 | -------------------------------------------------------------------------------- /tests/cypress/integration/pages-and-posts.cy.js: -------------------------------------------------------------------------------- 1 | describe( 'Pages & Posts', { testIsolation: true }, function () { 2 | let NewfoldRuntime; 3 | 4 | beforeEach( () => { 5 | cy.wpLogin(); 6 | cy.visit( 7 | '/wp-admin/admin.php?page=' + 8 | Cypress.env( 'pluginId' ) + 9 | '#/pages-and-posts' 10 | ); 11 | cy.window() 12 | .its( 'NewfoldRuntime' ) 13 | .then( ( data ) => { 14 | NewfoldRuntime = data; 15 | } ); 16 | cy.injectAxe(); 17 | } ); 18 | 19 | it( 'Pages & Posts Exists', () => { 20 | cy.get( '.wppbh-app-pagesAndPosts-page' ) 21 | .contains( 'Pages & Posts' ) 22 | .scrollIntoView() 23 | .should( 'be.visible' ); 24 | } ); 25 | 26 | it( 'site pages Exists', () => { 27 | cy.get( '.wppbh-app-site-page' ) 28 | .findByText( 'Site Pages' ) 29 | .should( 'exist' ); 30 | cy.get( '.wppbh-app-site-page' ) 31 | .find( 'a[href="edit.php?post_type=page"]' ) 32 | .click(); 33 | cy.url().should( 'include', 'edit.php?post_type=page' ); 34 | cy.go( 'back' ); 35 | 36 | cy.get( '.wppbh-app-site-page' ) 37 | .find( 38 | 'a[href="post-new.php?post_type=page&wb-library=patterns&wb-category=features"] Button' 39 | ) 40 | .click(); 41 | cy.url().should( 42 | 'include', 43 | 'post-new.php?post_type=page&wb-library=patterns&wb-category=features' 44 | ); 45 | cy.go( 'back' ); 46 | } ); 47 | 48 | it( 'Blog posts Exists', () => { 49 | cy.get( '.wppbh-app-blog-posts' ) 50 | .findByText( 'Blog Posts' ) 51 | .should( 'exist' ); 52 | cy.get( '.wppbh-app-blog-posts' ).find( 'a[href="edit.php"]' ).click(); 53 | cy.url().should( 'include', 'edit.php' ); 54 | cy.go( 'back' ); 55 | 56 | cy.get( '.wppbh-app-blog-posts' ) 57 | .get( 58 | 'a[href="post-new.php?wb-library=patterns&wb-category=text"] Button' 59 | ) 60 | .click(); 61 | cy.url().should( 62 | 'include', 63 | 'post-new.php?wb-library=patterns&wb-category=text' 64 | ); 65 | cy.go( 'back' ); 66 | } ); 67 | 68 | it( 'Bookings & Appointments Exists', () => { 69 | if ( 70 | NewfoldRuntime.capabilities.hasYithExtended && 71 | NewfoldRuntime.isWoocommerceActive 72 | ) { 73 | cy.get( '.wppbh-app-bookings' ) 74 | .findByText( 'Bookings & Appointments' ) 75 | .should( 'exist' ); 76 | cy.get( '.wppbh-app-bookings' ) 77 | .find( 78 | 'a[href="edit.php?post_type=yith_booking&yith-plugin-fw-panel-skip-redirect=1"]' 79 | ) 80 | .first() 81 | .click(); 82 | cy.url().should( 83 | 'include', 84 | 'edit.php?post_type=yith_booking&yith-plugin-fw-panel-skip-redirect=1' 85 | ); 86 | cy.go( 'back' ); 87 | 88 | cy.get( '.wppbh-app-bookings' ) 89 | .find( 90 | 'a[href="edit.php?post_type=yith_booking&yith-plugin-fw-panel-skip-redirect=1"] Button' 91 | ) 92 | .last() 93 | .click(); 94 | cy.url().should( 95 | 'include', 96 | 'edit.php?post_type=yith_booking&yith-plugin-fw-panel-skip-redirect=1' 97 | ); 98 | cy.go( 'back' ); 99 | } else { 100 | cy.get( '.wppbh-app-transform' ) 101 | .findByText( 'Transform your store!' ) 102 | .should( 'exist' ); 103 | cy.get( '.wppbh-app-transform' ) 104 | .find( 105 | 'a[href="admin.php?page=bluehost#/marketplace/product/6049dddb-1303-4c41-b3c0-242881697860"]' 106 | ) 107 | .first() 108 | .click(); 109 | cy.url().should( 110 | 'include', 111 | 'admin.php?page=bluehost#/marketplace/product/6049dddb-1303-4c41-b3c0-242881697860' 112 | ); 113 | cy.go( 'back' ); 114 | } 115 | } ); 116 | 117 | it( 'Products Exists', () => { 118 | if ( NewfoldRuntime.isWoocommerceActive ) { 119 | cy.get( '.wppbh-app-products' ) 120 | .findByText( 'Products' ) 121 | .should( 'exist' ); 122 | cy.get( '.wppbh-app-products' ) 123 | .find( 'a[href="edit.php?post_type=product"]' ) 124 | .click(); 125 | cy.url().should( 'include', 'edit.php?post_type=product' ); 126 | cy.go( 'back' ); 127 | 128 | cy.get( '.wppbh-app-products' ) 129 | .find( 'a[href="post-new.php?post_type=product"] Button' ) 130 | .click(); 131 | cy.url().should( 'include', 'post-new.php?post_type=product' ); 132 | cy.go( 'back' ); 133 | } else { 134 | cy.findByText( 'Products' ).should( 'not.exist' ); 135 | } 136 | } ); 137 | } ); 138 | -------------------------------------------------------------------------------- /tests/cypress/integration/version-check.cy.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | describe( 'Page', { testIsolation: true }, () => { 4 | beforeEach( () => { 5 | cy.wpLogin(); 6 | cy.visit( '/wp-admin/site-health.php?tab=debug' ); 7 | } ); 8 | 9 | it( 'Is running the correct WP version', () => { 10 | cy.get( '#health-check-accordion-block-wp-core' ).prev().click(); 11 | cy.get( '#health-check-accordion-block-wp-core' ) 12 | .find( 'tr' ) 13 | .first() 14 | .find( 'td' ) 15 | .last() 16 | .contains( new RegExp( `^${ Cypress.env( 'wpVersion' ) }` ) ); 17 | } ); 18 | 19 | it( 'Is running the correct PHP versions', () => { 20 | cy.get( '#health-check-accordion-block-wp-server' ).prev().click(); 21 | cy.get( '#health-check-accordion-block-wp-server' ) 22 | .find( 'tr' ) 23 | .eq( 2 ) 24 | .find( 'td' ) 25 | .last() 26 | .contains( Cypress.env( 'phpVersion' ) ); 27 | } ); 28 | } ); 29 | -------------------------------------------------------------------------------- /tests/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (on, config) => { 4 | 5 | if (config.env && config.env.baseUrl) { 6 | config.baseUrl = config.env.baseUrl; 7 | } 8 | 9 | return config; 10 | }; 11 | -------------------------------------------------------------------------------- /tests/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // 2 | 3 | // *********************************************** 4 | // This example commands.js shows you how to 5 | // create various custom commands and overwrite 6 | // existing commands. 7 | // 8 | // For more comprehensive examples of custom 9 | // commands please read more here: 10 | // https://on.cypress.io/custom-commands 11 | // *********************************************** 12 | // 13 | // 14 | // -- This is a parent command -- 15 | // Cypress.Commands.add("login", (email, password) => { ... }) 16 | // 17 | // 18 | // -- This is a child command -- 19 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 20 | // 21 | // 22 | // -- This is a dual command -- 23 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 24 | // 25 | // 26 | // -- This is will overwrite an existing command -- 27 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 28 | 29 | import '@testing-library/cypress/add-commands'; 30 | 31 | Cypress.Commands.add( 'login', ( username, password ) => { 32 | cy.getCookies().then( ( cookies ) => { 33 | let hasMatch = false; 34 | cookies.forEach( ( cookie ) => { 35 | if ( cookie.name.substr( 0, 20 ) === 'wordpress_logged_in_' ) { 36 | hasMatch = true; 37 | } 38 | } ); 39 | if ( ! hasMatch ) { 40 | cy.visit( '/wp-login.php' ).wait( 1000 ); 41 | cy.get( '#user_login' ).type( username ); 42 | cy.get( '#user_pass' ).type( `${ password }{enter}` ); 43 | 44 | // Speed up tests by setting permalink structure once 45 | // cy.setPermalinkStructure(); 46 | } 47 | } ); 48 | } ); 49 | 50 | Cypress.Commands.add( 'wpLogin', () => { 51 | cy.login( Cypress.env( 'wpUsername' ), Cypress.env( 'wpPassword' ) ); 52 | } ); 53 | 54 | Cypress.Commands.add( 'logout', () => { 55 | cy.getCookies().then( ( cookies ) => { 56 | cookies.forEach( ( cookie ) => { 57 | cy.clearCookie( cookie.name ); 58 | } ); 59 | } ); 60 | } ); 61 | 62 | Cypress.Commands.add( 63 | 'setPermalinkStructure', 64 | ( structure = '/%postname%/' ) => { 65 | cy.request( { 66 | method: 'GET', 67 | url: '/wp-json/', 68 | failOnStatusCode: false, 69 | } ).then( ( result ) => { 70 | if ( result.isOkStatusCode ) { 71 | return; 72 | } 73 | const permalinkWpCliCommand = `wp rewrite structure "${ structure }" --hard;`; 74 | const permalinkWpEnvCommand = `npx wp-env run cli ${ permalinkWpCliCommand }`; 75 | const permalinkWpEnvTestCommand = `npx wp-env run tests-cli ${ permalinkWpCliCommand }`; 76 | cy.exec( permalinkWpEnvCommand, { failOnNonZeroExit: true } ).then( 77 | ( result ) => { 78 | cy.request( '/wp-json/' ); 79 | } 80 | ); 81 | cy.exec( permalinkWpEnvTestCommand, { 82 | failOnNonZeroExit: true, 83 | } ).then( ( result ) => { 84 | cy.request( '/wp-json/' ); 85 | } ); 86 | } ); 87 | } 88 | ); 89 | 90 | Cypress.Commands.add( 'deletePages', () => { 91 | cy.visit( '/wp-admin/edit.php?post_type=page' ).wait( 1000 ); 92 | cy.get( 'body' ).then( ( $body ) => { 93 | // synchronously ask for the body's text 94 | // and do something based on whether it includes 95 | // another string 96 | if ( $body.text().includes( 'No pages found.' ) ) { 97 | // nothing needed 98 | } else { 99 | cy.get( '#cb-select-all-1' ).check(); 100 | cy.get( '#bulk-action-selector-top' ).select( 'trash' ); 101 | cy.get( '#doaction' ).click(); 102 | cy.wait( 2000 ); 103 | cy.get( '.subsubsub .trash' ).click(); 104 | cy.wait( 2000 ); 105 | cy.get( '#delete_all' ).click(); 106 | } 107 | } ); 108 | } ); 109 | -------------------------------------------------------------------------------- /tests/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | import 'cypress-axe'; 17 | import './commands'; 18 | 19 | const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/; 20 | Cypress.on( 'uncaught:exception', ( err ) => { 21 | /* returning false here prevents Cypress from failing the test */ 22 | if ( resizeObserverLoopErrRe.test( err.message ) ) { 23 | return false; 24 | } 25 | } ); 26 | 27 | // This needs to remain until all tests have been updated with testIsolation: 28 | // at that point this can be updated to a beforeEach and testIsolation can be 29 | // removed from the config as the default is true. 30 | before( () => { 31 | cy.wpLogin(); 32 | } ); 33 | -------------------------------------------------------------------------------- /tests/phpunit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( true, true ); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tests/phpunit/bootstrap.php: -------------------------------------------------------------------------------- 1 |