diff --git a/customer-spa/src/App.tsx b/customer-spa/src/App.tsx
index e27240d..9ce4fea 100644
--- a/customer-spa/src/App.tsx
+++ b/customer-spa/src/App.tsx
@@ -16,6 +16,7 @@ import ThankYou from './pages/ThankYou';
import Account from './pages/Account';
import Wishlist from './pages/Wishlist';
import Login from './pages/Login';
+import ForgotPassword from './pages/ForgotPassword';
// Create QueryClient instance
const queryClient = new QueryClient({
@@ -85,8 +86,9 @@ function AppRoutes() {
{/* Wishlist - Public route accessible to guests */}
} />
- {/* Login */}
+ {/* Login & Auth */}
} />
+ } />
{/* My Account */}
} />
diff --git a/customer-spa/src/pages/ForgotPassword/index.tsx b/customer-spa/src/pages/ForgotPassword/index.tsx
new file mode 100644
index 0000000..6f6242a
--- /dev/null
+++ b/customer-spa/src/pages/ForgotPassword/index.tsx
@@ -0,0 +1,161 @@
+import React, { useState } from 'react';
+import { Link } from 'react-router-dom';
+import { toast } from 'sonner';
+import Container from '@/components/Layout/Container';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { KeyRound, ArrowLeft, Mail, CheckCircle } from 'lucide-react';
+
+export default function ForgotPassword() {
+ const [email, setEmail] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [isSuccess, setIsSuccess] = useState(false);
+ const [error, setError] = useState('');
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError('');
+ setIsLoading(true);
+
+ try {
+ const apiRoot = (window as any).woonoowCustomer?.apiRoot || '/wp-json/woonoow/v1';
+ const nonce = (window as any).woonoowCustomer?.nonce || '';
+
+ const response = await fetch(`${apiRoot}/auth/forgot-password`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-WP-Nonce': nonce,
+ },
+ credentials: 'include',
+ body: JSON.stringify({ email }),
+ });
+
+ const data = await response.json();
+
+ if (data.success) {
+ setIsSuccess(true);
+ toast.success('Password reset email sent!');
+ } else {
+ setError(data.message || 'Failed to send reset email');
+ }
+ } catch (err: any) {
+ setError(err.message || 'An error occurred. Please try again.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Success state
+ if (isSuccess) {
+ return (
+
+
+
+
+
+
+
+
Check Your Email
+
+ We've sent a password reset link to {email}.
+ Please check your inbox and click the link to reset your password.
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {/* Back link */}
+
+
+ Back to login
+
+
+
+ {/* Header */}
+
+
+
+
+
Forgot Password?
+
+ Enter your email and we'll send you a link to reset your password.
+
+
+
+ {/* Error message */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Form */}
+
+
+ {/* Footer */}
+
+ Remember your password?{' '}
+
+ Sign in
+
+
+
+
+
+
+ );
+}
diff --git a/customer-spa/src/pages/Login/index.tsx b/customer-spa/src/pages/Login/index.tsx
index 413852b..07a1f6c 100644
--- a/customer-spa/src/pages/Login/index.tsx
+++ b/customer-spa/src/pages/Login/index.tsx
@@ -187,12 +187,12 @@ export default function Login() {
{/* Footer links */}
diff --git a/customer-spa/src/pages/ThankYou/index.tsx b/customer-spa/src/pages/ThankYou/index.tsx
index 0c36750..0f8e515 100644
--- a/customer-spa/src/pages/ThankYou/index.tsx
+++ b/customer-spa/src/pages/ThankYou/index.tsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { useParams, Link, useSearchParams } from 'react-router-dom';
import { useThankYouSettings } from '@/hooks/useAppearanceSettings';
import Container from '@/components/Layout/Container';
-import { CheckCircle, ShoppingBag, Package, Truck, User } from 'lucide-react';
+import { CheckCircle, ShoppingBag, Package, Truck, User, LogIn } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { formatPrice } from '@/lib/currency';
import { apiClient } from '@/lib/api/client';
@@ -196,13 +196,20 @@ export default function ThankYou() {
)}
- {isLoggedIn && (
+ {isLoggedIn ? (
+ ) : (
+
+
+
)}
@@ -451,13 +458,20 @@ export default function ThankYou() {
)}
- {isLoggedIn && (
+ {isLoggedIn ? (
+ ) : (
+
+
+
)}
diff --git a/includes/Api/AuthController.php b/includes/Api/AuthController.php
index 849cfc9..18699a8 100644
--- a/includes/Api/AuthController.php
+++ b/includes/Api/AuthController.php
@@ -186,4 +186,48 @@ class AuthController {
],
], 200 );
}
+
+ /**
+ * Forgot password endpoint - sends password reset email
+ *
+ * @param WP_REST_Request $request Request object
+ * @return WP_REST_Response Response object
+ */
+ public static function forgot_password( WP_REST_Request $request ): WP_REST_Response {
+ $email = sanitize_email( $request->get_param( 'email' ) );
+
+ if ( empty( $email ) || ! is_email( $email ) ) {
+ return new WP_REST_Response( [
+ 'success' => false,
+ 'message' => __( 'Please enter a valid email address', 'woonoow' ),
+ ], 400 );
+ }
+
+ // Check if user exists
+ $user = get_user_by( 'email', $email );
+
+ if ( ! $user ) {
+ // For security, don't reveal if email exists or not
+ // But still return success to prevent email enumeration attacks
+ return new WP_REST_Response( [
+ 'success' => true,
+ 'message' => __( 'If an account exists with this email, you will receive a password reset link.', 'woonoow' ),
+ ], 200 );
+ }
+
+ // Use WordPress's built-in password reset functionality
+ $result = retrieve_password( $user->user_login );
+
+ if ( is_wp_error( $result ) ) {
+ return new WP_REST_Response( [
+ 'success' => false,
+ 'message' => __( 'Failed to send password reset email. Please try again.', 'woonoow' ),
+ ], 500 );
+ }
+
+ return new WP_REST_Response( [
+ 'success' => true,
+ 'message' => __( 'Password reset email sent! Please check your inbox.', 'woonoow' ),
+ ], 200 );
+ }
}
diff --git a/includes/Api/CheckoutController.php b/includes/Api/CheckoutController.php
index 00ac741..ae6fbb3 100644
--- a/includes/Api/CheckoutController.php
+++ b/includes/Api/CheckoutController.php
@@ -300,6 +300,10 @@ class CheckoutController {
// The real password is already set via wp_insert_user
update_user_meta($new_user_id, '_woonoow_temp_password', $password);
+ // AUTO-LOGIN: Set authentication cookie so user is logged in after page reload
+ wp_set_auth_cookie($new_user_id, true);
+ wp_set_current_user($new_user_id);
+
// Set WooCommerce customer billing data
$customer = new \WC_Customer($new_user_id);
diff --git a/includes/Api/Routes.php b/includes/Api/Routes.php
index 05d0105..ce120d8 100644
--- a/includes/Api/Routes.php
+++ b/includes/Api/Routes.php
@@ -72,6 +72,13 @@ class Routes {
'permission_callback' => '__return_true',
] );
+ // Forgot password endpoint (public)
+ register_rest_route( $namespace, '/auth/forgot-password', [
+ 'methods' => 'POST',
+ 'callback' => [ AuthController::class, 'forgot_password' ],
+ 'permission_callback' => '__return_true',
+ ] );
+
// Defer to controllers to register their endpoints
CheckoutController::register();
OrdersController::register();