├── .nvmrc
├── .github
├── CODEOWNERS
├── workflows
│ ├── i18n-crowdin-upload.yml
│ ├── i18n-crowdin-download.yml
│ ├── auto-translate.yml
│ ├── newfold-prep-release.yml
│ ├── satis-update.yml
│ ├── brand-plugin-test.yml
│ ├── lint-check-php.yml
│ └── lint-check-spa.yml
└── dependabot.yml
├── src
├── Brands
│ ├── crazy-domains
│ │ ├── icon-empty.svg
│ │ ├── wp-admin.png
│ │ ├── full-service.png
│ │ ├── icon.svg
│ │ └── step-loader-logo.svg
│ ├── bluehost
│ │ ├── bluesky.png
│ │ ├── wp-admin.png
│ │ ├── step-interstitial.png
│ │ ├── dark.svg
│ │ ├── icon.svg
│ │ └── logo.svg
│ ├── wordpress
│ │ ├── wp-admin.png
│ │ ├── full-service.png
│ │ ├── icon.svg
│ │ ├── dark.svg
│ │ ├── step-loader-logo.svg
│ │ └── dark-other.svg
│ ├── newfold
│ │ └── icon.svg
│ ├── webcom
│ │ ├── icon.svg
│ │ └── dark.svg
│ └── hostgator
│ │ ├── dark.svg
│ │ └── icon.svg
├── app
│ ├── components
│ │ ├── Orb
│ │ │ ├── index.js
│ │ │ └── Orb.js
│ │ ├── Step
│ │ │ ├── index.js
│ │ │ └── Step.js
│ │ ├── AppBody
│ │ │ ├── index.js
│ │ │ └── AppBody.js
│ │ ├── Header
│ │ │ ├── index.js
│ │ │ └── Header.js
│ │ ├── Iframe
│ │ │ ├── index.js
│ │ │ └── Iframe.js
│ │ ├── Motion
│ │ │ ├── index.js
│ │ │ └── Motion.js
│ │ ├── Navigate
│ │ │ ├── index.js
│ │ │ └── Navigate.js
│ │ ├── ActionCard
│ │ │ ├── index.js
│ │ │ └── ActionCard.js
│ │ ├── BackButton
│ │ │ ├── index.js
│ │ │ └── BackButton.js
│ │ ├── BrandLoader
│ │ │ ├── index.js
│ │ │ ├── BrandLoader.scss
│ │ │ └── BrandLoader.js
│ │ ├── AnimateRoutes
│ │ │ ├── index.js
│ │ │ └── AnimateRoutes.js
│ │ ├── SiteGenPreviewCard
│ │ │ └── index.js
│ │ ├── ErrorBoundaryFallback
│ │ │ ├── index.js
│ │ │ ├── nfd-err-boundary.png
│ │ │ └── ErrorBoundaryFallback.js
│ │ ├── InteractionBlockingOverlay
│ │ │ ├── index.js
│ │ │ └── InteractionBlockingOverlay.js
│ │ └── index.js
│ ├── steps
│ │ ├── Fork
│ │ │ ├── index.js
│ │ │ ├── MigrationCard.js
│ │ │ └── SiteCreatorCard.js
│ │ ├── Migration
│ │ │ └── index.js
│ │ ├── Blueprints
│ │ │ └── index.js
│ │ ├── Previews
│ │ │ └── index.js
│ │ ├── Logo
│ │ │ ├── index.js
│ │ │ └── LogoStep.js
│ │ ├── Generating
│ │ │ └── index.js
│ │ ├── BlueprintCanvas
│ │ │ ├── index.js
│ │ │ ├── BlueprintCanvasStep.js
│ │ │ └── Preview.js
│ │ ├── Canvas
│ │ │ ├── index.js
│ │ │ └── CanvasStep.js
│ │ ├── Intake
│ │ │ ├── index.js
│ │ │ ├── SiteTitleInput.js
│ │ │ ├── calculatePromptStrength.js
│ │ │ ├── PromptInput.js
│ │ │ └── SiteTypeSelector.js
│ │ └── index.js
│ ├── utils
│ │ ├── blueprints
│ │ │ ├── index.js
│ │ │ └── fetchBlueprints.js
│ │ ├── api
│ │ │ ├── index.js
│ │ │ ├── migration.js
│ │ │ └── wp.js
│ │ ├── helpers
│ │ │ ├── index.js
│ │ │ ├── isCypress.js
│ │ │ └── resolve.js
│ │ ├── sitegen
│ │ │ ├── index.js
│ │ │ ├── generateSiteNavigationMenu.js
│ │ │ ├── generateSite.js
│ │ │ ├── generateSitePages.js
│ │ │ └── generateHomePages.js
│ │ ├── hooks
│ │ │ ├── index.js
│ │ │ ├── useAnimateRouteDirection.js
│ │ │ └── useGlobalStyles.js
│ │ └── analytics
│ │ │ └── hiive
│ │ │ ├── OnboardingEvent.js
│ │ │ └── index.js
│ ├── assets
│ │ ├── nfd-migration.png
│ │ ├── nfd-missing-resource.png
│ │ └── ai-sitegen-icon.svg
│ ├── data
│ │ └── store
│ │ │ ├── reducer.js
│ │ │ ├── actions.js
│ │ │ ├── selectors.js
│ │ │ └── index.js
│ ├── styles
│ │ ├── app.scss
│ │ ├── _wordpress.scss
│ │ ├── _utilities.scss
│ │ ├── _interface.scss
│ │ └── tailwind.css
│ └── index.js
├── DesignStudio
│ ├── utils
│ │ ├── constants.js
│ │ ├── text.js
│ │ ├── editor-utils.js
│ │ ├── url-utils.js
│ │ ├── simple-cache.js
│ │ ├── design-api.js
│ │ └── styles-utils.js
│ ├── onboarding-design-studio.js
│ ├── hooks
│ │ ├── useEditorControls.js
│ │ ├── useColorPaletteSelection.js
│ │ ├── useColorPalettePagination.js
│ │ ├── useColorSettings.js
│ │ ├── useTypographyUpdate.js
│ │ └── useLogoManagement.js
│ └── components
│ │ ├── ScreenHeader
│ │ ├── NavigationButton.jsx
│ │ └── index.jsx
│ │ ├── Typography
│ │ ├── FontPairings.jsx
│ │ └── PreviewItem.jsx
│ │ ├── Colors
│ │ ├── ColorPalettePagination.jsx
│ │ ├── ColorPaletteItem.jsx
│ │ └── ColorPalette.jsx
│ │ ├── Logo
│ │ ├── LogoUploader.jsx
│ │ └── LogoPreview.jsx
│ │ └── Screens
│ │ ├── ScreenRoot.jsx
│ │ ├── ScreenTypography.jsx
│ │ ├── ScreenColors.jsx
│ │ └── ScreenLogo.jsx
├── Scripts
│ ├── sitegen-theme-marker
│ │ ├── sitegen-theme-marker.css
│ │ └── sitegen-theme-marker.js
│ └── onboarding-restart-button
│ │ └── onboarding-restart-button.css
└── webpack-public-path.js
├── languages
├── wp-module-onboarding-de_DE.mo
├── wp-module-onboarding-en_AU.mo
├── wp-module-onboarding-en_GB.mo
├── wp-module-onboarding-es_ES.mo
├── wp-module-onboarding-es_MX.mo
├── wp-module-onboarding-fr_FR.mo
├── wp-module-onboarding-it_IT.mo
├── wp-module-onboarding-nl_NL.mo
└── wp-module-onboarding-pt_BR.mo
├── .gitignore
├── postcss.config.js
├── .gitattributes
├── jsconfig.json
├── includes
├── WP_CLI.php
├── Mustache
│ ├── Templates
│ │ └── themeStylesheet.mustache
│ └── Mustache.php
├── Services
│ ├── LanguageService.php
│ ├── I18nService.php
│ ├── SettingsService.php
│ ├── ReduxStateService.php
│ └── ThemeService.php
├── RestApi
│ ├── BaseHiiveController.php
│ ├── SiteClassificationController.php
│ ├── LanguagesController.php
│ ├── Themes
│ │ ├── ThemeInstallerController.php
│ │ ├── ThemeColorsController.php
│ │ └── ThemeFontsController.php
│ ├── GlobalStylesController.php
│ ├── PreviewsController.php
│ ├── RestApi.php
│ └── SiteImagesController.php
├── Permissions.php
├── WP_Config.php
├── Types
│ └── SiteClassification.php
├── Compatibility
│ ├── Status.php
│ └── Scan.php
├── Tasks
│ └── ImageSideloadTask.php
├── Models
│ └── Theme.php
├── Application.php
└── ExternalRedirectInterceptor.php
├── .editorconfig
├── phpcs.xml
├── .prettierrc.js
├── cypress.json
├── crowdin.yml
├── phpunit.xml
├── webpack.config.js
├── tailwind.config.js
└── .eslintrc.js
/.nvmrc:
--------------------------------------------------------------------------------
1 | v16
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/Brands/crazy-domains/icon-empty.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/Orb/index.js:
--------------------------------------------------------------------------------
1 | export { default as Orb } from './Orb';
2 |
--------------------------------------------------------------------------------
/src/app/components/Step/index.js:
--------------------------------------------------------------------------------
1 | export { default as Step } from './Step';
2 |
--------------------------------------------------------------------------------
/src/app/steps/Fork/index.js:
--------------------------------------------------------------------------------
1 | export { default as ForkStep } from './ForkStep';
2 |
--------------------------------------------------------------------------------
/src/app/components/AppBody/index.js:
--------------------------------------------------------------------------------
1 | export { default as AppBody } from './AppBody';
2 |
--------------------------------------------------------------------------------
/src/app/components/Header/index.js:
--------------------------------------------------------------------------------
1 | export { default as Header } from './Header';
2 |
--------------------------------------------------------------------------------
/src/app/components/Iframe/index.js:
--------------------------------------------------------------------------------
1 | export { default as Iframe } from './Iframe';
2 |
--------------------------------------------------------------------------------
/src/app/components/Motion/index.js:
--------------------------------------------------------------------------------
1 | export { default as Motion } from './Motion';
2 |
--------------------------------------------------------------------------------
/src/app/components/Navigate/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navigate } from './Navigate';
2 |
--------------------------------------------------------------------------------
/src/app/components/ActionCard/index.js:
--------------------------------------------------------------------------------
1 | export { default as ActionCard } from './ActionCard';
2 |
--------------------------------------------------------------------------------
/src/app/steps/Migration/index.js:
--------------------------------------------------------------------------------
1 | export { default as MigrationStep } from './MigrationStep';
2 |
--------------------------------------------------------------------------------
/src/app/components/BackButton/index.js:
--------------------------------------------------------------------------------
1 | export { default as BackButton } from './BackButton';
2 |
3 |
--------------------------------------------------------------------------------
/src/app/components/BrandLoader/index.js:
--------------------------------------------------------------------------------
1 | export { default as BrandLoader } from './BrandLoader';
2 |
--------------------------------------------------------------------------------
/src/app/steps/Blueprints/index.js:
--------------------------------------------------------------------------------
1 | export { default as BlueprintsStep } from './BlueprintsStep';
2 |
--------------------------------------------------------------------------------
/src/app/utils/blueprints/index.js:
--------------------------------------------------------------------------------
1 | export { default as fetchBlueprints } from './fetchBlueprints';
2 |
--------------------------------------------------------------------------------
/src/app/components/AnimateRoutes/index.js:
--------------------------------------------------------------------------------
1 | export { default as AnimateRoutes } from './AnimateRoutes';
2 |
--------------------------------------------------------------------------------
/src/app/components/SiteGenPreviewCard/index.js:
--------------------------------------------------------------------------------
1 | export { default as SiteGenPreviewCard } from './SiteGenPreviewCard';
2 |
--------------------------------------------------------------------------------
/src/app/utils/api/index.js:
--------------------------------------------------------------------------------
1 | export * from './migration';
2 | export * from './onboarding';
3 | export * from './wp';
4 |
--------------------------------------------------------------------------------
/src/Brands/bluehost/bluesky.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/Brands/bluehost/bluesky.png
--------------------------------------------------------------------------------
/src/Brands/bluehost/wp-admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/Brands/bluehost/wp-admin.png
--------------------------------------------------------------------------------
/src/app/assets/nfd-migration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/app/assets/nfd-migration.png
--------------------------------------------------------------------------------
/src/app/components/ErrorBoundaryFallback/index.js:
--------------------------------------------------------------------------------
1 | export { default as ErrorBoundaryFallback } from './ErrorBoundaryFallback';
2 |
--------------------------------------------------------------------------------
/src/Brands/wordpress/wp-admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/Brands/wordpress/wp-admin.png
--------------------------------------------------------------------------------
/src/app/utils/helpers/index.js:
--------------------------------------------------------------------------------
1 | export { default as isCypress } from './isCypress';
2 | export { default as resolve } from './resolve';
3 |
--------------------------------------------------------------------------------
/src/Brands/crazy-domains/wp-admin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/Brands/crazy-domains/wp-admin.png
--------------------------------------------------------------------------------
/src/Brands/wordpress/full-service.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/Brands/wordpress/full-service.png
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-de_DE.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-de_DE.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-en_AU.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-en_AU.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-en_GB.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-en_GB.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-es_ES.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-es_ES.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-es_MX.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-es_MX.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-fr_FR.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-fr_FR.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-it_IT.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-it_IT.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-nl_NL.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-nl_NL.mo
--------------------------------------------------------------------------------
/languages/wp-module-onboarding-pt_BR.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/languages/wp-module-onboarding-pt_BR.mo
--------------------------------------------------------------------------------
/src/app/assets/nfd-missing-resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/app/assets/nfd-missing-resource.png
--------------------------------------------------------------------------------
/src/app/components/InteractionBlockingOverlay/index.js:
--------------------------------------------------------------------------------
1 | export { default as InteractionBlockingOverlay } from './InteractionBlockingOverlay';
2 |
--------------------------------------------------------------------------------
/src/app/steps/Previews/index.js:
--------------------------------------------------------------------------------
1 | export { default as Preview } from './Preview';
2 | export { default as PreviewsStep } from './PreviewsStep';
3 |
--------------------------------------------------------------------------------
/src/Brands/bluehost/step-interstitial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/Brands/bluehost/step-interstitial.png
--------------------------------------------------------------------------------
/src/Brands/crazy-domains/full-service.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/Brands/crazy-domains/full-service.png
--------------------------------------------------------------------------------
/src/app/steps/Logo/index.js:
--------------------------------------------------------------------------------
1 | export { default as LogoStep } from './LogoStep';
2 | export { default as LogoUploadInput } from './LogoUploadInput';
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 | .env
4 | /node_modules
5 | /tests/_output
6 | /tests/_support/_generated
7 | /wordpress
8 | /wp-content
9 | node_modules
10 | vendor
--------------------------------------------------------------------------------
/src/app/steps/Generating/index.js:
--------------------------------------------------------------------------------
1 | export { default as GeneratingStep } from './GeneratingStep';
2 | export { default as ExperienceOptions } from './ExperienceOptions';
3 |
4 |
--------------------------------------------------------------------------------
/src/app/components/ErrorBoundaryFallback/nfd-err-boundary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/newfold-labs/wp-module-onboarding/HEAD/src/app/components/ErrorBoundaryFallback/nfd-err-boundary.png
--------------------------------------------------------------------------------
/src/app/components/BrandLoader/BrandLoader.scss:
--------------------------------------------------------------------------------
1 | .brand-loader {
2 | display: inline-block;
3 | background-size: contain;
4 | background-repeat: no-repeat;
5 | background-position: center;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/utils/helpers/isCypress.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Checks if the code is running in a Cypress test environment.
3 | */
4 | function isCypress() {
5 | return !! window.Cypress;
6 | }
7 |
8 | export default isCypress;
9 |
--------------------------------------------------------------------------------
/src/app/steps/BlueprintCanvas/index.js:
--------------------------------------------------------------------------------
1 | export { default as BlueprintCanvasStep } from './BlueprintCanvasStep';
2 | export { default as HeaderActions } from './HeaderActions';
3 | export { default as Preview } from './Preview';
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/app/steps/Canvas/index.js:
--------------------------------------------------------------------------------
1 | export { default as CanvasStep } from './CanvasStep';
2 | export { default as HeaderActions } from './HeaderActions';
3 | export { default as Preview } from './Preview';
4 | export { default as Sidebar } from './Sidebar';
5 |
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.env.testing export-ignore
2 | /.wp-env.json export-ignore
3 | /codeception.dist.yml export-ignore
4 | /package-lock.json export-ignore
5 | /package.json export-ignore
6 | /tests export-ignore
7 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "moduleResolution": "node",
5 | "module": "commonjs",
6 | "target": "es6",
7 | "paths": {
8 | "@/*": ["src/app/*"],
9 | }
10 | },
11 | "include": ["src/**/*"],
12 | "exclude": ["node_modules", "dist", "build"]
13 | }
--------------------------------------------------------------------------------
/includes/WP_CLI.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/DesignStudio/utils/constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Constants for query parameters
3 | */
4 | export const QUERY_PARAMS = {
5 | REFERRER: 'referrer',
6 | CANVAS_MODE: 'canvas',
7 | };
8 |
9 | /**
10 | * Valid referrer sources for onboarding integration
11 | */
12 | export const VALID_REFERRERS = [ 'nfd-onboarding', 'nfd-plugin' ];
13 |
--------------------------------------------------------------------------------
/src/app/steps/Intake/index.js:
--------------------------------------------------------------------------------
1 | export { default as IntakeStep } from './IntakeStep';
2 | export { default as SiteTitleInput } from './SiteTitleInput';
3 | export { default as PromptInput } from './PromptInput';
4 | export { default as calculatePromptStrength } from './calculatePromptStrength';
5 | export { default as SiteTypeSelector } from './SiteTypeSelector';
6 |
--------------------------------------------------------------------------------
/includes/Mustache/Templates/themeStylesheet.mustache:
--------------------------------------------------------------------------------
1 | /*
2 | Theme Name: {{ site_title }}
3 | Author: {{ site_title }} & {{ brand_name }}
4 | Author URI: {{ site_url }}
5 | Description: A Custom WordPress Theme built for {{ site_title }} by {{ brand_name }}
6 | Version: 1.0.0
7 | Template: {{ parent_theme_slug }}
8 | Text Domain: {{ child_theme_slug }}
9 | */
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | # WordPress Coding Standards
5 | # https://make.wordpress.org/core/handbook/coding-standards/
6 |
7 | root = true
8 |
9 | [*]
10 | charset = utf-8
11 | end_of_line = lf
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 | indent_style = tab
--------------------------------------------------------------------------------
/src/app/utils/sitegen/index.js:
--------------------------------------------------------------------------------
1 | export { default as generateSite } from './generateSite';
2 | export { default as generateSiteMeta } from './generateSiteMeta';
3 | export { default as generateHomePages } from './generateHomePages';
4 | export { default as generateSitePages } from './generateSitePages';
5 | export { default as generateSiteNavigationMenu } from './generateSiteNavigationMenu';
6 |
--------------------------------------------------------------------------------
/src/app/data/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from '@wordpress/data';
2 | import { runtime } from './slices/runtime';
3 | import { input } from './slices/input';
4 | import { sitegen } from './slices/sitegen';
5 | import { blueprints } from './slices/blueprints';
6 |
7 | export default combineReducers( {
8 | runtime,
9 | input,
10 | sitegen,
11 | blueprints,
12 | } );
13 |
--------------------------------------------------------------------------------
/src/app/utils/hooks/index.js:
--------------------------------------------------------------------------------
1 | export { default as useAnimateRouteDirection } from './useAnimateRouteDirection';
2 | export { default as useGlobalStyles } from './useGlobalStyles';
3 | export { default as useTemplateParts } from './useTemplateParts';
4 | export { default as usePublishSite } from './usePublishSite';
5 | export { default as usePublishBlueprintSite } from './usePublishBlueprintSite';
6 |
--------------------------------------------------------------------------------
/src/app/styles/app.scss:
--------------------------------------------------------------------------------
1 | @import "wordpress";
2 | @import "interface";
3 | /* @import "icons"; */
4 | /* @import "branding"; */
5 | @import "utilities";
6 |
7 | .nfd-onboarding-container {
8 | display: flex;
9 | background-color: var(--nfd-onboarding-canvas);
10 | position: fixed;
11 | bottom: 0;
12 | left: 0;
13 | right: 0;
14 | top: 0;
15 | overflow-y: auto;
16 | overflow-x: hidden;
17 | }
18 |
--------------------------------------------------------------------------------
/.github/workflows/i18n-crowdin-upload.yml:
--------------------------------------------------------------------------------
1 | name: Crowdin Upload Action
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | call-crowdin-upload-workflow:
8 | uses: newfold-labs/workflows/.github/workflows/i18n-crowdin-upload.yml@main
9 | with:
10 | CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
11 | secrets:
12 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
13 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Brands/bluehost/dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Brands/bluehost/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | const wordpressPrettierConfig = require( '@wordpress/prettier-config' );
2 |
3 | module.exports = {
4 | ...wordpressPrettierConfig,
5 | printWidth: 100, // Default print width for all files. Override if needed.
6 | singleQuote: true,
7 | tabWidth: 2,
8 | trailingComma: 'es5',
9 | overrides: [
10 | {
11 | files: '*.css',
12 | options: {
13 | printWidth: 160,
14 | },
15 | },
16 | ],
17 | };
18 |
--------------------------------------------------------------------------------
/src/app/assets/ai-sitegen-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/DesignStudio/utils/text.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Capitalizes the first letter of each word in a string
3 | *
4 | * @param {string} str String to capitalize
5 | * @return {string} Capitalized string
6 | */
7 | export const ucwords = ( str ) => {
8 | if ( ! str ) {
9 | return '';
10 | }
11 |
12 | return str
13 | .split( ' ' )
14 | .map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ).toLowerCase() )
15 | .join( ' ' );
16 | };
17 |
--------------------------------------------------------------------------------
/src/DesignStudio/onboarding-design-studio.js:
--------------------------------------------------------------------------------
1 | import domReady from '@wordpress/dom-ready';
2 | import { registerPlugin } from '@wordpress/plugins';
3 | import DesignStudio from './components/DesignStudio';
4 |
5 | // Register the plugin when DOM is ready
6 | domReady( () => {
7 | registerPlugin( 'nfd-design-studio', {
8 | render: DesignStudio,
9 | } );
10 | } );
11 |
12 | // Export components for potential reuse
13 | export { DesignStudio };
14 |
--------------------------------------------------------------------------------
/src/app/data/store/actions.js:
--------------------------------------------------------------------------------
1 | import { actions as runtimeActions } from './slices/runtime';
2 | import { actions as inputActions } from './slices/input';
3 | import { actions as sitegenActions } from './slices/sitegen';
4 | import { actions as blueprintsActions } from './slices/blueprints';
5 |
6 | export const actions = {
7 | ...runtimeActions,
8 | ...inputActions,
9 | ...sitegenActions,
10 | ...blueprintsActions,
11 | };
12 |
13 | export default actions;
14 |
--------------------------------------------------------------------------------
/src/app/utils/helpers/resolve.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Resolves a promise and returns the body and error.
3 | *
4 | * @param {Promise} promise
5 | * @return {Object} resolved
6 | */
7 | async function resolve( promise ) {
8 | const resolved = {
9 | body: null,
10 | error: null,
11 | };
12 |
13 | try {
14 | resolved.body = await promise;
15 | } catch ( error ) {
16 | resolved.error = error;
17 | }
18 |
19 | return resolved;
20 | }
21 |
22 | export default resolve;
23 |
--------------------------------------------------------------------------------
/src/app/data/store/selectors.js:
--------------------------------------------------------------------------------
1 | import { selectors as runtimeSelectors } from './slices/runtime';
2 | import { selectors as inputSelectors } from './slices/input';
3 | import { selectors as sitegenSelectors } from './slices/sitegen';
4 | import { selectors as blueprintsSelectors } from './slices/blueprints';
5 |
6 | const selectors = {
7 | ...runtimeSelectors,
8 | ...inputSelectors,
9 | ...sitegenSelectors,
10 | ...blueprintsSelectors,
11 | };
12 |
13 | export default selectors;
14 |
--------------------------------------------------------------------------------
/src/app/utils/sitegen/generateSiteNavigationMenu.js:
--------------------------------------------------------------------------------
1 | import { setupSiteNavigationMenu } from '@/utils/api';
2 |
3 | /**
4 | * Generate the site navigation menu.
5 | *
6 | * @return {boolean} True if successful, false otherwise.
7 | */
8 | const generateSiteNavigationMenu = async () => {
9 | const response = await setupSiteNavigationMenu();
10 | if ( response.error ) {
11 | return false;
12 | }
13 |
14 | return true;
15 | };
16 |
17 | export default generateSiteNavigationMenu;
18 |
--------------------------------------------------------------------------------
/src/app/styles/_wordpress.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Initialize all WordPress Sass
3 | * -----------------------------
4 | * @import @wordpress/base-styles
5 | * @import @wordpress/interface
6 | */
7 |
8 | @import '@wordpress/base-styles/breakpoints';
9 | @import '@wordpress/base-styles/colors';
10 | @import '@wordpress/base-styles/mixins';
11 | @import '@wordpress/base-styles/variables';
12 | @import '@wordpress/base-styles/z-index';
13 |
14 | @import '@wordpress/interface/src/style.scss';
15 |
--------------------------------------------------------------------------------
/src/Scripts/sitegen-theme-marker/sitegen-theme-marker.css:
--------------------------------------------------------------------------------
1 | .theme-name {
2 | display: flex;
3 | flex-direction: row;
4 | align-items: center;
5 |
6 | svg {
7 | margin-left: 8px;
8 | }
9 | }
10 |
11 | .nfd-onboarding-sitegen-theme-marker-filled {
12 | fill: #1d2327;
13 | }
14 |
15 | .theme.active {
16 |
17 | .nfd-onboarding-sitegen-theme-marker-filled {
18 | fill: none;
19 | }
20 | }
21 |
22 | .nfd-onboarding-sitegen-theme-marker-title {
23 | margin-left: 8px;
24 | width: 120px;
25 | text-overflow: ellipsis;
26 | overflow: hidden;
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/steps/Intake/SiteTitleInput.js:
--------------------------------------------------------------------------------
1 | import { TextField } from '@newfold/ui-component-library';
2 |
3 | const SiteTitleInput = ( { value, onChange } ) => {
4 | return (
5 |
6 | onChange( e.target.value ) }
11 | value={ value }
12 | />
13 |
14 | );
15 | };
16 |
17 | export default SiteTitleInput;
18 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:8880",
3 | "env": {
4 | "wpUsername": "admin",
5 | "wpPassword": "password"
6 | },
7 | "fixturesFolder": "tests/cypress/fixtures",
8 | "integrationFolder": "tests/cypress/integration",
9 | "pluginsFile": "tests/cypress/plugins/index.js",
10 | "screenshotsFolder": "tests/cypress/screenshots",
11 | "supportFile": "tests/cypress/support/index.js",
12 | "video": false,
13 | "videosFolder": "tests/cypress/videos",
14 | "videoUploadOnPasses": false,
15 | "chromeWebSecurity": false
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/styles/_utilities.scss:
--------------------------------------------------------------------------------
1 | .is-centered {
2 | display: flex;
3 | place-content: center;
4 | place-items: center;
5 | }
6 |
7 | .is-vertically-centered {
8 | display: flex;
9 | align-items: center;
10 | justify-content: space-evenly;
11 | flex-direction: column;
12 | }
13 |
14 | .is-bg-primary {
15 | background-color: var(--nfd-onboarding-primary);
16 | color: var(--nfd-onboarding-base);
17 | }
18 |
19 | .center {
20 | text-align: center;
21 | }
22 |
23 | input::placeholder {
24 | font-size: 0.75rem;
25 | color: var(--nfd-onboarding-light-gray-4);
26 | }
27 |
--------------------------------------------------------------------------------
/src/webpack-public-path.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Set webpack's public path (default is root directory of URI resource) to Plugin's build directory.
3 | * This helps lazy-loading work correctly. This value is set in `/includes/Data.php` in Data::runtime().
4 | */
5 | import { runtimeDataObjectIsMounted } from './onboarding';
6 |
7 | const webpackPublicPath = () => {
8 | if ( runtimeDataObjectIsMounted() ) {
9 | // eslint-disable-next-line camelcase, no-undef
10 | __webpack_public_path__ = window.nfdOnboarding.runtime.buildUrl;
11 | }
12 | };
13 |
14 | export default webpackPublicPath;
15 |
--------------------------------------------------------------------------------
/src/app/utils/api/migration.js:
--------------------------------------------------------------------------------
1 | import apiFetch from '@wordpress/api-fetch';
2 | import { wpRestURL } from '@/data/constants';
3 | import { resolve } from '@/utils/helpers';
4 |
5 | export const migrationRestRoute = 'newfold-migration/v1';
6 | export const migrateRestBase = `${ wpRestURL }/${ migrationRestRoute }`;
7 |
8 | export const migrateRestURL = ( api ) => {
9 | return `${ migrateRestBase }/${ api }`;
10 | };
11 |
12 | export async function getSiteMigrateUrl() {
13 | return await resolve(
14 | apiFetch( {
15 | url: migrateRestURL( 'migrate/connect' ),
16 | } ).then()
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | "project_id_env": "CROWDIN_PROJECT_ID"
2 | "api_token_env": "CROWDIN_PERSONAL_TOKEN"
3 | "base_path": "."
4 |
5 | "preserve_hierarchy": true
6 |
7 | "files": [
8 | {
9 | "source": "/languages/wp-module-onboarding.pot",
10 | "translation": "/languages/wp-module-onboarding-%locale%.po",
11 | "languages_mapping": {
12 | "locale": {
13 | "de": "de_DE",
14 | "en-AU": "en_AU",
15 | "en-GB": "en_GB",
16 | "es-ES": "es_ES",
17 | "fr": "fr_FR",
18 | "it": "it_IT",
19 | "nl": "nl_NL"
20 | }
21 | }
22 | }
23 | ]
--------------------------------------------------------------------------------
/src/app/utils/analytics/hiive/OnboardingEvent.js:
--------------------------------------------------------------------------------
1 | import { HiiveEvent } from '@newfold/js-utility-ui-analytics';
2 | import { getLabelKeyFromAction } from '.';
3 | import { CATEGORY } from './constants';
4 |
5 | export class OnboardingEvent extends HiiveEvent {
6 | constructor( action, value, additionalData, page, category = CATEGORY ) {
7 | const labelKey = getLabelKeyFromAction( action );
8 | super(
9 | category,
10 | action,
11 | {
12 | label_key: labelKey,
13 | [ labelKey ]: value,
14 | ...additionalData,
15 | page: page ? page : window.location.href,
16 | },
17 | category
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/components/AnimateRoutes/AnimateRoutes.js:
--------------------------------------------------------------------------------
1 | import { AnimatePresence } from 'motion/react';
2 | import { useAnimateRouteDirection } from '@/utils/hooks';
3 |
4 | /**
5 | * Wraps children components with animation context and handles route transition animations.
6 | * @param {ReactNode} children
7 | * @return {JSX.Element} Animated route wrapper component
8 | */
9 | const AnimateRoutes = ( { children } ) => {
10 | return (
11 |
16 | { children }
17 |
18 | );
19 | };
20 |
21 | export default AnimateRoutes;
22 |
--------------------------------------------------------------------------------
/.github/workflows/i18n-crowdin-download.yml:
--------------------------------------------------------------------------------
1 | name: Crowdin Download Action
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | base_branch:
7 | description: 'Base branch for the pull request'
8 | required: false
9 | default: 'main'
10 |
11 | permissions:
12 | contents: write
13 | pull-requests: write
14 |
15 | jobs:
16 | call-crowdin-workflow:
17 | uses: newfold-labs/workflows/.github/workflows/i18n-crowdin-download.yml@main
18 | with:
19 | base_branch: ${{ inputs.base_branch }}
20 | CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
21 | secrets:
22 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
23 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | tests/phpunit
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/includes/Services/LanguageService.php:
--------------------------------------------------------------------------------
1 | {
4 | // Generate site meta
5 | const siteMeta = await generateSiteMeta();
6 | if ( ! siteMeta ) {
7 | return false;
8 | }
9 |
10 | // Generate home pages
11 | const homePages = await generateHomePages();
12 | if ( ! homePages ) {
13 | return false;
14 | }
15 |
16 | // Generate the rest of the site pages.
17 | await generateSitePages();
18 |
19 | // Generate the site navigation menu.
20 | await generateSiteNavigationMenu();
21 |
22 | return true;
23 | };
24 |
25 | export default generateSite;
26 |
--------------------------------------------------------------------------------
/src/app/components/Motion/Motion.js:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react';
2 | import { motion } from 'motion/react';
3 |
4 | /**
5 | * A wrapper component for the Framer Motion library.
6 | * @param {Object} props Component props.
7 | * @param {string} props.tag HTML tag name.
8 | * @param {React.ReactNode} props.children Component children.
9 | * @param {React.Ref} ref Component ref.
10 | * @return {React.ReactNode} Motion component.
11 | */
12 | const Motion = forwardRef( ( {
13 | tag = 'div',
14 | children,
15 | ...props
16 | }, ref ) => {
17 | const Component = motion[ tag ];
18 |
19 | return (
20 |
24 | { children }
25 |
26 | );
27 | } );
28 |
29 | export default Motion;
30 |
31 |
--------------------------------------------------------------------------------
/src/app/utils/api/wp.js:
--------------------------------------------------------------------------------
1 | import apiFetch from '@wordpress/api-fetch';
2 | import { wpRestURL as wpRestApiURL, wpSiteUrl } from '@/data/constants';
3 | import { resolve } from '@/utils/helpers';
4 |
5 | export const wpRestRoute = 'wp/v2';
6 | export const wpRestBase = `${ wpRestApiURL }/${ wpRestRoute }`;
7 |
8 | export const wpRestURL = ( api ) => {
9 | return `${ wpRestBase }/${ api }`;
10 | };
11 |
12 | export async function getWpSettings() {
13 | return await resolve(
14 | apiFetch( {
15 | url: wpRestURL( 'settings' ),
16 | } ).then()
17 | );
18 | }
19 |
20 | export const fireWpCron = () => {
21 | apiFetch( {
22 | url: `${ wpSiteUrl }/wp-cron.php`,
23 | method: 'GET',
24 | parse: false,
25 | } ).catch( ( error ) => {
26 | // eslint-disable-next-line no-console
27 | console.error( error );
28 | } );
29 | };
30 |
--------------------------------------------------------------------------------
/.github/workflows/auto-translate.yml:
--------------------------------------------------------------------------------
1 | name: Check for Updates to Translations
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'trunk'
7 | workflow_dispatch:
8 |
9 | # Cancels all previous workflow runs for the branch that have not completed.
10 | concurrency:
11 | # The concurrency group contains the workflow name and the branch name.
12 | group: ${{ github.workflow }}-${{ github.ref_name }}
13 | cancel-in-progress: true
14 |
15 | permissions: {}
16 |
17 | jobs:
18 | translate:
19 | name: 'Check and update translations'
20 | permissions:
21 | contents: write
22 | pull-requests: write
23 | uses: newfold-labs/workflows/.github/workflows/reusable-translations.yml@main
24 | with:
25 | text_domain: 'wp-module-onboarding'
26 | secrets:
27 | TRANSLATOR_API_KEY: ${{ secrets.TRANSLATOR_API_KEY }}
28 |
--------------------------------------------------------------------------------
/src/Brands/webcom/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Brands/webcom/dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/includes/Mustache/Mustache.php:
--------------------------------------------------------------------------------
1 | mustache_engine = new \Mustache_Engine(
21 | array(
22 | 'loader' => new \Mustache_Loader_FilesystemLoader( __DIR__ . '/Templates' ),
23 | )
24 | );
25 | }
26 |
27 | /**
28 | * Render respective template data.
29 | *
30 | * @param string $template_name Template Name
31 | * @param array $data Data
32 | * @return string
33 | */
34 | public function render_template( $template_name, $data ) {
35 | return $this->mustache_engine->loadTemplate( $template_name )->render( $data );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/DesignStudio/hooks/useEditorControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { useDispatch, useSelect } from '@wordpress/data';
5 | import { store as editorStore } from '@wordpress/editor';
6 |
7 | /**
8 | * Custom hook to manage editor rendering mode
9 | * @return {Object} Editor control functions and state
10 | */
11 | export const useEditorControls = () => {
12 | const { setRenderingMode } = useDispatch( editorStore );
13 |
14 | const renderingMode = useSelect( ( select ) => {
15 | const editor = select( editorStore );
16 | return editor?.getRenderingMode ? editor.getRenderingMode() : null;
17 | }, [] );
18 |
19 | return {
20 | setShowTemplate: () => {
21 | if ( setRenderingMode && renderingMode !== 'template-locked' ) {
22 | setRenderingMode( 'template-locked' );
23 | return true;
24 | }
25 | return false;
26 | },
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/src/app/components/index.js:
--------------------------------------------------------------------------------
1 | export { ActionCard as ActionCard } from './ActionCard';
2 | export { AnimateRoutes as AnimateRoutes } from './AnimateRoutes';
3 | export { AppBody as AppBody } from './AppBody';
4 | export { BackButton as BackButton } from './BackButton';
5 | export { BrandLoader as BrandLoader } from './BrandLoader';
6 | export { ErrorBoundaryFallback as ErrorBoundaryFallback } from './ErrorBoundaryFallback';
7 | export { Header as Header } from './Header';
8 | export { Iframe as Iframe } from './Iframe';
9 | export { InteractionBlockingOverlay as InteractionBlockingOverlay } from './InteractionBlockingOverlay';
10 | export { Motion as Motion } from './Motion';
11 | export { Navigate as Navigate } from './Navigate';
12 | export { Orb as Orb } from './Orb';
13 | export { SiteGenPreviewCard as SiteGenPreviewCard } from './SiteGenPreviewCard';
14 | export { Step as Step } from './Step';
15 |
--------------------------------------------------------------------------------
/src/Brands/wordpress/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Brands/wordpress/dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/DesignStudio/utils/editor-utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Click the zoom out button
3 | */
4 | export const enableZoomOut = () => {
5 | // Find and click the zoom out button
6 | const zoomButtonSelectors = [
7 | 'button[aria-label="Zoom out"]',
8 | 'button[aria-label="Zoom Out"]',
9 | '.block-editor-zoom-out-button',
10 | '.edit-site-visual-editor__zoom-dropdown button',
11 | ];
12 |
13 | let zoomButton = null;
14 | for ( const selector of zoomButtonSelectors ) {
15 | zoomButton = document.querySelector( selector );
16 | if ( zoomButton ) {
17 | break;
18 | }
19 | }
20 |
21 | if ( zoomButton ) {
22 | zoomButton.click();
23 | return true;
24 | }
25 |
26 | // Retry after a delay
27 | setTimeout( () => {
28 | const retryZoomButton = document.querySelector( zoomButtonSelectors.join( ', ' ) );
29 | if ( retryZoomButton ) {
30 | retryZoomButton.click();
31 | }
32 | }, 1500 );
33 |
34 | return false;
35 | };
36 |
--------------------------------------------------------------------------------
/src/app/components/InteractionBlockingOverlay/InteractionBlockingOverlay.js:
--------------------------------------------------------------------------------
1 | import { BrandLoader } from '@/components';
2 |
3 | const InteractionBlockingOverlay = ( {
4 | hasLoadingSpinner = false,
5 | hasBackground = true,
6 | children,
7 | } ) => {
8 | return (
9 |
10 | { hasLoadingSpinner && (
11 |
12 |
13 |
14 | ) }
15 | { hasBackground && (
16 |
17 | ) }
18 | { children }
19 |
20 | );
21 | };
22 |
23 | export default InteractionBlockingOverlay;
24 |
--------------------------------------------------------------------------------
/src/app/utils/analytics/hiive/index.js:
--------------------------------------------------------------------------------
1 | import { HiiveAnalytics } from '@newfold/js-utility-ui-analytics';
2 | import { default as wpData } from '@wordpress/data';
3 | import { default as wpApiFetch } from '@wordpress/api-fetch';
4 | import { ACTION_TO_LABEL_KEY_MAP } from './constants';
5 |
6 | export const trackOnboardingEvent = ( onboardingEvent ) => {
7 | return HiiveAnalytics.track( onboardingEvent );
8 | };
9 |
10 | export const sendOnboardingEvent = ( onboardingEvent ) => {
11 | return HiiveAnalytics.send( onboardingEvent );
12 | };
13 |
14 | export const getLabelKeyFromAction = ( action ) => {
15 | if ( action in ACTION_TO_LABEL_KEY_MAP ) {
16 | return ACTION_TO_LABEL_KEY_MAP[ action ];
17 | }
18 | return undefined;
19 | };
20 |
21 | // passing dependencies to HiiveAnalytics solely for build purposes
22 | HiiveAnalytics.dependencies = {
23 | wpData,
24 | wpApiFetch,
25 | };
26 |
27 | export * from './OnboardingEvent';
28 |
--------------------------------------------------------------------------------
/src/app/data/store/index.js:
--------------------------------------------------------------------------------
1 | import { createReduxStore, register } from '@wordpress/data';
2 | import actions from './actions';
3 | import selectors from './selectors';
4 | import reducer from './reducer';
5 | import { dbSyncService as inputSliceDbSyncService } from './slices/input';
6 | import { dbSyncService as sitegenSliceDbSyncService } from './slices/sitegen';
7 | import { dbSyncService as blueprintsSliceDbSyncService } from './slices/blueprints';
8 |
9 | const STORE_NAME = 'newfold/onboarding';
10 | const STORE_CONFIG = {
11 | reducer,
12 | actions,
13 | selectors,
14 | };
15 |
16 | export const nfdOnboardingStore = createReduxStore( STORE_NAME, STORE_CONFIG );
17 | register( nfdOnboardingStore );
18 |
19 | /**
20 | * Initialize the store-db sync services.
21 | */
22 | export function initializeStoreDbSyncServices() {
23 | inputSliceDbSyncService();
24 | sitegenSliceDbSyncService();
25 | blueprintsSliceDbSyncService();
26 | }
27 |
--------------------------------------------------------------------------------
/src/DesignStudio/utils/url-utils.js:
--------------------------------------------------------------------------------
1 | import { QUERY_PARAMS, VALID_REFERRERS } from './constants';
2 |
3 | /**
4 | * Check if we should apply our customizations
5 | * @return {boolean} Whether to apply customizations
6 | */
7 | export const shouldApplyCustomizations = () => {
8 | const urlParams = new URLSearchParams( window.location.search );
9 | const referrer = urlParams.get( QUERY_PARAMS.REFERRER );
10 | return VALID_REFERRERS.includes( referrer );
11 | };
12 |
13 | /**
14 | * Set canvas mode to edit via URL parameter
15 | */
16 | export const setDefaultCanvasMode = () => {
17 | const urlParams = new URLSearchParams( window.location.search );
18 | if ( ! urlParams.has( QUERY_PARAMS.CANVAS_MODE ) ) {
19 | urlParams.set( QUERY_PARAMS.CANVAS_MODE, 'edit' );
20 | const newUrl = `${ window.location.pathname }?${ urlParams.toString() }${
21 | window.location.hash
22 | }`;
23 | window.history.replaceState( {}, '', newUrl );
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/ScreenHeader/NavigationButton.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import {
6 | FlexItem,
7 | __experimentalHStack as HStack,
8 | Icon,
9 | __experimentalItem as Item,
10 | Navigator,
11 | } from '@wordpress/components';
12 |
13 | function GenericNavigationButton( { icon, children, ...props } ) {
14 | return (
15 | -
16 | { icon && (
17 |
18 |
19 | { children }
20 |
21 | ) }
22 | { ! icon && children }
23 |
24 | );
25 | }
26 |
27 | function NavigationButton( props ) {
28 | return ;
29 | }
30 |
31 | function NavigationBackButton( props ) {
32 | return ;
33 | }
34 |
35 | export { NavigationBackButton, NavigationButton };
36 |
--------------------------------------------------------------------------------
/src/Brands/crazy-domains/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/Iframe/Iframe.js:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { forwardRef } from '@wordpress/element';
3 |
4 | const Iframe = forwardRef( ( {
5 | title,
6 | src,
7 | width = 400, // eslint-disable-line no-unused-vars
8 | height = 400,
9 | viewportScale = 1,
10 | viewportWidth = 1440,
11 | viewportHeight = 1000,
12 | className,
13 | ...props
14 | }, ref ) => {
15 | return (
16 |
26 |
40 |
41 | );
42 | } );
43 |
44 | export default Iframe;
45 |
--------------------------------------------------------------------------------
/.github/workflows/newfold-prep-release.yml:
--------------------------------------------------------------------------------
1 | name: Newfold Prepare Release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | level:
7 | description: 'The level of release to be used.'
8 | type: choice
9 | options:
10 | - 'patch'
11 | - 'minor'
12 | - 'major'
13 | default: 'patch'
14 | required: true
15 | # Disable permissions for all available scopes by default.
16 | # Any needed permissions should be configured at the job level.
17 | permissions: {}
18 |
19 | jobs:
20 |
21 | # This job runs the newfold module-prep-release workflow for this module.
22 | prep-release:
23 | name: Prepare Release
24 | permissions:
25 | contents: write
26 | pull-requests: write
27 | uses: newfold-labs/workflows/.github/workflows/reusable-module-prep-release.yml@main
28 | with:
29 | module-repo: ${{ github.repository }}
30 | module-branch: 'trunk'
31 | level: ${{ inputs.level }}
32 | json-file: 'package.json'
33 | php-file: 'bootstrap.php'
34 |
--------------------------------------------------------------------------------
/src/app/steps/BlueprintCanvas/BlueprintCanvasStep.js:
--------------------------------------------------------------------------------
1 | import { Step } from '@/components';
2 | import { Preview } from './';
3 |
4 | const BlueprintCanvasStep = () => {
5 | const [ canvasHeight, setCanvasHeight ] = useState( '100dvh' );
6 |
7 | const getCustomStyles = () => {
8 | return (
9 | `
10 | .nfd-onboarding-body {
11 | padding-top: 0 !important;
12 | padding-bottom: 0 !important;
13 | }
14 | `
15 | );
16 | };
17 |
18 | // On mount: calculate the appropriate canvas height.
19 | useEffect( () => {
20 | const appHeaderHeight = document.querySelector( '.nfd-onboarding-header' )?.offsetHeight + 1 || 0;
21 | setCanvasHeight( `calc(100dvh - ${ appHeaderHeight }px)` );
22 | }, [] );
23 |
24 | return (
25 |
26 |
27 |
35 |
36 | );
37 | };
38 |
39 | export default BlueprintCanvasStep;
40 |
--------------------------------------------------------------------------------
/src/app/utils/sitegen/generateSitePages.js:
--------------------------------------------------------------------------------
1 | import { dispatch, select } from '@wordpress/data';
2 | import { nfdOnboardingStore } from '@/data/store';
3 | import { getSitePages } from '@/utils/api';
4 | import { OnboardingEvent, trackOnboardingEvent } from '@/utils/analytics/hiive';
5 | import { ACTION_SITE_PAGES_GENERATION_FAILED } from '@/utils/analytics/hiive/constants';
6 |
7 | const generateSitePages = async () => {
8 | const prompt = select( nfdOnboardingStore ).getPrompt();
9 | const siteType = select( nfdOnboardingStore ).getSiteType();
10 |
11 | const response = await getSitePages( prompt, siteType );
12 | if ( response.error ) {
13 | trackOnboardingEvent(
14 | new OnboardingEvent( ACTION_SITE_PAGES_GENERATION_FAILED )
15 | );
16 |
17 | // eslint-disable-next-line no-console
18 | console.error( 'Failed to generate site pages' );
19 |
20 | return false;
21 | }
22 |
23 | // Flag that the site pages have been generated
24 | dispatch( nfdOnboardingStore ).setHasGeneratedSitePages( true );
25 |
26 | return true;
27 | };
28 |
29 | export default generateSitePages;
30 |
--------------------------------------------------------------------------------
/.github/workflows/satis-update.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@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0
26 | with:
27 | token: ${{ secrets.WEBHOOK_TOKEN }}
28 | repository: newfold-labs/satis
29 | event-type: '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 |
--------------------------------------------------------------------------------
/src/app/steps/Intake/calculatePromptStrength.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Calculate the strength of the prompt.
3 | *
4 | * @param {string} prompt - The prompt text to evaluate
5 | * @return {Object} The strength of the prompt.
6 | * {number} score - Numeric score from 0-3 representing prompt strength
7 | * {string} strength - Text representation of strength: 'VERY_WEAK', 'WEAK', 'MEDIUM', or 'HIGH'
8 | */
9 | const calculatePromptStrength = ( prompt ) => {
10 | const result = {
11 | score: 0,
12 | strength: 'VERY_WEAK',
13 | };
14 |
15 | if ( ! prompt || typeof prompt !== 'string' ) {
16 | return result;
17 | }
18 |
19 | const promptCharCount = prompt.length;
20 | if ( promptCharCount > 200 ) {
21 | // Greater than 200 characters
22 | result.score = 3;
23 | result.strength = 'HIGH';
24 | } else if ( promptCharCount > 150 ) {
25 | // Greater than 150 characters
26 | result.score = 2;
27 | result.strength = 'MEDIUM';
28 | } else if ( promptCharCount > 100 ) {
29 | // Greater than 100 characters
30 | result.score = 1;
31 | result.strength = 'WEAK';
32 | }
33 |
34 | // Less than 100 characters
35 | return result;
36 | };
37 |
38 | export default calculatePromptStrength;
39 |
--------------------------------------------------------------------------------
/src/app/components/BackButton/BackButton.js:
--------------------------------------------------------------------------------
1 | import { ArrowLeftIcon } from '@heroicons/react/24/outline';
2 | import Navigate from '../Navigate/Navigate';
3 |
4 | /**
5 | * BackButton component for header navigation.
6 | *
7 | * @param {Object} props - Component props
8 | * @param {string} props.toRoute - The route to navigate back to
9 | * @param {boolean} props.disabled - Whether the button is disabled
10 | * @return {JSX.Element} A back button with icon and optional text
11 | */
12 | const BackButton = ( { toRoute = '/', disabled = false } ) => {
13 | return (
14 |
21 |
22 |
23 | { __( 'Back', 'wp-module-onboarding' ) }
24 |
25 |
26 | );
27 | };
28 |
29 | export default BackButton;
30 |
31 |
--------------------------------------------------------------------------------
/src/app/steps/Intake/PromptInput.js:
--------------------------------------------------------------------------------
1 | import { Label, TextareaField } from '@newfold/ui-component-library';
2 |
3 | const PromptInput = ( { value, onChange } ) => {
4 | const inputPlaceholder = __(
5 | 'Ex: My business is called the “Bean there Café”. We offer a cozy, sustainable coffee shop in Asheville, North Carolina, focused on fair-trade coffee and local pastries. The site should feature a menu, a listing of upcoming events, and a blog on coffee culture.',
6 | 'wp-module-onboarding'
7 | );
8 |
9 | return (
10 |
11 |
17 |
18 | { __( 'What type of site will this be? Who is this site for?', 'wp-module-onboarding' ) }
19 |
20 | onChange( e.target.value ) }
26 | required={ true }
27 | />
28 |
29 | );
30 | };
31 |
32 | export default PromptInput;
33 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Typography/FontPairings.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import { Spinner, __experimentalVStack as VStack } from '@wordpress/components';
6 |
7 | /**
8 | * Internal dependencies
9 | */
10 | import { useFontPairings } from '../../hooks/useFontPairings';
11 | import { ucwords } from '../../utils/text';
12 | import TypographyPreviewItem from './PreviewItem';
13 |
14 | export default function FontPairings( { selectedStyle, onSelectStyle } ) {
15 | const { fontPairings, isLoading, error } = useFontPairings();
16 |
17 | if ( isLoading ) {
18 | return ;
19 | }
20 |
21 | if ( error ) {
22 | return Error loading font pairings: { error }
;
23 | }
24 |
25 | return (
26 |
27 | { fontPairings.map( ( style ) => (
28 | onSelectStyle( style ) }
34 | isSelected={ selectedStyle === style.id }
35 | />
36 | ) ) }
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Colors/ColorPalettePagination.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import { Button, __experimentalHStack as HStack } from '@wordpress/components';
6 | import { chevronLeft, chevronRight } from '@wordpress/icons';
7 |
8 | export default function ColorPalettePagination( {
9 | currentPage,
10 | totalPages,
11 | onPageChange,
12 | isLoading,
13 | } ) {
14 | if ( totalPages <= 1 ) {
15 | return null;
16 | }
17 |
18 | return (
19 |
20 |
21 | onPageChange( currentPage - 1 ) }
24 | disabled={ currentPage === 1 || isLoading }
25 | className="nfd-design-studio-color-palette-nav-button"
26 | />
27 |
28 | { currentPage } / { totalPages }
29 |
30 | onPageChange( currentPage + 1 ) }
33 | disabled={ currentPage === totalPages || isLoading }
34 | className="nfd-design-studio-color-palette-nav-button"
35 | />
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/ScreenHeader/index.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import {
6 | __experimentalHeading as Heading,
7 | __experimentalHStack as HStack,
8 | Navigator,
9 | __experimentalSpacer as Spacer,
10 | __experimentalView as View,
11 | __experimentalVStack as VStack,
12 | } from '@wordpress/components';
13 | import { __, isRTL } from '@wordpress/i18n';
14 | import { chevronLeft, chevronRight } from '@wordpress/icons';
15 |
16 | function ScreenHeader( { title, description, onBack } ) {
17 | return (
18 |
19 |
20 |
21 |
22 |
28 |
29 |
30 | { title }
31 |
32 |
33 |
34 |
35 |
36 | { description && { description }
}
37 |
38 | );
39 | }
40 |
41 | export default ScreenHeader;
42 |
--------------------------------------------------------------------------------
/src/app/styles/_interface.scss:
--------------------------------------------------------------------------------
1 | $header-height: 60px;
2 | $nav-sidebar-width: 300px;
3 |
4 | html.wp-toolbar {
5 | background: var(--nfd-onboarding-white);
6 | }
7 |
8 | body {
9 | &.is-fullscreen-mode {
10 | #wpbody {
11 | padding-top: 0;
12 | }
13 |
14 | #wpadminbar {
15 | display: none;
16 | }
17 | }
18 |
19 | &.dashboard_page_nfd-onboarding {
20 | overflow: hidden;
21 | @include wp-admin-reset(".nfd-onboarding-container");
22 | }
23 |
24 | // Common Components across Brands
25 | --nfd-onboarding-canvas: #F7F9FC;
26 | --nfd-onboarding-white: #fff;
27 | --nfd-onboarding-white-rgb: 255, 255, 255;
28 | --nfd-onboarding-black: #000;
29 | --nfd-onboarding-black-rgb: 0, 0, 0;
30 | --nfd-onboarding-header-base: var(--nfd-onboarding-light);
31 | --nfd-onboarding-header-contrast: var(--nfd-onboarding-dark);
32 | --nfd-onboarding-content-base: var(--nfd-onboarding-light);
33 | --nfd-onboarding-content-contrast: var(--nfd-onboarding-dark);
34 | --nfd-onboarding-drawer-base: var(--nfd-onboarding-dark);
35 | --nfd-onboarding-drawer-contrast: var(--nfd-onboarding-light);
36 | --nfd-onboarding-footer-contrast: var(--nfd-onboarding-dark);
37 | --nfd-onboarding-footer-base: var(--nfd-onboarding-light);
38 | --nfd-onboarding-minipreview-base: #f2f2f2;
39 | --nfd-onboarding-base-gray: #f0f0f0;
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/utils/sitegen/generateHomePages.js:
--------------------------------------------------------------------------------
1 | import { nfdOnboardingStore } from '@/data/store';
2 | import { dispatch, select } from '@wordpress/data';
3 | import { getHomepages } from '@/utils/api/onboarding';
4 | import { OnboardingEvent, trackOnboardingEvent } from '@/utils/analytics/hiive';
5 | import { ACTION_ERROR_STATE_TRIGGERED } from '@/utils/analytics/hiive/constants';
6 |
7 | /**
8 | * Generate the home pages for the site.
9 | *
10 | * @return {boolean} True if successful, false otherwise.
11 | */
12 | const generateHomePages = async () => {
13 | const prompt = select( nfdOnboardingStore ).getPrompt();
14 | const siteType = select( nfdOnboardingStore ).getSiteType();
15 |
16 | const response = await getHomepages( prompt, siteType );
17 | if ( response.error ) {
18 | trackOnboardingEvent(
19 | new OnboardingEvent(
20 | ACTION_ERROR_STATE_TRIGGERED,
21 | 'homepages',
22 | {
23 | source: 'quickstart',
24 | }
25 | )
26 | );
27 |
28 | // eslint-disable-next-line no-console
29 | console.error( 'Failed to generate home pages' );
30 |
31 | return false;
32 | }
33 | const homepages = response.body;
34 |
35 | // Set the homepages in the store
36 | dispatch( nfdOnboardingStore ).setHomepages( homepages );
37 |
38 | return true;
39 | };
40 |
41 | export default generateHomePages;
42 |
--------------------------------------------------------------------------------
/src/app/index.js:
--------------------------------------------------------------------------------
1 | import { HashRouter as Router } from 'react-router-dom';
2 | import { dispatch } from '@wordpress/data';
3 | import { FullscreenMode } from '@wordpress/interface';
4 | import { Root, ErrorBoundary as RootErrorBoundary } from '@newfold/ui-component-library';
5 | import { AppBody, ErrorBoundaryFallback, Header } from '@/components';
6 | import { nfdOnboardingStore, initializeStoreDbSyncServices } from '@/data/store';
7 | import '@/styles/tailwind.css';
8 | import '@/styles/app.scss';
9 |
10 | const App = () => {
11 | // Feed store data.
12 | dispatch( nfdOnboardingStore ).setRuntimeSlice( window.nfdOnboarding.runtime );
13 | dispatch( nfdOnboardingStore ).setInputSlice( window.nfdOnboarding.input );
14 | dispatch( nfdOnboardingStore ).setSiteGenSlice( window.nfdOnboarding.sitegen );
15 | dispatch( nfdOnboardingStore ).setBlueprintsSlice( window.nfdOnboarding.blueprints );
16 |
17 | // Initialize the store-DB sync services.
18 | initializeStoreDbSyncServices();
19 |
20 | return (
21 | <>
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | >
32 | );
33 | };
34 |
35 | export default App;
36 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 |
4 | # Maintain dependencies for GitHub Actions
5 | - package-ecosystem: "github-actions"
6 | directory: "/"
7 | target-branch: "trunk"
8 | allow:
9 | - dependency-type: direct
10 | schedule:
11 | interval: "daily"
12 | commit-message:
13 | prefix: "GitHub Actions"
14 | include: "scope"
15 | labels:
16 | - "dependencies"
17 | - "workflows"
18 |
19 | # Maintain dependencies for npm
20 | - package-ecosystem: "npm"
21 | directory: "/"
22 | target-branch: "trunk"
23 | allow:
24 | - dependency-type: direct
25 | schedule:
26 | interval: "daily"
27 | versioning-strategy: increase
28 | commit-message:
29 | prefix: "NPM"
30 | prefix-development: "NPM Dev"
31 | include: "scope"
32 | labels:
33 | - "dependencies"
34 | - "javascript"
35 |
36 | # Maintain dependencies for Composer
37 | - package-ecosystem: "composer"
38 | directory: "/"
39 | target-branch: "trunk"
40 | schedule:
41 | interval: "daily"
42 | allow:
43 | - dependency-type: direct
44 | versioning-strategy: increase
45 | commit-message:
46 | prefix: "Composer"
47 | prefix-development: "Composer Dev"
48 | include: "scope"
49 | labels:
50 | - "dependencies"
51 | - "php"
52 |
--------------------------------------------------------------------------------
/src/app/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @import "@newfold/ui-component-library";
2 |
3 | /* Tailwind layers */
4 | @tailwind base;
5 | @tailwind components;
6 | @tailwind utilities;
7 |
8 | /* Custom styles */
9 | @layer components {
10 | .nfd-root {
11 | /* Onboarding step container */
12 | .nfd-onboarding-step-container {
13 | @apply
14 | nfd-min-w-[800px]
15 | nfd-max-w-[800px]
16 | nfd-bg-transparent
17 | nfd-shadow-none
18 | nfd-rounded-none
19 | nfd-mx-auto
20 | tablet:!nfd-min-w-[90%]
21 | tablet:!nfd-max-w-[90%]
22 | mobile:!nfd-min-w-[90%]
23 | mobile:!nfd-max-w-[90%];
24 |
25 | .nfd-container__header {
26 | @apply
27 | nfd-text-content-primary
28 | nfd-pt-0
29 | nfd-px-0
30 | nfd-border-b-0;
31 |
32 | .nfd-title {
33 | @apply
34 | nfd-text-content-primary
35 | mobile:!nfd-text-lg;
36 | }
37 | }
38 |
39 | .nfd-container__block {
40 | @apply
41 | nfd-px-0
42 | nfd-pt-0;
43 | }
44 |
45 | .nfd-container__footer {
46 | @apply
47 | nfd-pb-0
48 | nfd-px-0
49 | nfd-border-t-0;
50 | }
51 | }
52 |
53 | /* Title */
54 | .nfd-title {
55 | @apply
56 | nfd-text-content-primary;
57 | }
58 |
59 | /* Label */
60 | .nfd-label {
61 | @apply
62 | nfd-text-content-primary
63 | nfd-text-[15px]
64 | nfd-font-semibold;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/includes/Services/I18nService.php:
--------------------------------------------------------------------------------
1 | url = NFD_HIIVE_BASE_URL;
26 | }
27 |
28 | /**
29 | * Get data from the endpoint containing the specific Hiive response.
30 | *
31 | * @param string $endpoint Endpoint request to get specific response
32 | * @param array $args Arguments determining response
33 | * @return \WP_Error|string
34 | */
35 | protected function get( $endpoint, $args = array() ) {
36 | $request = $this->url . $endpoint . '?' . http_build_query( $args );
37 |
38 | $response = \wp_remote_get( $request );
39 | if ( 200 === \wp_remote_retrieve_response_code( $response ) ) {
40 | $payload = \wp_remote_retrieve_body( $response );
41 |
42 | return $payload;
43 | }
44 |
45 | return new \WP_Error(
46 | 'hiive-error',
47 | 'Error in fetching data.',
48 | array( 'status' => \wp_remote_retrieve_response_code( $response ) )
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/includes/RestApi/SiteClassificationController.php:
--------------------------------------------------------------------------------
1 | namespace,
35 | $this->rest_base,
36 | array(
37 | 'methods' => \WP_REST_Server::READABLE,
38 | 'callback' => array( $this, 'get' ),
39 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
40 | )
41 | );
42 | }
43 |
44 | /**
45 | * Get site classification data.
46 | *
47 | * @return array
48 | */
49 | public function get() {
50 | if ( ! class_exists( 'NewfoldLabs\WP\Module\Data\SiteClassification\SiteClassification' ) ) {
51 | return array();
52 | }
53 | return SiteClassification::get();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/app/utils/blueprints/fetchBlueprints.js:
--------------------------------------------------------------------------------
1 | import { dispatch, select } from '@wordpress/data';
2 | import { nfdOnboardingStore } from '@/data/store';
3 | import { getBlueprints } from '@/utils/api';
4 |
5 | /**
6 | * Fetch the blueprints.
7 | */
8 | const fetchBlueprints = async () => {
9 | // If the blueprints are already fetched, return.
10 | const blueprints = select( nfdOnboardingStore ).getBlueprints();
11 | if ( blueprints.length > 0 ) {
12 | return;
13 | }
14 |
15 | // Fetch the blueprints.
16 | getBlueprints().then( ( response ) => {
17 | if ( ! response || response.error || ! Array.isArray( response.body ) ) {
18 | // eslint-disable-next-line no-console
19 | console.error( response.error );
20 | } else {
21 | // Put the results in the store.
22 | dispatch( nfdOnboardingStore ).setBlueprints( response.body );
23 |
24 | // Pre-download the preview images to avoid flickering.
25 | const images = response.body.map( ( blueprint ) => {
26 | return blueprint.screenshot_url;
27 | } );
28 | Promise.allSettled(
29 | images.map( ( imageUrl ) => {
30 | return new Promise( ( resolve, reject ) => {
31 | // eslint-disable-next-line no-undef
32 | const img = new Image();
33 | img.onload = () => resolve( imageUrl );
34 | img.onerror = () => reject( imageUrl );
35 | img.src = imageUrl;
36 | } );
37 | } )
38 | );
39 | }
40 | } );
41 | };
42 |
43 | export default fetchBlueprints;
44 |
--------------------------------------------------------------------------------
/includes/Permissions.php:
--------------------------------------------------------------------------------
1 | namespace,
33 | $this->rest_base,
34 | array(
35 | array(
36 | 'methods' => \WP_REST_Server::READABLE,
37 | 'callback' => array( $this, 'get_languages' ),
38 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
39 | ),
40 | )
41 | );
42 | }
43 |
44 | /**
45 | * Get available languages in WordPress.
46 | *
47 | * @return WP_REST_Response
48 | */
49 | public function get_languages() {
50 | // Use LanguageService to get languages
51 | $languages = LanguageService::get_all_languages();
52 |
53 | return new \WP_REST_Response(
54 | array(
55 | 'languages' => $languages,
56 | ),
57 | 200
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Brands/bluehost/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Brands/wordpress/step-loader-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Logo/LogoUploader.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
5 | import { Button, Placeholder } from '@wordpress/components';
6 | import { __ } from '@wordpress/i18n';
7 | import { upload } from '@wordpress/icons';
8 |
9 | const ALLOWED_MEDIA_TYPES = [ 'image' ];
10 | const ACCEPT_MEDIA_STRING = 'image/*';
11 |
12 | export default function LogoUploader( { onSelect, canUserEdit } ) {
13 | if ( ! canUserEdit ) {
14 | return (
15 |
24 | );
25 | }
26 |
27 | return (
28 |
33 |
34 | (
39 |
40 | { __( 'Upload Logo', 'wp-module-onboarding' ) }
41 |
42 | ) }
43 | />
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/includes/WP_Config.php:
--------------------------------------------------------------------------------
1 | wp_config = new \WPConfigTransformer( ABSPATH . 'wp-config.php' );
21 | }
22 |
23 | /**
24 | * Adds a new constant to WordPress Configuration.
25 | *
26 | * @param mixed $name Name of the constant
27 | * @param mixed $value Value of the constant
28 | *
29 | * @return boolean
30 | */
31 | public function add_constant( $name, $value ) {
32 | return $this->wp_config->add( 'constant', $name, $value, array( 'raw' => true ) );
33 | }
34 |
35 | /**
36 | * Updates an existing constant in WordPress Configuration.
37 | *
38 | * @param mixed $name Name of the constant
39 | * @param mixed $value Value of the constant
40 | *
41 | * @return boolean
42 | */
43 | public function update_constant( $name, $value ) {
44 | return $this->wp_config->update( 'constant', $name, $value, array( 'raw' => true ) );
45 | }
46 |
47 | /**
48 | * Checks if the constant already exists in the WordPress Configuration.
49 | *
50 | * @param mixed $name Name of the constant.
51 | *
52 | * @return boolean
53 | */
54 | public function constant_exists( $name ) {
55 | return $this->wp_config->exists( 'constant', $name );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Colors/ColorPaletteItem.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import { __experimentalHStack as HStack, Tooltip } from '@wordpress/components';
6 |
7 | export default function ColorPaletteItem( { palette, isActive, onSelect } ) {
8 | const handleKeyDown = ( event ) => {
9 | if ( event.key === 'Enter' || event.key === ' ' ) {
10 | event.preventDefault();
11 | onSelect( palette.name );
12 | }
13 | };
14 |
15 | // Extract colors from the palette structure
16 | const colorSwatches = palette.displayColors.map( ( item ) => item.color );
17 |
18 | return (
19 |
20 | onSelect( palette.name ) }
24 | onKeyDown={ handleKeyDown }
25 | tabIndex="0"
26 | aria-label={ palette.title }
27 | aria-current={ isActive }
28 | >
29 |
30 |
31 | { colorSwatches.map( ( color, index ) => (
32 |
40 | ) ) }
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/components/Step/Step.js:
--------------------------------------------------------------------------------
1 | import { motion } from 'motion/react';
2 | import { useAnimateRouteDirection } from '@/utils/hooks';
3 |
4 | const slideVariant = {
5 | enter: ( direction ) => {
6 | return {
7 | x: direction === 'forward' ? 300 : -300,
8 | opacity: 0,
9 | };
10 | },
11 | center: {
12 | x: 0,
13 | opacity: 1,
14 | },
15 | exit: ( direction ) => {
16 | return {
17 | x: direction === 'forward' ? -300 : 300,
18 | opacity: 0,
19 | };
20 | },
21 | };
22 |
23 | /**
24 | * Step component for onboarding steps (Animatable).
25 | * @param {ReactNode} children
26 | * @return {JSX.Element} Step component
27 | */
28 | const Step = ( { children } ) => {
29 | const direction = useAnimateRouteDirection();
30 |
31 | return (
32 |
44 | { children }
45 |
46 | );
47 | };
48 |
49 | /**
50 | * Actions container for the step component.
51 | * @param {ReactNode} children
52 | * @return {JSX.Element} Actions component
53 | */
54 | const Actions = ( { children } ) => {
55 | return (
56 |
57 | { children }
58 |
59 | );
60 | };
61 |
62 | Step.Actions = Actions;
63 |
64 | export default Step;
65 |
--------------------------------------------------------------------------------
/includes/RestApi/Themes/ThemeInstallerController.php:
--------------------------------------------------------------------------------
1 | namespace,
31 | $this->rest_base . '/initialize',
32 | array(
33 | array(
34 | 'methods' => \WP_REST_Server::CREATABLE,
35 | 'callback' => array( $this, 'initialize' ),
36 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
37 | ),
38 | )
39 | );
40 | }
41 |
42 | /**
43 | * Queue in the initial list of themes to be installed.
44 | *
45 | * @return \WP_REST_Response
46 | */
47 | public static function initialize() {
48 | if ( ThemeService::initialize() ) {
49 | return new \WP_REST_Response(
50 | array(),
51 | 202
52 | );
53 | }
54 |
55 | return new \WP_REST_Response(
56 | array(),
57 | 500
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import { TAILWINDCSS_PRESET } from '@newfold/ui-component-library';
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | presets: [ TAILWINDCSS_PRESET ],
6 | content: [
7 | ...TAILWINDCSS_PRESET.content, // Newfold UI
8 | './src/**/*.js', // Onboarding
9 | ],
10 | // Override preset theme colors
11 | theme: {
12 | extend: {
13 | colors: {
14 | primary: {
15 | DEFAULT: '#196BDE',
16 | 50: '#F5F9FE',
17 | 100: '#E6F0FD',
18 | 200: '#C5DCFB',
19 | 300: '#93BDF7',
20 | 400: '#4E94EE',
21 | 500: '#196BDE',
22 | 600: '#1259BC',
23 | 700: '#0C479A',
24 | 800: '#083577',
25 | 900: '#052455',
26 | },
27 | content: {
28 | default: '#030303',
29 | primary: '#1E1E1E',
30 | placeholder: '#B3BCC7',
31 | },
32 | },
33 | screens: {
34 | // Mobile breakpoints
35 | mobile: { max: '600px' },
36 | 'mobile-sm': { max: '480px' },
37 |
38 | // Tablet breakpoints
39 | tablet: { max: '1024px' },
40 | 'tablet-lg': { max: '1366px' },
41 |
42 | // Desktop breakpoints based on uploaded image resolutions
43 | 'desktop-sm': { min: '1280px' },
44 | 'desktop-md': { min: '1367px' },
45 | 'desktop-lg': { min: '1441px' },
46 | 'desktop-xl': { min: '1537px' },
47 | 'desktop-2xl': { min: '1921px' },
48 | 'desktop-3xl': { min: '2561px' },
49 |
50 | // Legacy breakpoints for backward compatibility
51 | small: { max: '1280px' },
52 | 'small-only': { min: '1024px', max: '1280px' },
53 | },
54 | },
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/src/app/steps/Fork/MigrationCard.js:
--------------------------------------------------------------------------------
1 | import { useNavigate } from 'react-router-dom';
2 | import { ArrowsRightLeftIcon } from '@heroicons/react/24/outline';
3 | import ActionCard from '@/components/ActionCard/ActionCard';
4 | import { OnboardingEvent, sendOnboardingEvent } from '@/utils/analytics/hiive';
5 | import { ACTION_FORK_OPTION_SELECTED } from '@/utils/analytics/hiive/constants';
6 |
7 | const MigrationCard = ( {
8 | canMigrateSite,
9 | migrationFallbackUrl = '',
10 | ...props
11 | } ) => {
12 | const navigate = useNavigate(); // Router navigate.
13 |
14 | const handleAction = () => {
15 | if ( canMigrateSite ) {
16 | navigate( '/migration', {
17 | state: { direction: 'forward' },
18 | replace: false,
19 | } );
20 | } else if ( migrationFallbackUrl ) {
21 | window.open( migrationFallbackUrl, '_blank' );
22 | }
23 | // Analytics: migration fork option selected event
24 | sendOnboardingEvent(
25 | new OnboardingEvent(
26 | ACTION_FORK_OPTION_SELECTED,
27 | 'MIGRATE'
28 | )
29 | );
30 | };
31 |
32 | return (
33 |
37 |
38 |
39 |
40 | { __( 'Import an Existing WordPress Site', 'wp-module-onboarding' ) }
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | export default MigrationCard;
48 |
--------------------------------------------------------------------------------
/includes/RestApi/Themes/ThemeColorsController.php:
--------------------------------------------------------------------------------
1 | namespace,
44 | $this->rest_base . $this->rest_extended_base,
45 | array(
46 | array(
47 | 'methods' => \WP_REST_Server::READABLE,
48 | 'callback' => array( $this, 'get_theme_colors' ),
49 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
50 | ),
51 | )
52 | );
53 | }
54 |
55 | /**
56 | * Retrieves the active theme color variations.
57 | *
58 | * @return array|\WP_Error
59 | */
60 | public function get_theme_colors() {
61 | $theme_color_palettes = Colors::get_colors_from_theme();
62 | return $theme_color_palettes;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/includes/Services/SettingsService.php:
--------------------------------------------------------------------------------
1 | $option_value ) {
24 | \update_option( Options::get_option_name( $option_key, false ), $option_value );
25 | }
26 | // Can't be part of initialization constants as they are static.
27 | \update_option( Options::get_option_name( 'start_date' ), gmdate( 'U' ) );
28 |
29 | // Flush permalinks
30 | flush_rewrite_rules();
31 |
32 | // Add constants to the WordPress configuration (wp-config.php)
33 | $wp_config_constants = Config::get_wp_config_initialization_constants();
34 | $wp_config = new WP_Config();
35 | foreach ( $wp_config_constants as $constant_key => $constant_value ) {
36 | if ( $wp_config->constant_exists( $constant_key ) ) {
37 | $wp_config->update_constant( $constant_key, $constant_value );
38 | continue;
39 | }
40 | $wp_config->add_constant( $constant_key, $constant_value );
41 | }
42 |
43 | \update_option( Options::get_option_name( 'settings_initialized' ), true );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/includes/Types/SiteClassification.php:
--------------------------------------------------------------------------------
1 | primary_type = ! empty( $primary_type ) ? $primary_type : 'other';
32 | $this->secondary_type = ! empty( $secondary_type ) ? $secondary_type : 'other';
33 | }
34 |
35 | /**
36 | * Set primary type.
37 | *
38 | * @param string $primary_type Primary type.
39 | */
40 | public function set_primary_type( string $primary_type ): void {
41 | $this->primary_type = $primary_type;
42 | }
43 |
44 | /**
45 | * Set secondary type.
46 | *
47 | * @param string $secondary_type Secondary type.
48 | */
49 | public function set_secondary_type( string $secondary_type ): void {
50 | $this->secondary_type = $secondary_type;
51 | }
52 |
53 | /**
54 | * Get primary type.
55 | *
56 | * @return string
57 | */
58 | public function get_primary_type(): string {
59 | return $this->primary_type;
60 | }
61 |
62 | /**
63 | * Get secondary type.
64 | *
65 | * @return string
66 | */
67 | public function get_secondary_type(): string {
68 | return $this->secondary_type;
69 | }
70 | }
--------------------------------------------------------------------------------
/src/Scripts/onboarding-restart-button/onboarding-restart-button.css:
--------------------------------------------------------------------------------
1 | /* Container for Build with AI */
2 | .build-with-ai {
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | text-align: center;
7 | height: auto;
8 | border: none;
9 | box-shadow: none;
10 | transition: background 100ms ease-in-out;
11 | }
12 |
13 | /* Link inside the Build with AI container */
14 | .build-with-ai__link {
15 | display: flex;
16 | flex-direction: column;
17 | position: relative;
18 | text-decoration: none;
19 | height: 100%;
20 | }
21 |
22 | /* Icon container */
23 | .build-with-ai__icon {
24 | flex-grow: 1;
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 | }
29 |
30 | /* Icon span */
31 | .build-with-ai__icon-span {
32 | width: 100px;
33 | height: 100px;
34 | display: flex;
35 | align-items: center;
36 | justify-content: center;
37 | margin-top: 25px;
38 | margin-bottom: 10px;
39 | border-radius: 50%;
40 | color: #8c8f94;
41 | font-size: 40px;
42 | background-color: rgba(140, 143, 148, 0.1);
43 | transition: background 100ms ease-in-out;
44 | }
45 |
46 | /* Text under the icon */
47 | .build-with-ai__text {
48 | margin: 0;
49 | color: #333;
50 | font-weight: 400;
51 | padding-bottom: 48px;
52 | transition: color 100ms ease-in-out;
53 | }
54 |
55 | /* Hover states for the Build with AI component */
56 | .build-with-ai:hover {
57 | background-color: #2271b1;
58 | }
59 |
60 | /* Hover effect on the icon span */
61 | .build-with-ai:hover .build-with-ai__icon-span {
62 | background-color: #fff;
63 | }
64 |
65 | /* Hover effect on the text */
66 | .build-with-ai:hover .build-with-ai__text {
67 | color: #fff;
68 | }
69 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Screens/ScreenRoot.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import {
6 | Card,
7 | CardBody,
8 | __experimentalVStack as VStack,
9 | __experimentalItemGroup as ItemGroup,
10 | CardMedia,
11 | } from '@wordpress/components';
12 | import { __ } from '@wordpress/i18n';
13 | import { color, typography, image } from '@wordpress/icons';
14 |
15 | /**
16 | * Internal dependencies
17 | */
18 | import { NavigationButton } from '../ScreenHeader/NavigationButton';
19 | import StylePreviewCard from '../StylePreviewCard';
20 |
21 | /**
22 | * Root screen for the Design Studio sidebar
23 | */
24 | export default function ScreenRoot() {
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | { __( 'Logo', 'wp-plugin-bluehost' ) }
37 |
38 |
39 |
40 | { __( 'Colors', 'wp-plugin-bluehost' ) }
41 |
42 |
43 |
44 | { __( 'Typography', 'wp-plugin-bluehost' ) }
45 |
46 |
47 |
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/src/Brands/hostgator/dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/Brands/hostgator/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/includes/Compatibility/Status.php:
--------------------------------------------------------------------------------
1 | result;
49 | $data['timestamp'] = \current_time( 'mysql' );
50 | }
51 |
52 | \update_option( Options::get_option_name( 'compatibility_results' ), $data );
53 |
54 | return false;
55 | }
56 |
57 | /**
58 | * Reset/Delete the stored scan results
59 | */
60 | public static function reset() {
61 | \delete_option( Options::get_option_name( 'compatibility_results' ) );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Typography/PreviewItem.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { __ } from '@wordpress/i18n';
5 | import { useState } from '@wordpress/element';
6 | import { check, Icon } from '@wordpress/icons';
7 |
8 | export default function TypographyPreviewItem( {
9 | headingFontFamily,
10 | bodyFontFamily,
11 | tooltipText,
12 | onClick,
13 | isSelected,
14 | } ) {
15 | const [ isUpdating, setIsUpdating ] = useState( false );
16 |
17 | const handleClick = async () => {
18 | if ( onClick && ! isUpdating ) {
19 | setIsUpdating( true );
20 | onClick();
21 | setIsUpdating( false );
22 | }
23 | };
24 |
25 | const handleKeyDown = ( event ) => {
26 | if ( onClick && ( event.key === 'Enter' || event.key === ' ' ) ) {
27 | event.preventDefault();
28 | handleClick();
29 | }
30 | };
31 |
32 | return (
33 |
42 |
43 | { __( 'Heading', 'wp-module-onboardingg' ) }
44 |
45 |
46 |
47 | { __( 'Paragraph text in your chosen font.', 'wp-module-onboarding' ) }
48 |
49 |
50 |
{ tooltipText }
51 |
52 | { isSelected && (
53 |
54 |
55 |
56 | ) }
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/src/DesignStudio/utils/simple-cache.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Simple in-memory cache for design studio data
3 | */
4 |
5 | const cache = new Map();
6 |
7 | /**
8 | * Set cache entry
9 | *
10 | * @param {string} key - Cache key
11 | * @param {*} value - Value to cache
12 | */
13 | export function setCache( key, value ) {
14 | cache.set( key, {
15 | data: value,
16 | timestamp: Date.now(),
17 | } );
18 | }
19 |
20 | /**
21 | * Get cache entry
22 | *
23 | * @param {string} key - Cache key
24 | * @param {number} ttl - Time to live in milliseconds (default: 10 minutes)
25 | * @return {*|null} Cached value or null if not found/expired
26 | */
27 | export function getCache( key, ttl = 10 * 60 * 1000 ) {
28 | const entry = cache.get( key );
29 |
30 | if ( ! entry ) {
31 | return null;
32 | }
33 |
34 | // Check if expired
35 | if ( Date.now() - entry.timestamp > ttl ) {
36 | cache.delete( key );
37 | return null;
38 | }
39 |
40 | return entry.data;
41 | }
42 |
43 | /**
44 | * Clear all cache
45 | */
46 | export function clearCache() {
47 | cache.clear();
48 | }
49 |
50 | /**
51 | * Generate a simple cache key for color palettes
52 | *
53 | * @param {number} page - Page number
54 | * @param {number} perPage - Items per page
55 | * @return {string} Cache key
56 | */
57 | export function colorPaletteKey( page = 1, perPage = 10 ) {
58 | return `color-palettes-${ page }-${ perPage }`;
59 | }
60 |
61 | /**
62 | * Generate a simple cache key for font pairings
63 | *
64 | * @param {number} page - Page number
65 | * @param {number} perPage - Items per page
66 | * @return {string} Cache key
67 | */
68 | export function fontPairingsKey( page = 1, perPage = 10 ) {
69 | return `font-pairings-${ page }-${ perPage }`;
70 | }
71 |
--------------------------------------------------------------------------------
/src/DesignStudio/utils/design-api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import apiFetch from '@wordpress/api-fetch';
5 |
6 | /**
7 | * Fetches paginated color palettes or font pairs from the WordPress REST API
8 | *
9 | * @async
10 | * @param {Object} options - The fetch options
11 | * @param {string} options.type - The type of data to fetch ('color-palettes' or 'font-pairs')
12 | * @param {string} [options.referer='nfd-onboarding'] - The referer value ('nfd-onboarding' or 'nfd-plugin')
13 | * @param {number} [options.page=1] - The page number to fetch
14 | * @param {number} [options.perPage=10] - Number of items per page
15 | * @return {Promise} The paginated data with metadata
16 | * @throws {Error} If the fetch request fails
17 | */
18 | export async function fetchDesignSettings( {
19 | type,
20 | referer = 'nfd-onboarding',
21 | page = 1,
22 | perPage = 10,
23 | } ) {
24 | if ( ! type || ! [ 'color-palettes', 'font-pairs' ].includes( type ) ) {
25 | throw new Error( 'Invalid type. Must be either "color-palettes" or "font-pairs"' );
26 | }
27 |
28 | try {
29 | const response = await apiFetch( {
30 | path: `/newfold-onboarding/v1/design/${ type }?page=${ page }&per_page=${ perPage }`,
31 | headers: {
32 | Referer: referer,
33 | },
34 | } );
35 |
36 | return {
37 | data: response.data || [],
38 | pagination: response.pagination || {
39 | total_items: 0,
40 | total_pages: 0,
41 | current_page: page,
42 | per_page: perPage,
43 | },
44 | };
45 | } catch ( error ) {
46 | // eslint-disable-next-line no-console
47 | console.error( `Failed to fetch ${ type }:`, error );
48 | throw error;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/app/steps/index.js:
--------------------------------------------------------------------------------
1 | import { ForkStep } from './Fork';
2 | import { IntakeStep } from './Intake';
3 | import { LogoStep } from './Logo';
4 | import { GeneratingStep } from './Generating';
5 | import { PreviewsStep } from './Previews';
6 | import { CanvasStep } from './Canvas';
7 | import { BlueprintsStep } from './Blueprints';
8 | import { BlueprintCanvasStep } from './BlueprintCanvas';
9 | import { MigrationStep } from './Migration';
10 |
11 | const STEPS = {
12 | fork: {
13 | path: '/',
14 | order: 10,
15 | isRequired: true,
16 | Component: ForkStep,
17 | },
18 | intake: {
19 | path: '/intake',
20 | order: 20,
21 | isRequired: true,
22 | Component: IntakeStep,
23 | },
24 | logo: {
25 | path: '/logo',
26 | order: 30,
27 | isRequired: true,
28 | Component: LogoStep,
29 | },
30 | generating: {
31 | path: '/generating',
32 | order: 40,
33 | isRequired: true,
34 | Component: GeneratingStep,
35 | },
36 | previews: {
37 | path: '/previews',
38 | order: 50,
39 | isRequired: true,
40 | Component: PreviewsStep,
41 | },
42 | design: {
43 | path: '/canvas',
44 | order: 60,
45 | isRequired: true,
46 | Component: CanvasStep,
47 | },
48 | blueprints: {
49 | path: '/blueprints',
50 | order: 70,
51 | isRequired: false,
52 | Component: BlueprintsStep,
53 | },
54 | blueprintCanvas: {
55 | path: '/blueprints-canvas',
56 | order: 80,
57 | isRequired: false,
58 | Component: BlueprintCanvasStep,
59 | },
60 | migration: {
61 | path: '/migration',
62 | order: 90,
63 | isRequired: false,
64 | Component: MigrationStep,
65 | },
66 | };
67 |
68 | export {
69 | STEPS,
70 | ForkStep,
71 | IntakeStep,
72 | LogoStep,
73 | GeneratingStep,
74 | PreviewsStep,
75 | CanvasStep,
76 | BlueprintsStep,
77 | BlueprintCanvasStep,
78 | MigrationStep,
79 | };
80 |
--------------------------------------------------------------------------------
/includes/RestApi/Themes/ThemeFontsController.php:
--------------------------------------------------------------------------------
1 | namespace,
42 | $this->rest_base . $this->rest_extended_base,
43 | array(
44 | array(
45 | 'methods' => \WP_REST_Server::READABLE,
46 | 'callback' => array( $this, 'get_theme_fonts' ),
47 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
48 | ),
49 | )
50 | );
51 | }
52 |
53 | /**
54 | * Retrieves the active theme font variations.
55 | *
56 | * @return array|\WP_Error
57 | */
58 | public function get_theme_fonts() {
59 | $theme_font_palettes = Fonts::get_fonts_from_theme();
60 |
61 | if ( ! $theme_font_palettes ) {
62 | return new \WP_Error(
63 | 'Theme Fonts not found',
64 | 'No WordPress Fonts are available for this theme.',
65 | array( 'status' => 404 )
66 | );
67 | }
68 |
69 | return $theme_font_palettes;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/app/steps/Intake/SiteTypeSelector.js:
--------------------------------------------------------------------------------
1 | import { SelectField } from '@newfold/ui-component-library';
2 | import classNames from 'classnames';
3 |
4 | const SiteTypeSelector = ( { value: selectedSiteType, onChange } ) => {
5 | const siteTypeOptions = [
6 | {
7 | value: 'personal',
8 | label: __( 'Personal', 'wp-module-onboarding' ),
9 | },
10 | {
11 | value: 'business',
12 | label: __( 'Business & Service', 'wp-module-onboarding' ),
13 | },
14 | {
15 | value: 'ecommerce',
16 | label: __( 'Online Store', 'wp-module-onboarding' ),
17 | },
18 | {
19 | value: 'linkinbio',
20 | label: __( 'Link in bio', 'wp-module-onboarding' ),
21 | },
22 | ];
23 |
24 | const getSelectedLabel = ( value ) => {
25 | const option = siteTypeOptions.find( ( o ) => o.value === value );
26 | return option ? option.label : __( 'Select Site Type', 'wp-module-onboarding' );
27 | };
28 |
29 | const handleChange = ( newValue ) => {
30 | // Validate the value is one of the options.
31 | const allowedValues = siteTypeOptions.map( ( option ) => option.value );
32 | if ( allowedValues.includes( newValue ) && newValue !== selectedSiteType ) {
33 | onChange( newValue );
34 | }
35 | };
36 |
37 | return (
38 |
39 | span]:nfd-text-gray-500' ) }
50 | />
51 |
52 | );
53 | };
54 |
55 | export default SiteTypeSelector;
56 |
--------------------------------------------------------------------------------
/.github/workflows/brand-plugin-test.yml:
--------------------------------------------------------------------------------
1 | name: Cypress Tests for Module Updates in Brand Plugin
2 |
3 | on:
4 | pull_request:
5 | types: [opened, reopened, ready_for_review, synchronize]
6 | branches:
7 | - main
8 | - trunk
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 | setup:
17 | name: Setup
18 | runs-on: ubuntu-latest
19 | outputs:
20 | branch: ${{ steps.extract_branch.outputs.branch }}
21 | steps:
22 | - name: Extract branch name
23 | shell: bash
24 | run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
25 | id: extract_branch
26 |
27 | bluehost:
28 | name: Bluehost Build and Test
29 | needs: setup
30 | uses: newfold-labs/workflows/.github/workflows/module-plugin-test.yml@main
31 | with:
32 | module-repo: ${{ github.repository }}
33 | module-branch: ${{ needs.setup.outputs.branch }}
34 | plugin-repo: "newfold-labs/wp-plugin-bluehost"
35 | secrets: inherit
36 |
37 | hostgator:
38 | name: HostGator Build and Test
39 | needs: setup
40 | uses: newfold-labs/workflows/.github/workflows/module-plugin-test.yml@main
41 | with:
42 | module-repo: ${{ github.repository }}
43 | module-branch: ${{ needs.setup.outputs.branch }}
44 | plugin-repo: "newfold-labs/wp-plugin-hostgator"
45 | secrets: inherit
46 |
47 | crazydomains:
48 | name: Crazy Domains Build and Test
49 | needs: setup
50 | uses: newfold-labs/workflows/.github/workflows/module-plugin-test.yml@main
51 | with:
52 | module-repo: ${{ github.repository }}
53 | module-branch: ${{ needs.setup.outputs.branch }}
54 | plugin-repo: "newfold-labs/wp-plugin-crazy-domains"
55 | secrets: inherit
56 |
--------------------------------------------------------------------------------
/src/DesignStudio/utils/styles-utils.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | /**
3 | * Remove global styles from all locations in the editor
4 | */
5 | export const removeGlobalStyles = () => {
6 | const hideGlobalStylesElements = () => {
7 | try {
8 | const stylesButtons = document?.querySelectorAll(
9 | 'button[aria-label="Styles"], ' +
10 | '.edit-site-header-edit-mode button[aria-label="Styles"], ' +
11 | '.interface-pinned-items button[aria-label="Styles"]'
12 | );
13 |
14 | stylesButtons.forEach( ( button ) => {
15 | if ( button ) {
16 | button.style.display = 'none';
17 | }
18 | } );
19 |
20 | if ( ! document?.getElementById( 'nfd-hide-global-styles-css' ) ) {
21 | const styleElement = document?.createElement( 'style' );
22 | styleElement.id = 'nfd-hide-global-styles-css';
23 | styleElement.innerHTML = `
24 | button[aria-label="Styles"],
25 | .interface-pinned-items button[aria-label="Styles"],
26 | #edit-site\\:global-styles,
27 | .interface-complementary-area[aria-label="Styles"],
28 | .components-dropdown-menu__menu-item[aria-label="Styles"],
29 | [aria-label="Styles panel"],
30 | .edit-site-global-styles-sidebar,
31 | button[aria-expanded="true"][aria-label="Styles"] {
32 | display: none !important;
33 | }
34 | `;
35 | document?.head.appendChild( styleElement );
36 | }
37 | } catch ( e ) {
38 | console.error( 'NFD Onboarding: Error hiding global styles', e );
39 | }
40 | };
41 |
42 | hideGlobalStylesElements();
43 |
44 | // Run the DOM-based approach repeatedly to catch dynamically rendered elements
45 | const interval = setInterval( hideGlobalStylesElements, 500 );
46 | setTimeout( () => clearInterval( interval ), 10000 ); // Stop after 10 seconds
47 | };
48 |
--------------------------------------------------------------------------------
/src/DesignStudio/hooks/useColorPaletteSelection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { useEffect, useState } from '@wordpress/element';
5 |
6 | // Helper function to compare colors
7 | const compareColors = ( color1, color2 ) => {
8 | // Normalize colors by removing spaces and converting to lowercase
9 | const normalize = ( color ) => color.toLowerCase().replace( /\s/g, '' );
10 | return normalize( color1 ) === normalize( color2 );
11 | };
12 |
13 | // Helper function to find matching palette
14 | const findMatchingPalette = ( palettes, styles ) => {
15 | if ( ! styles?.color?.palette ) {
16 | return null;
17 | }
18 |
19 | return palettes.find( ( palette ) => {
20 | const paletteColors = palette.palette.map( ( item ) => item.color );
21 | const globalColors = styles.color.palette.map( ( item ) => item.color );
22 |
23 | return (
24 | paletteColors.length === globalColors.length &&
25 | paletteColors.every( ( color, index ) => compareColors( color, globalColors[ index ] ) )
26 | );
27 | } );
28 | };
29 |
30 | export default function useColorPaletteSelection( colorPalettes, globalStyles, onChange ) {
31 | const [ selectedPalette, setSelectedPalette ] = useState( '' );
32 |
33 | // Effect to handle global styles changes
34 | useEffect( () => {
35 | if ( colorPalettes.length > 0 && globalStyles ) {
36 | const matchingPalette = findMatchingPalette( colorPalettes, globalStyles );
37 | if ( matchingPalette ) {
38 | setSelectedPalette( matchingPalette.name );
39 | }
40 | }
41 | }, [ globalStyles, colorPalettes ] );
42 |
43 | const handleSelect = ( paletteName ) => {
44 | // If the palette is already selected, do nothing
45 | if ( selectedPalette === paletteName ) {
46 | return;
47 | }
48 |
49 | setSelectedPalette( paletteName );
50 | const selectedPaletteObj = colorPalettes.find( ( p ) => p.name === paletteName );
51 | onChange?.( selectedPaletteObj );
52 | };
53 |
54 | return {
55 | selectedPalette,
56 | handleSelect,
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/src/DesignStudio/hooks/useColorPalettePagination.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { useState } from '@wordpress/element';
5 |
6 | /**
7 | * Internal dependencies
8 | */
9 | import { fetchDesignSettings } from '../utils/design-api';
10 | import { getCache, setCache, colorPaletteKey } from '../utils/simple-cache';
11 |
12 | export default function useColorPalettePagination() {
13 | const [ colorPalettes, setColorPalettes ] = useState( [] );
14 | const [ isLoading, setIsLoading ] = useState( true );
15 | const [ currentPage, setCurrentPage ] = useState( 1 );
16 | const [ totalPages, setTotalPages ] = useState( 1 );
17 |
18 | const loadPalettes = async ( page = 1 ) => {
19 | try {
20 | setIsLoading( true );
21 |
22 | // Check cache first
23 | const cacheKey = colorPaletteKey( page, 10 );
24 | const cached = getCache( cacheKey );
25 |
26 | if ( cached ) {
27 | setColorPalettes( cached.data );
28 | setTotalPages( cached.pagination.total_pages );
29 | setCurrentPage( cached.pagination.current_page );
30 | setIsLoading( false );
31 | return;
32 | }
33 |
34 | // Fetch from API
35 | const response = await fetchDesignSettings( {
36 | type: 'color-palettes',
37 | page,
38 | perPage: 10,
39 | } );
40 |
41 | // Cache the response
42 | setCache( cacheKey, response );
43 |
44 | setColorPalettes( response.data );
45 | setTotalPages( response.pagination.total_pages );
46 | setCurrentPage( response.pagination.current_page );
47 | } catch ( error ) {
48 | // eslint-disable-next-line no-console
49 | console.error( 'Failed to load palettes:', error );
50 | } finally {
51 | setIsLoading( false );
52 | }
53 | };
54 |
55 | const handlePageChange = ( newPage ) => {
56 | if ( newPage >= 1 && newPage <= totalPages ) {
57 | loadPalettes( newPage );
58 | }
59 | };
60 |
61 | return {
62 | colorPalettes,
63 | isLoading,
64 | currentPage,
65 | totalPages,
66 | loadPalettes,
67 | handlePageChange,
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/src/Scripts/sitegen-theme-marker/sitegen-theme-marker.js:
--------------------------------------------------------------------------------
1 | window.onload = function () {
2 | const homepages = window.nfdOnboarding.homepages.data;
3 | const activeTheme = window.nfdOnboarding.active;
4 | Object.keys( homepages ).forEach( ( slug ) => {
5 | const homepage = homepages[ slug ];
6 | const ele = document.getElementById( `${ slug }-name` );
7 | if ( ele ) {
8 | ele.innerHTML =
9 | ( activeTheme === slug ? 'Active: ' : '' ) +
10 | `
11 |
12 |
13 | ` +
14 | `
15 |
16 |
17 |
18 | ` +
19 | `${ homepage.title }
`;
20 | }
21 | } );
22 | };
23 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Screens/ScreenTypography.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import { __experimentalHeading as Heading } from '@wordpress/components';
6 | import { __ } from '@wordpress/i18n';
7 |
8 | /**
9 | * Internal dependencies
10 | */
11 | import { useFontPairings } from '../../hooks/useFontPairings';
12 | import ScreenHeader from '../ScreenHeader';
13 | import FontPairings from '../Typography/FontPairings';
14 |
15 | export default function ScreenTypography() {
16 | const { fontPairings, selectedStyle, handleSelectStyle } = useFontPairings();
17 |
18 | // If no font pairings are available yet, show a message
19 | if ( fontPairings.length === 0 ) {
20 | return (
21 | <>
22 |
26 |
27 |
28 |
{ __( 'Font Pairings', 'wp-module-onboarding' ) }
29 |
{ __( 'No font pairings available.', 'wp-module-onboarding' ) }
30 |
31 |
32 | >
33 | );
34 | }
35 |
36 | return (
37 | <>
38 |
45 |
46 |
47 |
48 | { __( 'Font Pairings', 'wp-module-onboarding' ) }
49 |
54 |
55 |
56 | >
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/app/components/BrandLoader/BrandLoader.js:
--------------------------------------------------------------------------------
1 | import { useSelect } from '@wordpress/data';
2 | import { nfdOnboardingStore } from '@/data/store';
3 | import bluehostLogo from '../../../Brands/bluehost/step-loader-logo.svg';
4 | import hostgatorLogo from '../../../Brands/hostgator/step-loader-logo.svg';
5 | import crazyDomainsLogo from '../../../Brands/crazy-domains/step-loader-logo.svg';
6 | import webcomLogo from '../../../Brands/webcom/logo.svg';
7 | import wordpressLogo from '../../../Brands/wordpress/step-loader-logo.svg';
8 | import './BrandLoader.scss';
9 |
10 | /**
11 | * BrandLoader component - displays brand-specific loading animation
12 | *
13 | * @param {Object} props Component props
14 | * @param {string} props.width Width of the loader (default: '120px')
15 | * @param {string} props.height Height of the loader (default: '120px')
16 | * @param {string} props.alt Alt text for the loader image
17 | * @return {JSX.Element} Brand loader component
18 | */
19 | const BrandLoader = ( { width = '120px', height = '120px', alt = 'Loading animation' } ) => {
20 | const brandName = useSelect( ( select ) => {
21 | return select( nfdOnboardingStore ).getBrandName();
22 | }, [] );
23 |
24 | if ( ! brandName ) {
25 | return null;
26 | }
27 |
28 | const normalizedBrand = brandName.toLowerCase().replace( / /g, '-' );
29 |
30 | // Map brand names to their imported logos
31 | const brandLogoMap = {
32 | bluehost: bluehostLogo,
33 | 'bluehost-india': bluehostLogo,
34 | hostgator: hostgatorLogo,
35 | 'hostgator-us': hostgatorLogo,
36 | 'hostgator-br': hostgatorLogo,
37 | 'crazy-domains': crazyDomainsLogo,
38 | webcom: webcomLogo,
39 | wordpress: wordpressLogo,
40 | };
41 |
42 | const logoUrl = brandLogoMap[ normalizedBrand ] || wordpressLogo;
43 |
44 | return (
45 |
55 | );
56 | };
57 |
58 | export default BrandLoader;
59 |
--------------------------------------------------------------------------------
/src/app/components/Orb/Orb.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from '@wordpress/element';
2 | import './stylesheet.scss';
3 |
4 | const Orb = ( { height = '80px' } ) => {
5 | // Inline style to handle custom properties
6 | const logoStyle = {
7 | '--wnd-ai-logo-size': height,
8 | height: `var(--wnd-ai-logo-size, ${ height })`,
9 | };
10 |
11 | useEffect( () => {
12 | document
13 | .querySelectorAll( '[data-wnd-ai-logo]' )
14 | .forEach( function ( input ) {
15 | let keyIsDown = false;
16 | let animationFrameId = null;
17 |
18 | const toggleClass = () => {
19 | if ( keyIsDown ) {
20 | document.body.classList.add( 'wnd-ai-logo-keydown' );
21 | } else {
22 | document.body.classList.remove( 'wnd-ai-logo-keydown' );
23 | }
24 | animationFrameId = null;
25 | };
26 |
27 | input.addEventListener( 'keydown', function () {
28 | if ( ! keyIsDown ) {
29 | keyIsDown = true;
30 | if ( ! animationFrameId ) {
31 | animationFrameId =
32 | window.requestAnimationFrame( toggleClass );
33 | }
34 | }
35 | } );
36 |
37 | input.addEventListener( 'keyup', function () {
38 | keyIsDown = false;
39 | if ( ! animationFrameId ) {
40 | animationFrameId =
41 | window.requestAnimationFrame( toggleClass );
42 | }
43 | } );
44 | } );
45 | }, [] );
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default Orb;
62 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Screens/ScreenColors.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import { __experimentalHeading as Heading, ToggleControl } from '@wordpress/components';
6 | import { useState } from '@wordpress/element';
7 | import { __ } from '@wordpress/i18n';
8 |
9 | /**
10 | * Internal dependencies
11 | */
12 | import useColorSettings from '../../hooks/useColorSettings';
13 | import ColorPalette from '../Colors/ColorPalette';
14 | import CustomColorPicker from '../Colors/CustomColorPicker';
15 | import ScreenHeader from '../ScreenHeader';
16 |
17 | export default function ScreenColors() {
18 | const [ isUsingCustomPalette, setIsUsingCustomPalette ] = useState( false );
19 |
20 | const { settings, globalStyles, updatePalette, updateCustomColor } = useColorSettings();
21 |
22 | const handlePaletteChange = ( paletteArray ) => {
23 | updatePalette( paletteArray );
24 | setIsUsingCustomPalette( false );
25 | };
26 |
27 | return (
28 | <>
29 |
36 |
37 |
38 | { __( 'Palette', 'wp-module-onboarding' ) }
39 |
40 |
41 |
42 | {
46 | setIsUsingCustomPalette( isChecked );
47 | } }
48 | className="nfd-design-studio-custom-palette-toggle"
49 | __nextHasNoMarginBottom={ true }
50 | />
51 | { isUsingCustomPalette && (
52 |
53 | ) }
54 |
55 |
56 | >
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/app/components/ErrorBoundaryFallback/ErrorBoundaryFallback.js:
--------------------------------------------------------------------------------
1 | import { Button, Title } from '@newfold/ui-component-library';
2 | import { pluginDashboardPage } from '@/data/constants';
3 | import errorImage from './nfd-err-boundary.svg';
4 |
5 | const BootError = () => {
6 | return (
7 |
8 |
9 |
10 |
{ __( 'Whoops! Looks Like You Took a Wrong Turn' ) }
11 |
{ __( "This page is not currently available. Let's send you back to your dashboard where all the good stuff is!" ) }
12 |
window.location.href = pluginDashboardPage }
15 | >
16 | { __( 'Go to Dashboard' ) }
17 |
18 |
19 |
20 | );
21 | };
22 |
23 | const UnspecifiedError = () => {
24 | return (
25 |
26 |
27 |
28 |
{ __( 'Oops! Something Unexpected Happened' ) }
29 |
{ __( 'We encountered a temporary hiccup, please try again!' ) }
30 |
window.location.reload() }>{ __( 'Try Again' ) }
31 |
32 |
33 | );
34 | };
35 |
36 | const ErrorBoundaryFallback = ( { error } ) => {
37 | const getErrorComponent = () => {
38 | switch ( error.message ) {
39 | case 'onboarding_completed':
40 | return ;
41 | case 'no_sitegen_capability':
42 | return ;
43 | default:
44 | return ;
45 | }
46 | };
47 |
48 | return (
49 |
50 | { getErrorComponent() }
51 |
52 | );
53 | };
54 |
55 | export default ErrorBoundaryFallback;
56 |
--------------------------------------------------------------------------------
/includes/RestApi/GlobalStylesController.php:
--------------------------------------------------------------------------------
1 | namespace,
27 | $this->rest_base . '/set-color-palette',
28 | array(
29 | array(
30 | 'methods' => \WP_REST_Server::EDITABLE,
31 | 'callback' => array( $this, 'set_color_palette' ),
32 | 'args' => $this->get_set_color_palette_args(),
33 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
34 | ),
35 | )
36 | );
37 | }
38 |
39 | public function get_set_color_palette_args() {
40 | return array(
41 | 'color_palette' => array(
42 | 'type' => 'object',
43 | 'required' => true,
44 | ),
45 | );
46 | }
47 |
48 | /**
49 | * Set the color palette.
50 | *
51 | * @param \WP_REST_Request $request The request object.
52 | * @return \WP_REST_Response
53 | */
54 | public function set_color_palette( \WP_REST_Request $request ): \WP_REST_Response {
55 | $data = json_decode( $request->get_body(), true );
56 | $color_palette = $data['color_palette'];
57 | if ( ! is_array( $color_palette ) || empty( $color_palette ) ) {
58 | return new \WP_REST_Response(
59 | 'Color palette is invalid.',
60 | 400
61 | );
62 | }
63 |
64 | $response = ( new GlobalStylesService() )->set_color_palette( $color_palette );
65 | if ( is_wp_error( $response ) ) {
66 | return new \WP_REST_Response(
67 | 'Error setting color palette.',
68 | 500
69 | );
70 | }
71 |
72 | return new \WP_REST_Response(
73 | array( 'colors' => $response ),
74 | 200
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/app/components/ActionCard/ActionCard.js:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { forwardRef } from '@wordpress/element';
3 |
4 | /**
5 | * ActionCardOverlay component
6 | * An overlay layer to sit on top of the ActionCard to provide consistent onClick behavior
7 | *
8 | * @return {JSX.Element} The ActionCardOverlay component
9 | */
10 | const ActionCardOverlay = () => {
11 | return (
12 |
13 | );
14 | };
15 |
16 | /**
17 | * ActionCard component
18 | * A card component that can be used to display content in an actionable card format
19 | *
20 | * @param {Object} props - The props for the ActionCard component
21 | * @param {Function} props.onClick - The function to call when the card is clicked
22 | * @param {Function} props.onKeyDown - The function to call when the card is keyed down
23 | * @param {React.ReactNode} props.children - The children to render inside the card
24 | * @param {Object} ref - The ref for the card
25 | *
26 | * @return {JSX.Element} The ActionCard component
27 | */
28 | const ActionCard = forwardRef( ( {
29 | onClick,
30 | onKeyDown,
31 | className,
32 | children,
33 | ...props
34 | }, ref ) => {
35 | const handleKeyDown = ( e ) => {
36 | if ( onKeyDown ) {
37 | return onKeyDown( e );
38 | }
39 | if ( e.key === 'Enter' || e.key === ' ' ) {
40 | e.preventDefault();
41 | onClick();
42 | }
43 | };
44 |
45 | return (
46 |
58 |
59 | { children }
60 |
61 | );
62 | } );
63 |
64 | ActionCard.displayName = 'ActionCard';
65 |
66 | export default ActionCard;
67 |
--------------------------------------------------------------------------------
/src/app/steps/Canvas/CanvasStep.js:
--------------------------------------------------------------------------------
1 | import { useNavigate } from 'react-router-dom';
2 | import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
3 | import { Button } from '@newfold/ui-component-library';
4 | import { Step } from '@/components';
5 | import { Preview, Sidebar } from './';
6 |
7 | /**
8 | * Since we disable the sidebar on mobile, we need to add a button to switch between previews.
9 | * @return {JSX.Element} - A button that allows the user to safely navigate back to the previews step to change the preview.
10 | */
11 | const MobileLayoutSwitcher = () => {
12 | const navigate = useNavigate();
13 |
14 | const handleNavigate = () => {
15 | navigate( '/previews', {
16 | state: { direction: 'backward' },
17 | } );
18 | };
19 |
20 | return (
21 |
27 |
28 | { __( 'Change Preview', 'wp-module-onboarding' ) }
29 |
30 |
31 | );
32 | };
33 |
34 | const CanvasStep = () => {
35 | const [ canvasHeight, setCanvasHeight ] = useState( '100dvh' );
36 |
37 | const getCustomStyles = () => {
38 | return (
39 | `
40 | .nfd-onboarding-body {
41 | padding-top: 0 !important;
42 | padding-bottom: 0 !important;
43 | }
44 | `
45 | );
46 | };
47 |
48 | // On mount: calculate the appropriate canvas height.
49 | useEffect( () => {
50 | const appHeaderHeight = document.querySelector( '.nfd-onboarding-header' )?.offsetHeight + 1 || 0;
51 | setCanvasHeight( `calc(100dvh - ${ appHeaderHeight }px)` );
52 | }, [] );
53 |
54 | return (
55 |
56 |
57 |
67 |
68 | );
69 | };
70 |
71 | export default CanvasStep;
72 |
--------------------------------------------------------------------------------
/.github/workflows/lint-check-php.yml:
--------------------------------------------------------------------------------
1 | name: 'Lint Checker: PHP'
2 | on:
3 | push:
4 | paths:
5 | - '**.php'
6 | - '!build/**/*.php'
7 | pull_request:
8 | types: [opened, edited, reopened, ready_for_review]
9 | paths:
10 | - '**.php'
11 | - '!build/**/*.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26 |
27 | # User PHP 7.4 here for compatibility with the WordPress codesniffer rules.
28 | - name: Setup PHP
29 | uses: shivammathur/setup-php@20529878ed81ef8e78ddf08b480401e6101a850f # v2.35.3
30 | with:
31 | php-version: '7.3'
32 | coverage: none
33 | tools: composer, cs2pr
34 |
35 | - uses: technote-space/get-diff-action@f27caffdd0fb9b13f4fc191c016bb4e0632844af # v6.1.2
36 | with:
37 | PATTERNS: |
38 | **/*.php
39 | !build/**/*.php
40 |
41 | - name: Get Composer cache directory
42 | id: composer-cache
43 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
44 | if: "!! env.GIT_DIFF"
45 |
46 | - name: Cache Composer vendor directory
47 | uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
48 | with:
49 | path: ${{ steps.composer-cache.outputs.dir }}
50 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
51 | restore-keys: |
52 | ${{ runner.os }}-composer-
53 | if: "!! env.GIT_DIFF"
54 |
55 | - name: Validate composer.json and composer.lock
56 | run: composer validate
57 | if: "!! env.GIT_DIFF"
58 |
59 | - name: Install dependencies
60 | run: composer install --no-progress --optimize-autoloader --prefer-dist
61 | if: "!! env.GIT_DIFF"
62 |
63 | - name: Detecting PHP Code Standards Violations
64 | run: vendor/bin/phpcs --standard=phpcs.xml -s ${{ env.GIT_DIFF }}
65 | if: "!! env.GIT_DIFF"
66 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Colors/ColorPalette.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import {
6 | __experimentalGrid as Grid,
7 | __experimentalVStack as VStack,
8 | Spinner,
9 | } from '@wordpress/components';
10 | import { useEffect } from '@wordpress/element';
11 |
12 | /**
13 | * Internal dependencies
14 | */
15 | import useColorPalettePagination from '../../hooks/useColorPalettePagination';
16 | import useColorPaletteSelection from '../../hooks/useColorPaletteSelection';
17 | import ColorPaletteItem from './ColorPaletteItem';
18 | import ColorPalettePagination from './ColorPalettePagination';
19 |
20 | export default function ColorPalette( { onChange, globalStyles } ) {
21 | const { colorPalettes, isLoading, currentPage, totalPages, loadPalettes, handlePageChange } =
22 | useColorPalettePagination();
23 |
24 | const { selectedPalette, handleSelect } = useColorPaletteSelection(
25 | colorPalettes,
26 | globalStyles,
27 | onChange
28 | );
29 |
30 | useEffect( () => {
31 | loadPalettes();
32 | // eslint-disable-next-line react-hooks/exhaustive-deps
33 | }, [] );
34 |
35 | // Show spinner when loading
36 | if ( isLoading ) {
37 | return (
38 |
43 | );
44 | }
45 |
46 | // Return empty div when no palettes, but keep component mounted
47 | if ( colorPalettes.length === 0 ) {
48 | return
;
49 | }
50 |
51 | return (
52 |
53 |
54 |
55 | { colorPalettes.map( ( palette, index ) => (
56 |
62 | ) ) }
63 |
64 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/src/app/components/Navigate/Navigate.js:
--------------------------------------------------------------------------------
1 | import { useNavigate } from "react-router-dom";
2 | import { Button, Link } from "@newfold/ui-component-library";
3 | import classNames from "classnames";
4 |
5 | /**
6 | * Navigate component.
7 | *
8 | * Router navigation component that can handle routing within the app.
9 | * @param {string} [toRoute='/'] - The path to navigate to when the component is clicked.
10 | * @param {'forward' | 'backward'} [direction='forward'] - The animation direction of the navigation.
11 | * @param {'button' | 'link'} [as='button'] - Determines whether to render as a Button or a Link.
12 | * @param {'primary' | 'secondary'} [variant='primary'] - The variant style of the Button component.
13 | * @param {Function} [callback] - The callback function to be called when the component is clicked.
14 | * @param {ReactNode} children - The child elements or text to be displayed inside the component.
15 | * @param {Object} props - Additional props to be passed to the
16 | * @return {JSX.Element} A Button or Link component that navigates to the specified route on click.
17 | */
18 | const Navigate = ( {
19 | toRoute = '/',
20 | direction = 'forward',
21 | as = 'button',
22 | variant = 'primary',
23 | callback,
24 | children,
25 | className,
26 | ...props
27 | } ) => {
28 | const navigate = useNavigate(); // Router navigate.
29 |
30 | const handleOnClick = () => {
31 | if ( callback ) {
32 | callback();
33 | }
34 |
35 | navigate( toRoute, {
36 | state: { direction },
37 | replace: false,
38 | } );
39 | };
40 |
41 | if ( 'link' === as ) {
42 | return (
43 |
48 | { children }
49 |
50 | );
51 | }
52 |
53 | return (
54 |
64 | { children }
65 |
66 | );
67 | };
68 |
69 | export default Navigate;
70 |
--------------------------------------------------------------------------------
/src/app/utils/hooks/useGlobalStyles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { store as coreStore } from '@wordpress/core-data';
5 | import { useDispatch, useSelect } from '@wordpress/data';
6 | import { useCallback } from '@wordpress/element';
7 |
8 | function useGlobalStyles() {
9 | const [ hasResolved, setHasResolved ] = useState( false );
10 |
11 | const { editEntityRecord, saveEditedEntityRecord } = useDispatch( coreStore );
12 |
13 | const { globalStylesId, settings } = useSelect( ( select ) => {
14 | const {
15 | __experimentalGetCurrentGlobalStylesId,
16 | getEditedEntityRecord,
17 | } = select( coreStore );
18 |
19 | const id = __experimentalGetCurrentGlobalStylesId
20 | ? __experimentalGetCurrentGlobalStylesId()
21 | : undefined;
22 | const record = id ? getEditedEntityRecord( 'root', 'globalStyles', id ) : undefined;
23 |
24 | return {
25 | globalStylesId: id,
26 | settings: record?.settings,
27 | };
28 | }, [] );
29 |
30 | // Ready to fulfill.
31 | useEffect( () => {
32 | if ( globalStylesId && settings ) {
33 | setHasResolved( true );
34 | }
35 | }, [ globalStylesId, settings ] );
36 |
37 | const setConfig = useCallback(
38 | ( config ) => {
39 | if ( ! globalStylesId || ! settings ) {
40 | return;
41 | }
42 |
43 | editEntityRecord( 'root', 'globalStyles', globalStylesId, {
44 | settings: {
45 | ...( settings || {} ),
46 | ...config,
47 | },
48 | } );
49 | },
50 | [ editEntityRecord, globalStylesId, settings ]
51 | );
52 |
53 | const setColorPalette = useCallback(
54 | async ( paletteArray ) => {
55 | if ( ! globalStylesId || ! settings ) {
56 | // eslint-disable-next-line no-console
57 | console.error( 'setColorPalette dependency not yet ready. Use "hasResolved" state to check if the dependency is resolved.' );
58 | return;
59 | }
60 |
61 | if ( ! paletteArray ) {
62 | return;
63 | }
64 |
65 | setConfig( {
66 | color: {
67 | palette: {
68 | ...( settings?.color?.palette || {} ),
69 | theme: paletteArray,
70 | },
71 | },
72 | } );
73 |
74 | return await saveEditedEntityRecord( 'root', 'globalStyles', globalStylesId );
75 | },
76 | [ globalStylesId, settings, setConfig, saveEditedEntityRecord ]
77 | );
78 |
79 | return {
80 | hasResolved,
81 | settings,
82 | setColorPalette,
83 | };
84 | }
85 |
86 | export default useGlobalStyles;
87 |
--------------------------------------------------------------------------------
/includes/RestApi/PreviewsController.php:
--------------------------------------------------------------------------------
1 | namespace,
35 | $this->rest_base . '/snapshot',
36 | array(
37 | 'methods' => \WP_REST_Server::EDITABLE,
38 | 'callback' => array( $this, 'generate_snapshot' ),
39 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
40 | 'args' => $this->generate_snapshot_args(),
41 | )
42 | );
43 | }
44 |
45 | /**
46 | * Args for Generating the snapshot.
47 | *
48 | * @return array
49 | */
50 | public function generate_snapshot_args() {
51 | return array(
52 | 'content' => array(
53 | 'required' => true,
54 | 'type' => 'string',
55 | ),
56 | 'slug' => array(
57 | 'required' => true,
58 | 'type' => 'string',
59 | ),
60 | 'custom_styles' => array(
61 | 'required' => false,
62 | 'type' => 'string',
63 | ),
64 | );
65 | }
66 |
67 | /**
68 | * Generates a snapshot.
69 | *
70 | * @param \WP_REST_Request $request The incoming request.
71 | * @return \WP_REST_Response|\WP_Error
72 | */
73 | public function generate_snapshot( \WP_REST_Request $request ): \WP_REST_Response {
74 | $content = $request->get_param( 'content' );
75 | $slug = $request->get_param( 'slug' );
76 | $custom_styles = $request->get_param( 'custom_styles' );
77 |
78 | $snapshot = PreviewsService::generate_snapshot( $content, $slug, $custom_styles );
79 | if ( is_wp_error( $snapshot ) ) {
80 | return new \WP_REST_Response(
81 | $snapshot->get_error_message(),
82 | 500
83 | );
84 | }
85 |
86 | return new \WP_REST_Response(
87 | $snapshot,
88 | 200
89 | );
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/app/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import { useLocation } from 'react-router-dom';
3 | import { ReactComponent as BluehostLogo } from '@/assets/bluehost-logo.svg';
4 | import { HeaderActions as CanvasStepHeaderActions } from '@/steps/Canvas';
5 | import { HeaderActions as BlueprintCanvasStepHeaderActions } from '@/steps/BlueprintCanvas';
6 | import { BackButton } from '@/components';
7 |
8 | // Map of routes that should show a back button in the header
9 | const BACK_NAVIGATION_MAP = {
10 | '/intake': '/',
11 | '/logo': '/intake',
12 | '/generating': '/logo',
13 | '/previews': '/logo',
14 | '/blueprints': '/',
15 | };
16 |
17 | const Header = () => {
18 | const [ isCanvasStep, setIsCanvasStep ] = useState( false );
19 | const [ isBlueprintCanvasStep, setIsBlueprintCanvasStep ] = useState( false );
20 | const location = useLocation();
21 |
22 | // Show Canvas Step Header Actions on Canvas Step
23 | useEffect( () => {
24 | if ( location.pathname.includes( '/canvas' ) ) {
25 | setIsCanvasStep( true );
26 | setIsBlueprintCanvasStep( false );
27 | } else if ( location.pathname.includes( '/blueprints-canvas' ) ) {
28 | setIsBlueprintCanvasStep( true );
29 | setIsCanvasStep( false );
30 | } else {
31 | setIsCanvasStep( false );
32 | setIsBlueprintCanvasStep( false );
33 | }
34 | }, [ location ] );
35 |
36 | // Determine if the current route should show a back button
37 | const backRoute = BACK_NAVIGATION_MAP[ location.pathname ];
38 | const showBackButton = !! backRoute;
39 |
40 | return (
41 |
62 | );
63 | };
64 |
65 | export default Header;
66 |
--------------------------------------------------------------------------------
/includes/Compatibility/Scan.php:
--------------------------------------------------------------------------------
1 | '6.5',
17 | );
18 |
19 | /**
20 | * Environment data.
21 | *
22 | * @var array
23 | */
24 | public $data = array(
25 | 'wp_version' => '',
26 | );
27 |
28 | /**
29 | * Active Configuration.
30 | *
31 | * @var array
32 | */
33 | public $config = array();
34 |
35 | /**
36 | * Test statuses.
37 | *
38 | * @var array
39 | */
40 | public $test_statuses = array(
41 | 'compatible',
42 | 'unsupported-wp',
43 | );
44 |
45 | /**
46 | * Result of the compatibility scan.
47 | *
48 | * @var string
49 | */
50 | public $result = '';
51 |
52 | /**
53 | * Scan constructor.
54 | *
55 | * @param array $config Config data.
56 | */
57 | public function __construct( $config = array() ) {
58 | $this->setup( $config );
59 | // Fetch Relevant Data
60 | $this->fetch();
61 | // Evaluate Using Configuration & Data
62 | $this->evaluate();
63 |
64 | return array(
65 | 'status' => $this->result,
66 | 'data' => $this->data,
67 | );
68 | }
69 |
70 | /**
71 | * Register configuration.
72 | *
73 | * @param array $config Config data.
74 | */
75 | protected function setup( $config ) {
76 | $this->config = array_merge( $this->default_config, $config );
77 | }
78 |
79 | /**
80 | * Set up environment data to be checked.
81 | */
82 | protected function fetch() {
83 | global $wp_version;
84 |
85 | $this->data = array_merge(
86 | $this->data,
87 | array(
88 | 'wp_version' => $wp_version,
89 | )
90 | );
91 |
92 | $previous = Status::get();
93 | if ( ! empty( $previous ) && is_array( $previous ) ) {
94 | $this->data['previous_result'] = $previous;
95 | }
96 | }
97 |
98 | /**
99 | * Run the compatibility check.
100 | */
101 | protected function evaluate() {
102 |
103 | $this->result = 'scan-initiated';
104 |
105 | if ( version_compare( $this->data['wp_version'], $this->config['min_wp'], '<' ) ) {
106 | $this->result = 'unsupported-wp';
107 | }
108 |
109 | if ( 'scan-initiated' === $this->result ) {
110 | $this->result = 'compatible';
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/includes/Services/ReduxStateService.php:
--------------------------------------------------------------------------------
1 | 'state_input',
25 | 'steps' => 'state_steps',
26 | 'sitegen' => 'state_sitegen',
27 | 'blueprints' => 'state_blueprints',
28 | );
29 |
30 | /**
31 | * Get the state data
32 | *
33 | * @param string $state The slice name to get
34 | * @return array The state data
35 | */
36 | public static function get( string $state ): array {
37 | $data = array();
38 | if ( ! self::validate( $state ) ) {
39 | return $data;
40 | }
41 |
42 | $data = \get_option( Options::get_option_name( self::$states[ $state ] ), false );
43 | if ( ! $data ) {
44 | $data = array();
45 | }
46 | return $data;
47 | }
48 |
49 | /**
50 | * Update the input slice state
51 | *
52 | * @param string $state The slice name to update
53 | * @param array $data The update slice state
54 | * @return bool True if the update was successful, false otherwise
55 | */
56 | public static function update( string $state, array $data ): bool {
57 | if ( ! self::validate( $state, $data ) ) {
58 | return false;
59 | }
60 |
61 | // Update the last updated time
62 | $data['last_updated'] = time();
63 |
64 | return \update_option( Options::get_option_name( self::$states[ $state ] ), $data );
65 | }
66 |
67 | /**
68 | * Validate the state and data
69 | *
70 | * @param string $state The slice name to update
71 | * @param array|null $data The update slice state
72 | * @return bool True if the update was successful, false otherwise
73 | */
74 | private static function validate( string $state, ?array $data = null ): bool {
75 | // Validate the state
76 | $slices = array_keys( self::$states );
77 | if ( ! in_array( $state, $slices ) ) {
78 | return false;
79 | }
80 |
81 | // Validate the data if it is provided
82 | if ( null !== $data && empty( $data ) ) {
83 | return false;
84 | }
85 |
86 | return true;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/includes/Services/ThemeService.php:
--------------------------------------------------------------------------------
1 | post_id = $post_id;
34 | $this->image_urls = $image_urls;
35 | }
36 |
37 | /**
38 | * Execute the task
39 | *
40 | * @return bool|\WP_Error True on success, WP_Error on failure
41 | */
42 | public function execute() {
43 | try {
44 | // Upload images to WordPress media library
45 | $result = SiteGenImageService::upload_images_to_wp_media_library( $this->image_urls, $this->post_id );
46 |
47 | if ( is_wp_error( $result ) ) {
48 | return $result;
49 | }
50 |
51 | if ( false === $result ) {
52 | return new \WP_Error( 'upload_failed', 'Image upload returned false' );
53 | }
54 |
55 | // Update post content with new image URLs
56 | if ( ! empty( $result ) ) {
57 | $content_updated = SiteGenImageService::update_post_content_with_new_image_urls( $this->post_id, $result );
58 | if ( ! $content_updated ) {
59 | return new \WP_Error( 'content_update_failed', 'Failed to update post content with new image URLs' );
60 | }
61 | }
62 |
63 | return true;
64 | } catch ( \Exception $e ) {
65 | return new \WP_Error( 'image_sideload_failed', $e->getMessage() );
66 | }
67 | }
68 |
69 | /**
70 | * Get the post ID
71 | *
72 | * @return int
73 | */
74 | public function get_post_id() {
75 | return $this->post_id;
76 | }
77 |
78 | /**
79 | * Get the image URLs
80 | *
81 | * @return array
82 | */
83 | public function get_image_urls() {
84 | return $this->image_urls;
85 | }
86 |
87 | /**
88 | * Get a unique identifier for this task
89 | *
90 | * @return string
91 | */
92 | public function get_id() {
93 | return 'image_sideload_' . $this->post_id . '_' . md5( maybe_serialize( $this->image_urls ) );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Screens/ScreenLogo.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import { isBlobURL } from '@wordpress/blob';
6 | import { PanelBody, Placeholder, Spinner } from '@wordpress/components';
7 | import { __ } from '@wordpress/i18n';
8 |
9 | /**
10 | * Internal dependencies
11 | */
12 | import useLogoManagement from '../../hooks/useLogoManagement';
13 | import LogoPreview from '../Logo/LogoPreview';
14 | import LogoUploader from '../Logo/LogoUploader';
15 | import ScreenHeader from '../ScreenHeader';
16 |
17 | export default function ScreenLogo() {
18 | const {
19 | siteLogoId,
20 | canUserEdit,
21 | mediaItemData,
22 | isRequestingMediaItem,
23 | temporaryURL,
24 | logoUrl,
25 | logoMediaDetails,
26 | alt,
27 | onSelectLogo,
28 | onRemoveLogo,
29 | } = useLogoManagement();
30 |
31 | // Determine what to display for the logo
32 | let logoContent;
33 | const isLoading = siteLogoId === undefined || isRequestingMediaItem;
34 |
35 | if ( isLoading ) {
36 | logoContent = (
37 |
38 |
39 |
40 |
41 |
42 | );
43 | } else if ( logoUrl || temporaryURL ) {
44 | // Display the logo
45 | const imgSrc = temporaryURL || logoUrl;
46 | const isTransient = !! temporaryURL && isBlobURL( temporaryURL );
47 |
48 | logoContent = (
49 |
60 | );
61 | } else {
62 | logoContent = ;
63 | }
64 |
65 | return (
66 |
67 |
74 |
75 |
76 |
{ logoContent }
77 |
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/app/steps/Fork/SiteCreatorCard.js:
--------------------------------------------------------------------------------
1 | import { useRef } from '@wordpress/element';
2 | import { useNavigate } from 'react-router-dom';
3 | import { Title } from '@newfold/ui-component-library';
4 | import aiSiteGenIconUrl from '@/assets/ai-sitegen-icon.svg';
5 | import ActionCard from '@/components/ActionCard/ActionCard';
6 | import { OnboardingEvent, sendOnboardingEvent } from '@/utils/analytics/hiive';
7 | import { ACTION_FORK_OPTION_SELECTED } from '@/utils/analytics/hiive/constants';
8 |
9 | const SiteCreatorCard = ( { initialFocus = false } ) => {
10 | const cardRef = useRef( null );
11 | const navigate = useNavigate();
12 |
13 | // Focus the card if initialFocus is true
14 | useEffect( () => {
15 | if ( initialFocus ) {
16 | window.requestAnimationFrame( () => {
17 | if ( cardRef.current ) {
18 | cardRef.current.focus();
19 | }
20 | } );
21 | }
22 | }, [ initialFocus ] );
23 |
24 | const handleAction = () => {
25 | navigate( '/intake', {
26 | state: { direction: 'forward' },
27 | replace: false,
28 | } );
29 |
30 | // Analytics: site creator fork option selected event
31 | sendOnboardingEvent(
32 | new OnboardingEvent(
33 | ACTION_FORK_OPTION_SELECTED,
34 | 'AI'
35 | )
36 | );
37 | };
38 |
39 | return (
40 |
44 |
45 |
46 |
50 |
51 | { __( 'Site Creator', 'wp-module-onboarding' ) }
52 |
53 |
54 |
55 |
56 |
60 | { __( 'Get a site created in seconds!', 'wp-module-onboarding' ) }
61 |
62 |
63 |
64 | { __( 'Content & design generated by AI,', 'wp-module-onboarding' ) }
65 |
66 |
67 |
68 | { __( 'fully customizable by you.', 'wp-module-onboarding' ) }
69 |
70 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | export default SiteCreatorCard;
78 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const restrictedImports = [
2 | {
3 | name: 'framer-motion',
4 | message:
5 | 'Please use the Framer Motion API through `@wordpress/components` instead.',
6 | },
7 | {
8 | name: 'lodash',
9 | // [TODO] Update this list as we start moving away from lodash.
10 | importNames: [
11 | 'memoize',
12 | ],
13 | message:
14 | 'This Lodash method is not recommended. Please use native functionality instead. If using `memoize`, please use `memize` instead.',
15 | },
16 | {
17 | name: 'reakit',
18 | message:
19 | 'Please use Reakit API through `@wordpress/components` instead.',
20 | },
21 | {
22 | name: 'redux',
23 | importNames: [ 'combineReducers' ],
24 | message: 'Please use `combineReducers` from `@wordpress/data` instead.',
25 | },
26 | {
27 | name: 'puppeteer-testing-library',
28 | message: '`puppeteer-testing-library` is still experimental.',
29 | },
30 | {
31 | name: '@emotion/css',
32 | message:
33 | 'Please use `@emotion/react` and `@emotion/styled` in order to maintain iframe support. As a replacement for the `cx` function, please use the `useCx` hook defined in `@wordpress/components` instead.',
34 | },
35 | {
36 | name: '@wordpress/edit-post',
37 | message:
38 | "edit-post is a WordPress top level package that shouldn't be imported into other packages",
39 | },
40 | {
41 | name: '@wordpress/edit-site',
42 | message:
43 | "edit-site is a WordPress top level package that shouldn't be imported into other packages",
44 | },
45 | {
46 | name: '@wordpress/edit-widgets',
47 | message:
48 | "edit-widgets is a WordPress top level package that shouldn't be imported into other packages",
49 | },
50 | ];
51 |
52 | module.exports = {
53 | root: true,
54 | extends: [
55 | 'plugin:@wordpress/eslint-plugin/recommended-with-formatting',
56 | ],
57 | settings: {
58 | 'import/resolver': {
59 | alias: {
60 | map: [
61 | [ '@', './src/app' ],
62 | ],
63 | extensions: [ '.js', '.jsx', '.json', '.css', '.scss' ],
64 | },
65 | },
66 | },
67 | globals: {
68 | __: true,
69 | _n: true,
70 | sprintf: true,
71 | useContext: true,
72 | useEffect: true,
73 | useState: true,
74 | useLocation: true,
75 | useNavigate: true,
76 | },
77 | rules: {
78 | 'no-restricted-imports': [
79 | 'error',
80 | {
81 | paths: restrictedImports,
82 | },
83 | ],
84 | 'jsdoc/check-param-names': 'off',
85 | 'jsdoc/no-undefined-types': [
86 | 'error',
87 | {
88 | definedTypes: [
89 | 'React',
90 | 'JSX',
91 | 'ReactNode',
92 | ],
93 | },
94 | ],
95 | },
96 | };
97 |
--------------------------------------------------------------------------------
/src/app/components/AppBody/AppBody.js:
--------------------------------------------------------------------------------
1 | import { Routes, Route, useLocation } from 'react-router-dom';
2 | import { ErrorBoundary as AppErrorBoundary } from '@newfold/ui-component-library';
3 | import { STEPS } from '@/steps';
4 | import { AnimateRoutes, ErrorBoundaryFallback } from '@/components';
5 | import { OnboardingEvent, sendOnboardingEvent } from '@/utils/analytics/hiive';
6 | import { ACTION_PAGEVIEW } from '@/utils/analytics/hiive/constants';
7 |
8 | const AppBody = () => {
9 | /**
10 | * Check if any conditions prevent the onboarding from being accessed.
11 | *
12 | * @return {boolean} True if the onboarding can be accessed, false otherwise.
13 | */
14 | const canAccessOnboarding = () => {
15 | const response = {
16 | result: true,
17 | error: null,
18 | };
19 |
20 | // Block if the onboarding has already been completed.
21 | if ( 'completed' === window.nfdOnboarding?.runtime?.status ) {
22 | response.result = false;
23 | response.error = 'onboarding_completed';
24 | }
25 |
26 | // Block if missing 'hasAISiteGen' capability.
27 | if ( ! window.NewfoldRuntime?.capabilities?.hasAISiteGen ) {
28 | response.result = false;
29 | response.error = 'no_sitegen_capability';
30 | }
31 |
32 | return response;
33 | };
34 |
35 | /**
36 | * Get the routes for the onboarding steps.
37 | *
38 | * @return {Array} Array of components.
39 | */
40 | const getRoutes = () => {
41 | return Object.entries( STEPS ).map( ( [ key, Step ] ) => {
42 | return } />;
43 | } );
44 | };
45 |
46 | /**
47 | * Boot the onboarding.
48 | *
49 | * @return {React.ReactNode} The onboarding routes.
50 | */
51 | const boot = () => {
52 | // Check if the onboarding can be accessed.
53 | const canBoot = canAccessOnboarding();
54 | if ( ! canBoot.result ) {
55 | throw new Error( canBoot.error );
56 | }
57 |
58 | return (
59 |
60 | { getRoutes() }
61 |
62 | );
63 | };
64 |
65 | // Analytics: Track pageview events.
66 | const location = useLocation();
67 | useEffect( () => {
68 | sendOnboardingEvent( new OnboardingEvent( ACTION_PAGEVIEW ) );
69 | }, [ location ] );
70 |
71 | return (
72 |
77 | );
78 | };
79 |
80 | export default AppBody;
81 |
--------------------------------------------------------------------------------
/includes/Models/Theme.php:
--------------------------------------------------------------------------------
1 | theme_name = $theme_name;
38 | $this->is_newfold_theme = false;
39 | }
40 |
41 | /**
42 | * Sets the Theme Name
43 | *
44 | * @param string $theme_name name of the theme.
45 | * @return void
46 | */
47 | public function set_theme_name( $theme_name ) {
48 | $this->theme_name = $theme_name;
49 | }
50 |
51 | /**
52 | * Retrieve the Theme Name
53 | *
54 | * @return string
55 | */
56 | public function get_theme_name() {
57 | return $this->theme_name;
58 | }
59 |
60 | /**
61 | * Sets the Theme image path
62 | *
63 | * @param string $theme_image Path to theme screenshot image.
64 | * @return void
65 | */
66 | public function set_theme_image( $theme_image ) {
67 | $this->theme_image = $theme_image;
68 | }
69 |
70 | /**
71 | * Retrieve the path to theme screenshot image
72 | *
73 | * @return string
74 | */
75 | public function get_theme_image() {
76 | return $this->theme_image;
77 | }
78 |
79 | /**
80 | * Sets the status of a theme as a Newfold theme.
81 | *
82 | * @param boolean $is_newfold_theme Determines if there is a Newfold theme
83 | * @return void
84 | */
85 | public function set_is_newfold_theme( $is_newfold_theme ) {
86 | $this->is_newfold_theme = $is_newfold_theme;
87 | }
88 |
89 | /**
90 | * Retrieve is_newfold_theme status - true if the theme author is Newfold Digital.
91 | *
92 | * @return boolean
93 | */
94 | public function get_is_newfold_theme() {
95 | return $this->is_newfold_theme;
96 | }
97 |
98 | /**
99 | * To JSON Serialize the Theme data
100 | *
101 | * @return array
102 | */
103 | public function jsonSerialize() {
104 | return array(
105 | 'name' => $this->theme_name,
106 | 'image' => $this->theme_image,
107 | 'isNewfoldTheme' => $this->is_newfold_theme,
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/includes/RestApi/RestApi.php:
--------------------------------------------------------------------------------
1 | controllers as $controller ) {
54 | /**
55 | * Get an instance of the WP_REST_Controller.
56 | *
57 | * @var $instance WP_REST_Controller
58 | */
59 | $instance = new $controller();
60 | $instance->register_routes();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/DesignStudio/hooks/useColorSettings.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { store as coreStore } from '@wordpress/core-data';
5 | import { useDispatch, useSelect } from '@wordpress/data';
6 | import { useCallback, useMemo } from '@wordpress/element';
7 |
8 | export default function useColorSettings() {
9 | const { editEntityRecord } = useDispatch( coreStore );
10 |
11 | const { globalStylesId, settings, rawPalette } = useSelect( ( select ) => {
12 | const { __experimentalGetCurrentGlobalStylesId, getEditedEntityRecord } = select( coreStore );
13 | const id = __experimentalGetCurrentGlobalStylesId
14 | ? __experimentalGetCurrentGlobalStylesId()
15 | : undefined;
16 | const record = id ? getEditedEntityRecord( 'root', 'globalStyles', id ) : undefined;
17 |
18 | return {
19 | globalStylesId: id,
20 | settings: record?.settings,
21 | rawPalette: record?.settings?.color?.palette?.theme,
22 | };
23 | }, [] );
24 |
25 | const themePalette = useMemo( () => rawPalette || [], [ rawPalette ] );
26 |
27 | const globalStyles = useMemo(
28 | () => ( {
29 | color: {
30 | palette: themePalette,
31 | },
32 | } ),
33 | [ themePalette ]
34 | );
35 |
36 | const setConfig = useCallback(
37 | ( config ) => {
38 | if ( ! globalStylesId || ! settings ) {
39 | return;
40 | }
41 |
42 | editEntityRecord( 'root', 'globalStyles', globalStylesId, {
43 | settings: {
44 | ...( settings || {} ),
45 | ...config,
46 | },
47 | } );
48 | },
49 | [ editEntityRecord, globalStylesId, settings ]
50 | );
51 |
52 | const updatePalette = useCallback(
53 | ( paletteArray ) => {
54 | if ( ! globalStylesId || ! settings ) {
55 | return;
56 | }
57 |
58 | setConfig( {
59 | color: {
60 | palette: {
61 | ...( settings?.color?.palette || {} ),
62 | theme: paletteArray?.palette,
63 | },
64 | },
65 | } );
66 | },
67 | [ globalStylesId, settings, setConfig ]
68 | );
69 |
70 | const updateCustomColor = useCallback(
71 | ( slug, newColor ) => {
72 | if ( ! globalStylesId || ! settings ) {
73 | return;
74 | }
75 |
76 | const updatedPalette = themePalette.map( ( color ) =>
77 | color.slug === slug ? { ...color, color: newColor } : color
78 | );
79 |
80 | setConfig( {
81 | color: {
82 | palette: {
83 | ...( settings?.color?.palette || {} ),
84 | theme: updatedPalette,
85 | },
86 | },
87 | } );
88 | },
89 | [ globalStylesId, settings, themePalette, setConfig ]
90 | );
91 |
92 | return {
93 | settings,
94 | globalStyles,
95 | updatePalette,
96 | updateCustomColor,
97 | };
98 | }
99 |
--------------------------------------------------------------------------------
/src/DesignStudio/hooks/useTypographyUpdate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { useDispatch, useSelect } from '@wordpress/data';
5 | import { store as coreStore } from '@wordpress/core-data';
6 | import { useMemo } from '@wordpress/element';
7 |
8 | /**
9 | * Updates global typography settings with selected font families.
10 | *
11 | * @return {Object} Typography update functions and current settings
12 | */
13 | export function useTypographyUpdate() {
14 | const { editEntityRecord } = useDispatch( coreStore );
15 |
16 | const { globalStylesId, record } = useSelect( ( select ) => {
17 | const { __experimentalGetCurrentGlobalStylesId, getEditedEntityRecord } = select( coreStore );
18 | const id = __experimentalGetCurrentGlobalStylesId
19 | ? __experimentalGetCurrentGlobalStylesId()
20 | : undefined;
21 |
22 | return {
23 | globalStylesId: id,
24 | record: id ? getEditedEntityRecord( 'root', 'globalStyles', id ) : undefined,
25 | };
26 | }, [] );
27 |
28 | const settings = record?.settings;
29 |
30 | const globalStyles = useMemo(
31 | () => ( {
32 | typography: {
33 | fontFamilies: record?.settings?.typography?.fontFamilies || {},
34 | elements: record?.styles?.elements || {},
35 | },
36 | } ),
37 | [ record?.settings?.typography?.fontFamilies, record?.styles?.elements ]
38 | );
39 |
40 | const updateTypography = async ( headingFontFamily, bodyFontFamily ) => {
41 | if ( ! globalStylesId || ! settings ) {
42 | return false;
43 | }
44 |
45 | try {
46 | editEntityRecord( 'root', 'globalStyles', globalStylesId, {
47 | styles: {
48 | ...( record?.styles || {} ),
49 | blocks: {
50 | ...( record?.styles?.blocks || {} ),
51 | 'core/heading': {
52 | ...( record?.styles?.blocks?.[ 'core/heading' ] || {} ),
53 | typography: {
54 | ...( record?.styles?.blocks?.[ 'core/heading' ]?.typography || {} ),
55 | fontFamily: headingFontFamily,
56 | },
57 | },
58 | },
59 | typography: {
60 | ...( record?.styles?.typography || {} ),
61 | fontFamily: bodyFontFamily,
62 | },
63 | elements: {
64 | ...( record?.styles?.elements || {} ),
65 | heading: {
66 | ...( record?.styles?.elements?.heading || {} ),
67 | typography: {
68 | ...( record?.styles?.elements?.heading?.typography || {} ),
69 | fontFamily: headingFontFamily,
70 | },
71 | },
72 | },
73 | },
74 | } );
75 | return true;
76 | } catch ( error ) {
77 | return false;
78 | }
79 | };
80 |
81 | return {
82 | settings,
83 | globalStyles,
84 | updateTypography,
85 | };
86 | }
87 |
--------------------------------------------------------------------------------
/.github/workflows/lint-check-spa.yml:
--------------------------------------------------------------------------------
1 | name: "Lint Checker: Onboarding SPA"
2 | on:
3 | workflow_dispatch:
4 | push:
5 | paths:
6 | - "src/**/*.js"
7 | - "src/**/*.scss"
8 | pull_request:
9 | types: [opened, edited, reopened, ready_for_review]
10 | paths:
11 | - "src/**/*.js"
12 | - "src/**/*.scss"
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 | lint-check-spa:
20 | name: Run Lint Checks
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Checkout Repository
24 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25 |
26 | # Install Node and npm
27 | - name: Setup Node.js
28 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
29 | with:
30 | node-version: 16.x
31 | cache: 'npm'
32 |
33 | # Checks if node_modules exists in the cache.
34 | - name: Cache node_modules directory
35 | id: cache
36 | uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
37 | with:
38 | path: node_modules
39 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
40 | restore-keys: ${{ runner.os }}-node-
41 |
42 | # Installs @wordpress/scripts for lint checks if it does not exist in the cache.
43 | - name: Install dependencies
44 | run: npm i @wordpress/scripts@26.10.0 --legacy-peer-deps
45 | if: steps.cache.outputs.cache-hit != 'true'
46 |
47 | # Gets the files changed wrt to trunk and filters out the js files.
48 | - uses: technote-space/get-diff-action@f27caffdd0fb9b13f4fc191c016bb4e0632844af # v6.1.2
49 | with:
50 | PATTERNS: |
51 | +(src)/**/*.js
52 |
53 | # Runs wp-scripts for checking JS coding issues.
54 | - name: Run JS Lint
55 | id: js-lint
56 | run: npx wp-scripts lint-js ${{ env.GIT_DIFF }}
57 | if: "!! env.GIT_DIFF"
58 |
59 | # Gets the files changed wrt to trunk and filters out the SASS files.
60 | - uses: technote-space/get-diff-action@f27caffdd0fb9b13f4fc191c016bb4e0632844af # v6.1.2
61 | with:
62 | PATTERNS: |
63 | +(src)/**/*.scss
64 | if: ${{ success() || steps.js-lint.conclusion == 'failure' }}
65 |
66 | # Runs wp-scripts for checking SASS coding issues.
67 | - name: Run SASS Lint
68 | id: sass-lint
69 | run: npx wp-scripts lint-style ${{ env.GIT_DIFF }}
70 | if: ${{ (!! env.GIT_DIFF) && (success() || steps.js-lint.conclusion == 'failure') }}
71 |
--------------------------------------------------------------------------------
/src/DesignStudio/components/Logo/LogoPreview.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */
2 | /**
3 | * WordPress dependencies
4 | */
5 | import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
6 | import {
7 | Button,
8 | __experimentalHStack as HStack,
9 | Spinner,
10 | __experimentalTruncate as Truncate,
11 | } from '@wordpress/components';
12 | import { __ } from '@wordpress/i18n';
13 |
14 | export default function LogoPreview( {
15 | imgSrc,
16 | alt,
17 | isTransient,
18 | canUserEdit,
19 | siteLogoId,
20 | onSelectLogo,
21 | onRemoveLogo,
22 | logoMediaDetails,
23 | mediaItemData,
24 | } ) {
25 | const { width: naturalWidth, height: naturalHeight } = logoMediaDetails?.sizes?.full || {};
26 |
27 | return (
28 |
29 |
30 |
35 | { isTransient && (
36 |
37 |
38 |
39 | ) }
40 |
41 | { canUserEdit && (
42 |
43 |
44 | (
49 |
55 | { __( 'Replace', 'wp-module-onboarding' ) }
56 |
57 | ) }
58 | />
59 |
60 |
61 | ) }
62 |
63 |
64 |
65 |
66 |
67 | { logoMediaDetails?.sizes?.full?.file ||
68 | mediaItemData?.filename ||
69 | __( 'Current logo', 'wp-module-onboarding' ) }
70 |
71 | { naturalWidth && naturalHeight && (
72 |
73 | { naturalWidth } × { naturalHeight }
74 |
75 | ) }
76 |
77 |
78 | { canUserEdit && siteLogoId && (
79 |
85 | { __( 'Remove', 'wp-module-onboarding' ) }
86 |
87 | ) }
88 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/includes/Application.php:
--------------------------------------------------------------------------------
1 | container = $container;
40 |
41 | $defaults = array(
42 | 'option_name' => 'nfd_onboarding',
43 | 'admin_screen_id' => container()->plugin()->id,
44 | 'admin_app_url' => \admin_url( 'admin.php?page=nfd-onboarding' ),
45 | );
46 |
47 | $this->args = \wp_parse_args(
48 | $container->has( 'onboarding' ) ? $container['onboarding'] : array(),
49 | $defaults
50 | );
51 |
52 | if ( is_readable( NFD_ONBOARDING_DIR . '/vendor/autoload.php' ) ) {
53 | require_once NFD_ONBOARDING_DIR . '/vendor/autoload.php';
54 | }
55 |
56 | \do_action( 'nfd_module_onboarding_pre_init' );
57 |
58 | // Reset the stored Compatibility Status every time WP Core is updated.
59 | \add_action( '_core_updated_successfully', array( Status::class, 'reset' ) );
60 |
61 | \add_filter( 'login_redirect', array( LoginRedirect::class, 'wplogin' ), 10, 3 );
62 | \add_filter( 'newfold_sso_success_url', array( LoginRedirect::class, 'sso' ), 10 );
63 | \add_filter(
64 | Options::get_option_name( 'redirect' ) . '_disable',
65 | array( LoginRedirect::class, 'remove_handle_redirect_action' )
66 | );
67 |
68 | if ( defined( '\\WP_CLI' ) && \WP_CLI ) {
69 | new WP_CLI();
70 | }
71 |
72 | if ( Permissions::is_authorized_admin() || Permissions::rest_is_authorized_admin() ) {
73 | new RestApi();
74 | new WP_Admin();
75 | new ExternalRedirectInterceptor();
76 | }
77 |
78 | if ( Permissions::is_authorized_admin() ) {
79 | StatusService::track();
80 | // Initialize event tracking for database option changes
81 | EventService::init();
82 | }
83 |
84 | \do_action( 'nfd_module_onboarding_post_init' );
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/includes/ExternalRedirectInterceptor.php:
--------------------------------------------------------------------------------
1 | url_has_whitelisted_params( $location );
35 |
36 | // Get the brand plugin page URL from the runtime data.
37 | if (
38 | isset( $runtime_data['currentBrand'], $runtime_data['currentBrand']['pluginDashboardPage'] ) &&
39 | is_string( $runtime_data['currentBrand']['pluginDashboardPage'] )
40 | ) {
41 | // Set the brand plugin page URL.
42 | $brand_plugin_url = $runtime_data['currentBrand']['pluginDashboardPage'];
43 | }
44 |
45 | // Allow the redirect if it has whitelisted params or the brand plugin page URL is empty.
46 | if ( $location_has_whitelisted_params || empty( $brand_plugin_url ) ) {
47 | return $location;
48 | }
49 |
50 | // Check if the location is the brand plugin page.
51 | $location_is_brand_plugin_url = strpos( $location, $brand_plugin_url );
52 | // Intercept if the redirect is going anywhere other than the brand plugin page.
53 | if ( false === $location_is_brand_plugin_url || 0 !== $location_is_brand_plugin_url ) {
54 | return '';
55 | }
56 |
57 | // Allow the redirect if it's going to the brand plugin page.
58 | return $location;
59 | }
60 |
61 | /**
62 | * Check if the URL has any whitelisted params.
63 | *
64 | * @param string $url The URL to check.
65 | * @return bool True if the URL has any whitelisted params, false otherwise.
66 | */
67 | private function url_has_whitelisted_params( string $url ): bool {
68 | $whitelisted_params = array(
69 | 'referrer' => WP_Admin::$slug,
70 | );
71 |
72 | foreach ( $whitelisted_params as $key => $value ) {
73 | if ( false !== strpos( $url, $key . '=' . $value ) ) {
74 | return true;
75 | }
76 | }
77 |
78 | return false;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/app/steps/BlueprintCanvas/Preview.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState, useEffect } from '@wordpress/element';
2 | import { useSelect } from '@wordpress/data';
3 | import { nfdOnboardingStore } from '@/data/store';
4 | import { Iframe, BrandLoader } from '@/components';
5 |
6 | const Preview = () => {
7 | const [ preview, setPreview ] = useState( null );
8 | const [ isLoading, setIsLoading ] = useState( true );
9 |
10 | const { blueprints, selectedBlueprint } = useSelect( ( select ) => {
11 | return {
12 | blueprints: select( nfdOnboardingStore ).getBlueprints(),
13 | selectedBlueprint: select( nfdOnboardingStore ).getSelectedBlueprint(),
14 | };
15 | } );
16 |
17 | const refMap = useRef( {
18 | previewContainer: null,
19 | previewContainerResizeObserver: null,
20 | iframeElement: null,
21 | previousIframeDimensions: {
22 | width: 0,
23 | height: 0,
24 | },
25 | } );
26 |
27 | const StatusOverlay = () => {
28 | const appHeaderHeight = document.querySelector( '.nfd-onboarding-header' )?.offsetHeight || 0;
29 |
30 | if ( isLoading ) {
31 | return (
32 |
38 |
39 |
40 | );
41 | }
42 | return null;
43 | };
44 |
45 | const getPreview = () => {
46 | const selectedPreview = blueprints.find( ( blueprint ) => blueprint.slug === selectedBlueprint );
47 | setPreview( selectedPreview );
48 | };
49 |
50 | useEffect( () => {
51 | setIsLoading( true );
52 | getPreview();
53 | // eslint-disable-next-line react-hooks/exhaustive-deps
54 | }, [ selectedBlueprint ] );
55 |
56 | const iframeOnLoad = () => {
57 | setTimeout( () => {
58 | // Set 500ms delay to allow the iframe to fully render and avoid flickering.
59 | setIsLoading( false );
60 | }, 500 );
61 | };
62 |
63 | return (
64 | {
67 | refMap.current.previewContainer = el;
68 | } }
69 | >
70 |
71 | { preview &&
88 | );
89 | };
90 |
91 | export default Preview;
92 |
--------------------------------------------------------------------------------
/src/Brands/wordpress/dark-other.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/steps/Logo/LogoStep.js:
--------------------------------------------------------------------------------
1 | import { useDispatch, useSelect } from '@wordpress/data';
2 | import { store as coreStore } from '@wordpress/core-data';
3 | import { Container } from '@newfold/ui-component-library';
4 | import { Navigate, Step } from '@/components';
5 | import { OnboardingEvent, trackOnboardingEvent } from '@/utils/analytics/hiive';
6 | import { ACTION_LOGO_ADDED, ACTION_LOGO_SKIPPED } from '@/utils/analytics/hiive/constants';
7 | import { LogoUploadInput } from '.';
8 | import { nfdOnboardingStore } from '@/data/store';
9 |
10 | const LogoStep = () => {
11 | const [ isUploading, setIsUploading ] = useState( false );
12 |
13 | const { editEntityRecord, saveEditedEntityRecord } = useDispatch( coreStore );
14 | const { siteLogoId, storeLogoId } = useSelect( ( select ) => {
15 | // Get the site logo from the core data store.
16 | const { getEntityRecord } = select( coreStore );
17 | const siteSettings = getEntityRecord( 'root', 'site' );
18 | const logoId = siteSettings?.site_logo;
19 |
20 | // Get the logo from the input slice.
21 | const logo = select( nfdOnboardingStore ).getLogo();
22 | return {
23 | siteLogoId: logoId,
24 | storeLogoId: logo.id,
25 | };
26 | }, [] );
27 |
28 | const handleNext = () => {
29 | // If the logo is set.
30 | if ( null !== storeLogoId && storeLogoId !== siteLogoId ) {
31 | editEntityRecord( 'root', 'site', undefined, {
32 | site_logo: storeLogoId,
33 | } );
34 | saveEditedEntityRecord( 'root', 'site' );
35 |
36 | // Analytics: track the logo added event
37 | trackOnboardingEvent( new OnboardingEvent( ACTION_LOGO_ADDED ) );
38 | } else {
39 | // Analytics: track the logo skipped event
40 | trackOnboardingEvent( new OnboardingEvent( ACTION_LOGO_SKIPPED ) );
41 | }
42 | };
43 |
44 | return (
45 |
46 |
47 |
52 |
53 |
54 |
55 |
56 |
57 |
63 | { __( 'Next', 'wp-module-onboarding' ) }
64 |
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default LogoStep;
73 |
--------------------------------------------------------------------------------
/src/DesignStudio/hooks/useLogoManagement.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WordPress dependencies
3 | */
4 | import { store as coreStore } from '@wordpress/core-data';
5 | import { useDispatch, useSelect } from '@wordpress/data';
6 | import { useEffect, useState } from '@wordpress/element';
7 |
8 | export default function useLogoManagement() {
9 | const [ temporaryURL, setTemporaryURL ] = useState();
10 |
11 | // Get current site logo from WordPress data store
12 | const { siteLogoId, canUserEdit, mediaItemData, isRequestingMediaItem } = useSelect(
13 | ( select ) => {
14 | const { canUser, getEntityRecord, getEditedEntityRecord } = select( coreStore );
15 |
16 | const _canUserEdit = canUser( 'update', { kind: 'root', name: 'site' } );
17 | const siteSettings = _canUserEdit ? getEditedEntityRecord( 'root', 'site' ) : undefined;
18 | const siteData = getEntityRecord( 'root', '__unstableBase' );
19 | const _siteLogoId = _canUserEdit ? siteSettings?.site_logo : siteData?.site_logo;
20 |
21 | const mediaItem =
22 | _siteLogoId && select( coreStore ).getMedia( _siteLogoId, { context: 'view' } );
23 |
24 | const _isRequestingMediaItem =
25 | !! _siteLogoId &&
26 | ! select( coreStore ).hasFinishedResolution( 'getMedia', [
27 | _siteLogoId,
28 | { context: 'view' },
29 | ] );
30 |
31 | return {
32 | siteLogoId: _siteLogoId,
33 | canUserEdit: _canUserEdit,
34 | url: siteData?.home,
35 | mediaItemData: mediaItem,
36 | isRequestingMediaItem: _isRequestingMediaItem,
37 | };
38 | },
39 | []
40 | );
41 |
42 | // Get dispatch functions
43 | const { editEntityRecord } = useDispatch( coreStore );
44 |
45 | // Get logo details
46 | const {
47 | alt_text: alt,
48 | source_url: logoUrl,
49 | media_details: logoMediaDetails,
50 | } = mediaItemData ?? {};
51 |
52 | // Set logo function
53 | const setLogo = ( newValue ) => {
54 | editEntityRecord( 'root', 'site', undefined, {
55 | site_logo: newValue,
56 | } );
57 | };
58 |
59 | // Handle logo selection
60 | const onSelectLogo = ( media ) => {
61 | if ( ! media ) {
62 | return;
63 | }
64 |
65 | if ( ! media.id && media.url ) {
66 | // This is a temporary blob image
67 | setTemporaryURL( media.url );
68 | return;
69 | }
70 |
71 | setLogo( media.id );
72 | };
73 |
74 | // Handle logo removal
75 | const onRemoveLogo = () => {
76 | setLogo( null );
77 | };
78 |
79 | // Reset temporary url when logoUrl is available
80 | useEffect( () => {
81 | if ( logoUrl && temporaryURL ) {
82 | setTemporaryURL();
83 | }
84 | }, [ logoUrl, temporaryURL ] );
85 |
86 | return {
87 | siteLogoId,
88 | canUserEdit,
89 | mediaItemData,
90 | isRequestingMediaItem,
91 | temporaryURL,
92 | logoUrl,
93 | logoMediaDetails,
94 | alt,
95 | onSelectLogo,
96 | onRemoveLogo,
97 | };
98 | }
99 |
--------------------------------------------------------------------------------
/src/Brands/crazy-domains/step-loader-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/includes/RestApi/SiteImagesController.php:
--------------------------------------------------------------------------------
1 | namespace,
40 | $this->rest_base,
41 | array(
42 | array(
43 | 'methods' => \WP_REST_Server::READABLE,
44 | 'callback' => array( $this, 'get_images' ),
45 | 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ),
46 | 'args' => $this->get_request_params(),
47 | ),
48 | )
49 | );
50 | }
51 |
52 | /**
53 | * Query Hiive Unsplash worker for images.
54 | *
55 | * @param \WP_REST_Request $request Request model.
56 | *
57 | * @return \WP_REST_Response|\WP_Error
58 | */
59 | public function get_images( \WP_REST_Request $request ) {
60 | // Define the required request and response args.
61 | $request_args = array(
62 | 'query' => $request->get_param( 'siteType' ),
63 | 'per_page' => $this->results_per_page,
64 | );
65 | $response_args = array( 'id', 'width', 'height', 'links', 'description', 'alt_description', 'urls' );
66 |
67 | // Request the Hiive Unsplash worker for images.
68 | $payload = $this->get( '/workers/unsplash/search/photos', $request_args );
69 | if ( \is_wp_error( $payload ) ) {
70 | return $payload;
71 | }
72 |
73 | // Filter out unnecessary keys from the results.
74 | $results = json_decode( $payload, true )['results'];
75 | foreach ( $results as $index => $result ) {
76 | $results[ $index ] = array_filter(
77 | $result,
78 | function ( $key ) use ( $response_args ) {
79 | return in_array( $key, $response_args );
80 | },
81 | ARRAY_FILTER_USE_KEY
82 | );
83 | }
84 |
85 | return new \WP_REST_Response(
86 | $results,
87 | 200
88 | );
89 | }
90 |
91 | /**
92 | * Get query params for the route.
93 | *
94 | * @return array
95 | */
96 | public function get_request_params() {
97 | return array(
98 | 'siteType' => array(
99 | 'type' => 'string',
100 | 'required' => true,
101 | 'sanitize_callback' => 'sanitize_text_field',
102 | ),
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------