diff --git a/adventure-rental-app/src/AuthPage.jsx b/adventure-rental-app/src/AuthPage.jsx
index 9c1a04c..26b1c9a 100644
--- a/adventure-rental-app/src/AuthPage.jsx
+++ b/adventure-rental-app/src/AuthPage.jsx
@@ -2,23 +2,31 @@ import React, { useState, useCallback } from 'react';
import LoginForm from './LoginForm';
import SignupForm from './SignupForm';
import OtpForm from './OtpForm';
+import ForgotPasswordForm from './ForgotPasswordForm';
+import ResetPasswordForm from './ResetPasswordForm';
const AuthPage = () => {
- const [view, setView] = useState('login'); // 'login', 'signup', or 'otp'
+ const [view, setView] = useState('login'); // 'login', 'signup', 'otp', 'forgotPassword', 'resetPassword'
const showSignup = useCallback(() => setView('signup'), []);
const showLogin = useCallback(() => setView('login'), []);
const showOtp = useCallback(() => setView('otp'), []);
+ const showForgotPassword = useCallback(() => setView('forgotPassword'), []);
+ const showResetPassword = useCallback(() => setView('resetPassword'), []);
const renderForm = () => {
switch (view) {
case 'signup':
return ;
case 'otp':
- return ;
+ return ;
+ case 'forgotPassword':
+ return ;
+ case 'resetPassword':
+ return ;
case 'login':
default:
- return ;
+ return ;
}
};
diff --git a/adventure-rental-app/src/ForgotPasswordForm.jsx b/adventure-rental-app/src/ForgotPasswordForm.jsx
new file mode 100644
index 0000000..c9b2a24
--- /dev/null
+++ b/adventure-rental-app/src/ForgotPasswordForm.jsx
@@ -0,0 +1,117 @@
+import React, { useState, useCallback } from 'react';
+import axios from 'axios';
+
+const ForgotPasswordForm = ({ showLogin, showReset }) => {
+ const [mode, setMode] = useState('email'); // 'email' or 'whatsapp'
+ const [identifier, setIdentifier] = useState('');
+ const [error, setError] = useState('');
+ const [success, setSuccess] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [emailError, setEmailError] = useState('');
+
+ const validateEmail = (email) => {
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return regex.test(email);
+ };
+
+ const handleIdentifierChange = (e) => {
+ let value = e.target.value;
+ if (mode === 'email') {
+ if (!validateEmail(value) && value.length > 0) {
+ setEmailError('Please enter a valid email address.');
+ } else {
+ setEmailError('');
+ }
+ } else {
+ setEmailError('');
+ if (value.startsWith('0')) {
+ value = value.substring(1);
+ }
+ value = value.replace(/[^0-9]/g, '');
+ }
+ setIdentifier(value);
+ };
+
+ const handleModeChange = useCallback((newMode) => {
+ setMode(newMode);
+ setIdentifier('');
+ setEmailError('');
+ setError('');
+ setSuccess('');
+ }, []);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (mode === 'email' && emailError) {
+ setError('Please fix the errors before submitting.');
+ return;
+ }
+ setLoading(true);
+ setError('');
+ setSuccess('');
+
+ const finalIdentifier = mode === 'whatsapp' ? `62${identifier}` : identifier;
+
+ try {
+ await axios.post('https://api.karyamanswasta.my.id/webhook/forgot-password/adventure', { identifier: finalIdentifier });
+ setSuccess('Password reset link sent. Please check your email/WhatsApp.');
+ // showReset(); // You might want to navigate to reset password form after a delay
+ } catch (err) {
+ setError(err.response?.data?.message || 'Failed to send reset link.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const isFormValid = identifier.trim() !== '' && !emailError;
+
+ return (
+ <>
+
+
Forgot Password
+
Enter your email or WhatsApp to reset
+
+
+
+
+
+
+ >
+ );
+};
+
+export default ForgotPasswordForm;
\ No newline at end of file
diff --git a/adventure-rental-app/src/LoginForm.jsx b/adventure-rental-app/src/LoginForm.jsx
index 0a0e3a6..1084dc7 100644
--- a/adventure-rental-app/src/LoginForm.jsx
+++ b/adventure-rental-app/src/LoginForm.jsx
@@ -13,7 +13,7 @@ const FlashlightOffIcon = () => (
);
-const LoginForm = ({ toggleForm, onSuccess }) => {
+const LoginForm = ({ toggleForm, onSuccess, showForgotPassword }) => {
const [loginMode, setLoginMode] = useState('email'); // 'email' or 'whatsapp'
const [identifier, setIdentifier] = useState('');
const [password, setPassword] = useState('');
@@ -178,9 +178,9 @@ const LoginForm = ({ toggleForm, onSuccess }) => {
-
+
+
diff --git a/adventure-rental-app/src/OtpForm.jsx b/adventure-rental-app/src/OtpForm.jsx
index 1c982d3..bebed90 100644
--- a/adventure-rental-app/src/OtpForm.jsx
+++ b/adventure-rental-app/src/OtpForm.jsx
@@ -1,25 +1,64 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import axios from 'axios';
-const OtpForm = () => {
+const OtpForm = ({ onSuccess }) => {
const [emailOtp, setEmailOtp] = useState('');
const [whatsappOtp, setWhatsappOtp] = useState('');
const [error, setError] = useState('');
+ const [success, setSuccess] = useState('');
const [loading, setLoading] = useState(false);
+ const [timer, setTimer] = useState(60); // 1 minute in seconds
+
+ useEffect(() => {
+ if (timer > 0) {
+ const interval = setInterval(() => {
+ setTimer(prev => prev - 1);
+ }, 1000);
+ return () => clearInterval(interval);
+ }
+ }, [timer]);
+
+ const formatTime = (seconds) => {
+ const minutes = Math.floor(seconds / 60);
+ const remainingSeconds = seconds % 60;
+ return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
+ };
+
+ const handleOtpChange = (setter) => (e) => {
+ const value = e.target.value.replace(/[^0-9]/g, '');
+ setter(value);
+ };
+
+ const handleResend = async () => {
+ setLoading(true);
+ setError('');
+ setSuccess('');
+ try {
+ await axios.post('https://api.karyamanswasta.my.id/webhook/otp-request/adventure');
+ setSuccess('A new OTP has been sent.');
+ setTimer(60); // Reset timer
+ } catch (err) {
+ setError('Failed to resend OTP.');
+ } finally {
+ setLoading(false);
+ }
+ };
const handleVerify = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
+ setSuccess('');
try {
- // Assuming you have a webhook for OTP verification
- await axios.post('https://api.karyamanswasta.my.id/webhook/verify-otp/adventure', {
+ await axios.post('https://api.karyamanswasta.my.id/webhook/otp-verify/adventure', {
emailOtp,
whatsappOtp,
});
- // On success, you would typically redirect to the main app
- console.log('OTP verification successful');
+ setSuccess('Account verified successfully! Redirecting to login...');
+ setTimeout(() => {
+ onSuccess();
+ }, 2000);
} catch (err) {
setError(err.response?.data?.message || 'OTP verification failed.');
} finally {
@@ -27,41 +66,57 @@ const OtpForm = () => {
}
};
+ const isFormValid = emailOtp.length >= 6 && whatsappOtp.length >= 6;
+
return (
<>
Verify Your Account
Enter the OTP sent to your email and WhatsApp
+
{formatTime(timer)}
>
);
diff --git a/adventure-rental-app/src/ResetPasswordForm.jsx b/adventure-rental-app/src/ResetPasswordForm.jsx
new file mode 100644
index 0000000..13c557d
--- /dev/null
+++ b/adventure-rental-app/src/ResetPasswordForm.jsx
@@ -0,0 +1,63 @@
+import React, { useState } from 'react';
+import axios from 'axios';
+
+const ResetPasswordForm = ({ showLogin }) => {
+ const [token, setToken] = useState('');
+ const [newPassword, setNewPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [error, setError] = useState('');
+ const [success, setSuccess] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (newPassword !== confirmPassword) {
+ setError("Passwords don't match.");
+ return;
+ }
+ setLoading(true);
+ setError('');
+ setSuccess('');
+
+ try {
+ // Assuming you have a webhook for resetting the password
+ await axios.post('https://api.karyamanswasta.my.id/webhook/forgot-password/adventure', { token, newPassword });
+ setSuccess('Password has been sent successfully!');
+ setTimeout(() => {
+ showLogin();
+ }, 2000);
+ } catch (err) {
+ setError(err.response?.data?.message || 'Failed to reset password.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <>
+
+
Reset Password
+
Enter the token from your email/WhatsApp and a new password.
+
+
+ >
+ );
+};
+
+export default ResetPasswordForm;
\ No newline at end of file
diff --git a/adventure-rental-app/tailwind.config.js b/adventure-rental-app/tailwind.config.js
index e442df5..83f7b3c 100644
--- a/adventure-rental-app/tailwind.config.js
+++ b/adventure-rental-app/tailwind.config.js
@@ -17,7 +17,7 @@ export default {
sans: ['Poppins', 'sans-serif'],
},
backgroundImage: {
- 'login-bg': "url('https://images.unsplash.com/photo-1515444744559-7be63e1600de?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
+ 'login-bg': "url('https://images.unsplash.com/photo-1723067950251-af96d68b9c1e?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D')",
}
},
},