diff --git a/admin-spa/src/App.tsx b/admin-spa/src/App.tsx
index 1226bb1..0041cfd 100644
--- a/admin-spa/src/App.tsx
+++ b/admin-spa/src/App.tsx
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
-import { HashRouter, Routes, Route, NavLink, useLocation, useParams, Navigate } from 'react-router-dom';
+import { HashRouter, Routes, Route, NavLink, useLocation, useParams, Navigate, Link } from 'react-router-dom';
+import { Login } from './routes/Login';
import Dashboard from '@/routes/Dashboard';
import DashboardRevenue from '@/routes/Dashboard/Revenue';
import DashboardOrders from '@/routes/Dashboard/Orders';
@@ -19,7 +20,6 @@ import ProductAttributes from '@/routes/Products/Attributes';
import CouponsIndex from '@/routes/Coupons';
import CouponNew from '@/routes/Coupons/New';
import CustomersIndex from '@/routes/Customers';
-import { Login } from '@/routes/Login';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { LayoutDashboard, ReceiptText, Package, Tag, Users, Settings as SettingsIcon, Maximize2, Minimize2, Loader2 } from 'lucide-react';
import { Toaster } from 'sonner';
@@ -441,7 +441,7 @@ function AuthWrapper() {
});
// In standalone mode, trust the initial PHP auth check
- // No need for additional API call since PHP already verified the session
+ // PHP uses wp_signon which sets proper WordPress cookies
if (window.WNW_CONFIG?.standaloneMode) {
setIsAuthenticated(window.WNW_CONFIG.isAuthenticated);
setIsChecking(false);
@@ -464,7 +464,7 @@ function AuthWrapper() {
}
if (location.pathname === '/login' && isAuthenticated) {
- return ;
+ return ;
}
return (
diff --git a/admin-spa/src/lib/api.ts b/admin-spa/src/lib/api.ts
index 41cc36b..833ef90 100644
--- a/admin-spa/src/lib/api.ts
+++ b/admin-spa/src/lib/api.ts
@@ -9,7 +9,7 @@ export const api = {
if (!headers.has('Accept')) headers.set('Accept', 'application/json');
if (options.body && !headers.has('Content-Type')) headers.set('Content-Type', 'application/json');
- const res = await fetch(url, { credentials: 'same-origin', ...options, headers });
+ const res = await fetch(url, { credentials: 'include', ...options, headers });
if (!res.ok) {
let responseData: any = null;
diff --git a/admin-spa/src/routes/Login.tsx b/admin-spa/src/routes/Login.tsx
index 14cd623..e44e166 100644
--- a/admin-spa/src/routes/Login.tsx
+++ b/admin-spa/src/routes/Login.tsx
@@ -37,6 +37,11 @@ export function Login() {
window.WNW_CONFIG.currentUser = data.user;
window.WNW_CONFIG.nonce = data.nonce;
+ // CRITICAL: Also update WNW_API.nonce for API requests
+ if (window.WNW_API) {
+ window.WNW_API.nonce = data.nonce;
+ }
+
// Redirect to dashboard (no reload needed, auth state is updated)
navigate('/dashboard');
} else {
diff --git a/includes/Api/AuthController.php b/includes/Api/AuthController.php
index 72ef2dd..b2a5cff 100644
--- a/includes/Api/AuthController.php
+++ b/includes/Api/AuthController.php
@@ -31,8 +31,14 @@ class AuthController {
], 400 );
}
- // Authenticate user
- $user = wp_authenticate( $username, $password );
+ // Use wp_signon to properly authenticate and set cookies (same as wp-login.php)
+ $credentials = [
+ 'user_login' => $username,
+ 'user_password' => $password,
+ 'remember' => true,
+ ];
+
+ $user = wp_signon( $credentials, false );
if ( is_wp_error( $user ) ) {
return new WP_REST_Response( [
@@ -43,15 +49,14 @@ class AuthController {
// Check if user has WooCommerce permissions
if ( ! user_can( $user, 'manage_woocommerce' ) ) {
+ // Logout if no permission
+ wp_logout();
return new WP_REST_Response( [
'success' => false,
'message' => __( 'You do not have permission to access this area', 'woonoow' ),
], 403 );
}
- // Set auth cookie
- wp_set_auth_cookie( $user->ID, true );
-
// Return user data and new nonce
return new WP_REST_Response( [
'success' => true,