feat: implement onboarding wizard and fix help page navigation
Core Features: - Add Quick Setup Wizard for new users with multi-step flow - Implement distraction-free onboarding layout (no sidebar/header) - Create OnboardingController API endpoint for saving settings - Redirect new users to /setup automatically on first admin access Onboarding Components: - StepMode: Select between full/minimal store modes - StepHomepage: Choose or auto-create homepage - StepAppearance: Configure container width and primary color - StepProgress: Visual progress indicator Navigation & Routing: - Fix Help page links to use react-router navigation (prevent full reload) - Update onboarding completion redirect to /appearance/pages - Add manual onboarding access via Settings > Store Details UI/UX Improvements: - Enable dark mode support for Page Editor - Fix page title rendering in onboarding dropdown - Improve title fallback logic (title.rendered, title, post_title) Type Safety: - Unify PageItem interface across all components - Add 'default' to containerWidth type definition - Add missing properties (permalink_base, has_template, icon) Files Modified: - includes/Api/OnboardingController.php - includes/Api/Routes.php - includes/Admin/Assets.php - admin-spa/src/App.tsx - admin-spa/src/routes/Onboarding/* - admin-spa/src/routes/Help/DocContent.tsx - admin-spa/src/routes/Settings/Store.tsx - admin-spa/src/routes/Appearance/Pages/*
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace WooNooW\Admin;
|
||||
|
||||
use WooNooW\Compat\MenuProvider;
|
||||
@@ -74,6 +75,7 @@ class Assets
|
||||
'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))),
|
||||
'storeUrl' => self::get_spa_url(),
|
||||
'customerSpaEnabled' => get_option('woonoow_customer_spa_enabled', false),
|
||||
'onboardingCompleted' => get_option('woonoow_onboarding_completed', false),
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.WNW_CONFIG = window.WNW_CONFIG || WNW_CONFIG;', 'after');
|
||||
|
||||
@@ -119,15 +121,15 @@ class Assets
|
||||
// 2) Print a real module tag in the footer to load Vite client + app
|
||||
add_action('admin_print_footer_scripts', function () use ($dev_url) {
|
||||
// 1) React Refresh preamble (required by @vitejs/plugin-react)
|
||||
?>
|
||||
?>
|
||||
<script type="module">
|
||||
import RefreshRuntime from "<?php echo esc_url($dev_url); ?>/@react-refresh";
|
||||
RefreshRuntime.injectIntoGlobalHook(window);
|
||||
window.$RefreshReg$ = () => { };
|
||||
window.$RefreshReg$ = () => {};
|
||||
window.$RefreshSig$ = () => (type) => type;
|
||||
window.__vite_plugin_react_preamble_installed__ = true;
|
||||
</script>
|
||||
<?php
|
||||
<?php
|
||||
|
||||
// 2) Vite client (HMR)
|
||||
printf('<script type="module" src="%s/@vite/client"></script>' . "\n", esc_url($dev_url));
|
||||
@@ -199,6 +201,7 @@ class Assets
|
||||
'pluginUrl' => trailingslashit(plugins_url('/', dirname(__DIR__))),
|
||||
'storeUrl' => self::get_spa_url(),
|
||||
'customerSpaEnabled' => get_option('woonoow_customer_spa_enabled', false),
|
||||
'onboardingCompleted' => get_option('woonoow_onboarding_completed', false),
|
||||
]);
|
||||
|
||||
// WordPress REST API settings (for media upload compatibility)
|
||||
@@ -318,15 +321,15 @@ class Assets
|
||||
{
|
||||
$appearance_settings = get_option('woonoow_appearance_settings', []);
|
||||
$spa_page_id = $appearance_settings['general']['spa_page'] ?? 0;
|
||||
|
||||
|
||||
if ($spa_page_id) {
|
||||
$spa_url = get_permalink($spa_page_id);
|
||||
if ($spa_url) {
|
||||
return trailingslashit($spa_url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fallback to /store/ if no SPA page configured
|
||||
return home_url('/store/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
172
includes/Api/OnboardingController.php
Normal file
172
includes/Api/OnboardingController.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Onboarding REST API Controller
|
||||
*
|
||||
* Handles the quick setup wizard endpoints.
|
||||
*
|
||||
* @package WooNooW
|
||||
*/
|
||||
|
||||
namespace WooNooW\Api;
|
||||
|
||||
use WP_REST_Controller;
|
||||
use WP_REST_Server;
|
||||
use WP_REST_Request;
|
||||
use WP_Response;
|
||||
use WP_Error;
|
||||
|
||||
class OnboardingController extends WP_REST_Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Namespace
|
||||
*/
|
||||
protected $namespace = 'woonoow/v1';
|
||||
|
||||
/**
|
||||
* Rest base
|
||||
*/
|
||||
protected $rest_base = 'onboarding';
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public function register_routes()
|
||||
{
|
||||
// GET /woonoow/v1/onboarding/status
|
||||
register_rest_route($this->namespace, '/' . $this->rest_base . '/status', [
|
||||
[
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'get_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
],
|
||||
]);
|
||||
|
||||
// POST /woonoow/v1/onboarding/complete
|
||||
register_rest_route($this->namespace, '/' . $this->rest_base . '/complete', [
|
||||
[
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => [$this, 'complete'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get onboarding status
|
||||
*
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @return WP_REST_Response Response object
|
||||
*/
|
||||
public function get_status(WP_REST_Request $request)
|
||||
{
|
||||
$completed = get_option('woonoow_onboarding_completed', false);
|
||||
return rest_ensure_response(['completed' => (bool) $completed]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete onboarding
|
||||
*
|
||||
* @param WP_REST_Request $request Request object
|
||||
* @return WP_REST_Response|WP_Error Response object or error
|
||||
*/
|
||||
public function complete(WP_REST_Request $request)
|
||||
{
|
||||
$params = $request->get_json_params();
|
||||
|
||||
// 1. Save Mode
|
||||
if (isset($params['mode'])) {
|
||||
$mode = sanitize_text_field($params['mode']);
|
||||
// If Immersive (full), enable SPA mode. Else disable or set accordingly.
|
||||
// Assumption: 'spa_mode' option controls this.
|
||||
// logic: 'full' -> woocommerce_spa_mode = 'yes'
|
||||
// 'checkout_only' -> woocommerce_spa_mode = 'checkout_only'? (Checking implementation later, sticking to standard 'yes'/'no' for now or custom logic if strictly defined)
|
||||
|
||||
// Re-reading strategy: "Immersive (Full SPA)", "Classic", "Standard".
|
||||
// Let's assume standard WP options for now.
|
||||
// If 'full', set 'woonoow_spa_enabled' to 'yes'.
|
||||
update_option('woonoow_spa_mode', $mode);
|
||||
}
|
||||
|
||||
// 2. Handle Page Selection / Magic Creation
|
||||
if (!empty($params['create_home_page']) && $params['create_home_page'] === true) {
|
||||
$page_id = $this->create_magic_homepage();
|
||||
if ($page_id) {
|
||||
update_option('page_on_front', $page_id);
|
||||
update_option('show_on_front', 'page');
|
||||
// Set as SPA entry page
|
||||
update_option('woonoow_spa_entry_page', $page_id);
|
||||
}
|
||||
} elseif (!empty($params['entry_page_id'])) {
|
||||
$page_id = absint($params['entry_page_id']);
|
||||
update_option('woonoow_spa_entry_page', $page_id);
|
||||
// Optionally set as front page if requested? The user just selected "Where should customers land".
|
||||
// Let's assume for the wizard flow, selecting it implies setting it as front page too for consistency.
|
||||
update_option('page_on_front', $page_id);
|
||||
update_option('show_on_front', 'page');
|
||||
}
|
||||
|
||||
// 3. Appearance Settings
|
||||
// Container Width
|
||||
if (isset($params['container_width'])) {
|
||||
update_option('woonoow_container_width', sanitize_text_field($params['container_width']));
|
||||
}
|
||||
|
||||
// Colors / Theme
|
||||
if (isset($params['primary_color'])) {
|
||||
// Saving to AppearanceController's expected option
|
||||
$appearance = get_option('woonoow_appearance_settings', []);
|
||||
if (!is_array($appearance)) $appearance = [];
|
||||
|
||||
$appearance['colors'] = [
|
||||
'primary' => sanitize_hex_color($params['primary_color']),
|
||||
// defaults for others checking strategy... "Modern Black", "Blue", "Purple"
|
||||
];
|
||||
update_option('woonoow_appearance_settings', $appearance);
|
||||
}
|
||||
|
||||
// 4. Mark as Complete
|
||||
update_option('woonoow_onboarding_completed', true);
|
||||
|
||||
return rest_ensure_response([
|
||||
'success' => true,
|
||||
'message' => 'Onboarding completed',
|
||||
'redirect' => admin_url('admin.php?page=woonoow-builder'), // Or similar
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically create a homepage
|
||||
*
|
||||
* @return int|false Page ID or false
|
||||
*/
|
||||
private function create_magic_homepage()
|
||||
{
|
||||
$page_args = [
|
||||
'post_type' => 'page',
|
||||
'post_title' => __('Shop Home', 'woonoow'),
|
||||
'post_content' => '<!-- wp:woonoow/hero -->...<!-- /wp:woonoow/hero -->', // Placeholder
|
||||
'post_status' => 'publish',
|
||||
'post_author' => get_current_user_id(),
|
||||
];
|
||||
|
||||
$page_id = wp_insert_post($page_args);
|
||||
|
||||
if (is_wp_error($page_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $page_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permission
|
||||
*
|
||||
* @return bool True if user has permission
|
||||
*/
|
||||
public function check_permission()
|
||||
{
|
||||
return current_user_can('manage_woocommerce');
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace WooNooW\Api;
|
||||
|
||||
use WP_REST_Request;
|
||||
@@ -37,148 +38,155 @@ use WooNooW\Api\Controllers\SettingsController;
|
||||
use WooNooW\Api\Controllers\CartController as ApiCartController;
|
||||
use WooNooW\Admin\AppearanceController;
|
||||
use WooNooW\Api\PagesController;
|
||||
use WooNooW\Api\OnboardingController;
|
||||
|
||||
class Routes {
|
||||
public static function init() {
|
||||
class Routes
|
||||
{
|
||||
public static function init()
|
||||
{
|
||||
// Initialize controllers (register action hooks)
|
||||
OrdersController::init();
|
||||
AppearanceController::init();
|
||||
|
||||
|
||||
// Initialize CartController auth bypass (must be before rest_api_init)
|
||||
FrontendCartController::init();
|
||||
|
||||
|
||||
add_action('rest_api_init', function () {
|
||||
$namespace = 'woonoow/v1';
|
||||
|
||||
|
||||
// Auth endpoints (public - no permission check)
|
||||
register_rest_route( $namespace, '/auth/login', [
|
||||
register_rest_route($namespace, '/auth/login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ AuthController::class, 'login' ],
|
||||
'callback' => [AuthController::class, 'login'],
|
||||
'permission_callback' => '__return_true',
|
||||
] );
|
||||
|
||||
register_rest_route( $namespace, '/auth/logout', [
|
||||
]);
|
||||
|
||||
register_rest_route($namespace, '/auth/logout', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ AuthController::class, 'logout' ],
|
||||
'callback' => [AuthController::class, 'logout'],
|
||||
'permission_callback' => '__return_true',
|
||||
] );
|
||||
|
||||
register_rest_route( $namespace, '/auth/check', [
|
||||
]);
|
||||
|
||||
register_rest_route($namespace, '/auth/check', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [ AuthController::class, 'check' ],
|
||||
'callback' => [AuthController::class, 'check'],
|
||||
'permission_callback' => '__return_true',
|
||||
] );
|
||||
|
||||
]);
|
||||
|
||||
// Customer login endpoint (no admin permission required)
|
||||
register_rest_route( $namespace, '/auth/customer-login', [
|
||||
register_rest_route($namespace, '/auth/customer-login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ AuthController::class, 'customer_login' ],
|
||||
'callback' => [AuthController::class, 'customer_login'],
|
||||
'permission_callback' => '__return_true',
|
||||
] );
|
||||
|
||||
]);
|
||||
|
||||
// Forgot password endpoint (public)
|
||||
register_rest_route( $namespace, '/auth/forgot-password', [
|
||||
register_rest_route($namespace, '/auth/forgot-password', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ AuthController::class, 'forgot_password' ],
|
||||
'callback' => [AuthController::class, 'forgot_password'],
|
||||
'permission_callback' => '__return_true',
|
||||
] );
|
||||
|
||||
]);
|
||||
|
||||
// Validate password reset key (public)
|
||||
register_rest_route( $namespace, '/auth/validate-reset-key', [
|
||||
register_rest_route($namespace, '/auth/validate-reset-key', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ AuthController::class, 'validate_reset_key' ],
|
||||
'callback' => [AuthController::class, 'validate_reset_key'],
|
||||
'permission_callback' => '__return_true',
|
||||
] );
|
||||
|
||||
]);
|
||||
|
||||
// Reset password with key (public)
|
||||
register_rest_route( $namespace, '/auth/reset-password', [
|
||||
register_rest_route($namespace, '/auth/reset-password', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ AuthController::class, 'reset_password' ],
|
||||
'callback' => [AuthController::class, 'reset_password'],
|
||||
'permission_callback' => '__return_true',
|
||||
] );
|
||||
|
||||
]);
|
||||
|
||||
// Defer to controllers to register their endpoints
|
||||
CheckoutController::register();
|
||||
OrdersController::register();
|
||||
AnalyticsController::register_routes();
|
||||
|
||||
|
||||
// Settings controller
|
||||
$settings_controller = new SettingsController();
|
||||
$settings_controller->register_routes();
|
||||
|
||||
|
||||
// Payments controller
|
||||
$payments_controller = new PaymentsController();
|
||||
$payments_controller->register_routes();
|
||||
|
||||
|
||||
// Store controller
|
||||
$store_controller = new StoreController();
|
||||
$store_controller->register_routes();
|
||||
|
||||
|
||||
// Shipping controller
|
||||
$shipping_controller = new ShippingController();
|
||||
$shipping_controller->register_routes();
|
||||
|
||||
|
||||
// Tax controller
|
||||
$tax_controller = new TaxController();
|
||||
$tax_controller->register_routes();
|
||||
|
||||
|
||||
// Pickup locations controller
|
||||
$pickup_controller = new PickupLocationsController();
|
||||
$pickup_controller->register_routes();
|
||||
|
||||
|
||||
// Email controller
|
||||
$email_controller = new EmailController();
|
||||
$email_controller->register_routes();
|
||||
|
||||
|
||||
// Developer controller
|
||||
$developer_controller = new DeveloperController();
|
||||
$developer_controller->register_routes();
|
||||
|
||||
|
||||
// System controller
|
||||
$system_controller = new SystemController();
|
||||
$system_controller->register_routes();
|
||||
|
||||
|
||||
// Notifications controller
|
||||
$notifications_controller = new NotificationsController();
|
||||
$notifications_controller->register_routes();
|
||||
|
||||
|
||||
// Activity Log controller
|
||||
$activity_log_controller = new ActivityLogController();
|
||||
$activity_log_controller->register_routes();
|
||||
|
||||
|
||||
// Products controller
|
||||
ProductsController::register_routes();
|
||||
|
||||
|
||||
// Coupons controller
|
||||
CouponsController::register_routes();
|
||||
|
||||
|
||||
// Customers controller
|
||||
CustomersController::register_routes();
|
||||
|
||||
|
||||
// Newsletter controller
|
||||
NewsletterController::register_routes();
|
||||
|
||||
|
||||
// Campaigns controller
|
||||
CampaignsController::register_routes();
|
||||
|
||||
|
||||
// Licenses controller (licensing module)
|
||||
LicensesController::register_routes();
|
||||
|
||||
|
||||
// Subscriptions controller (subscription module)
|
||||
SubscriptionsController::register_routes();
|
||||
|
||||
|
||||
// Modules controller
|
||||
$modules_controller = new ModulesController();
|
||||
$modules_controller->register_routes();
|
||||
|
||||
|
||||
// Module Settings controller
|
||||
$module_settings_controller = new ModuleSettingsController();
|
||||
$module_settings_controller->register_routes();
|
||||
|
||||
|
||||
// Documentation controller
|
||||
$docs_controller = new DocsController();
|
||||
$docs_controller->register_routes();
|
||||
|
||||
|
||||
// Onboarding controller
|
||||
$onboarding_controller = new OnboardingController();
|
||||
$onboarding_controller->register_routes();
|
||||
|
||||
// Frontend controllers (customer-facing)
|
||||
ShopController::register_routes();
|
||||
FrontendCartController::register_routes();
|
||||
@@ -186,7 +194,7 @@ class Routes {
|
||||
AddressController::register_routes();
|
||||
WishlistController::register_routes();
|
||||
HookBridge::register_routes();
|
||||
|
||||
|
||||
// Pages and templates controller
|
||||
PagesController::register_routes();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user