From 78d7bc11613e159b572bbfa3e6a5c16f56ce41e7 Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Thu, 1 Jan 2026 17:36:40 +0700 Subject: [PATCH] fix: auto-login after checkout, ThankYou guest buttons, forgot password page 1. Auto-login after checkout: - Added wp_set_auth_cookie() and wp_set_current_user() in CheckoutController - Auto-registered users are now logged in when thank-you page loads 2. ThankYou page guest buttons: - Added 'Login / Create Account' button for guests - Shows for both receipt and basic templates - No more dead-end after placing order as guest 3. Forgot password flow: - Created ForgotPassword page component (/forgot-password route) - Added forgot_password API endpoint in AuthController - Uses WordPress retrieve_password() for reset email - Replaced wp-login.php link in Login page --- customer-spa/src/App.tsx | 4 +- .../src/pages/ForgotPassword/index.tsx | 161 ++++++++++++++++++ customer-spa/src/pages/Login/index.tsx | 6 +- customer-spa/src/pages/ThankYou/index.tsx | 20 ++- includes/Api/AuthController.php | 44 +++++ includes/Api/CheckoutController.php | 4 + includes/Api/Routes.php | 7 + 7 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 customer-spa/src/pages/ForgotPassword/index.tsx 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 */} +
+
+ +
+ + setEmail(e.target.value)} + placeholder="Enter your email" + required + autoComplete="email" + disabled={isLoading} + className="pl-10" + /> +
+
+ + +
+ + {/* 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();