update transaction detail

This commit is contained in:
wayanrivan
2025-04-28 15:51:05 +07:00
parent 99687023a3
commit 1f1679c185
4 changed files with 166 additions and 162 deletions

View File

@ -1,95 +1,50 @@
import { useState, useContext, useEffect, useCallback, MouseEvent } from 'react'; import { useState, useContext, useCallback, MouseEvent } from 'react';
import { AccountUserProfileContext } from '../hooks'; import { AccountUserProfileContext } from '../hooks';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useAuthContext } from '@/auth'; import { useAuthContext } from '@/auth';
import { KeenIcon } from '@/components'; import { KeenIcon } from '@/components';
import clsx from 'clsx'; import clsx from 'clsx';
type PasswordType = 'password' | 'retype_password' | 'current_password'; type PasswordType = 'password';
const PinCode = () => { const PinCode = () => {
const { setPassword } = useContext(AccountUserProfileContext); const { setPincode } = useContext(AccountUserProfileContext);
const { getUser } = useAuthContext(); const { getUser } = useAuthContext();
const [newPassword, setNewPassword] = useState(''); const [newPincode, setNewPincode] = useState('');
const [currentPassword, setCurrentPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [messagePassword, setMessagePassword] = useState(true);
const [showPassword, setShowPassword] = useState({ const [showPassword, setShowPassword] = useState({
current_password: false,
password: false, password: false,
retype_password: false
}); });
const [passwordErrors, setPasswordErrors] = useState<string[]>([]);
const validatePassword = (password: string) => {
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
const hasCapitalLetter = /[A-Z]/.test(password);
const hasNumber = /[0-9]/.test(password);
return hasSpecialChar && hasCapitalLetter && hasNumber;
};
useEffect(() => {
const passwordsMatch = newPassword === confirmPassword;
const isPasswordValid = validatePassword(newPassword);
setMessagePassword(passwordsMatch && isPasswordValid);
const errors: string[] = [];
if (newPassword) {
if (!/[A-Z]/.test(newPassword)) {
errors.push('Password must contain at least one capital letter');
}
if (!/[0-9]/.test(newPassword)) {
errors.push('Password must contain at least one number');
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(newPassword)) {
errors.push('Password must contain at least one special character');
}
if (newPassword !== confirmPassword) {
errors.push('Passwords do not match');
}
}
setPasswordErrors(errors);
}, [newPassword, confirmPassword]);
const handleResetPassword = useCallback(async () => { const handleResetPassword = useCallback(async () => {
const user: any = await getUser(); const user: any = await getUser();
if (!messagePassword) {
toast.error('Passwords do not match!');
return;
}
setIsSubmitting(true); setIsSubmitting(true);
try { try {
await setPassword({ await setPincode({
current_password: currentPassword, newpincode: newPincode,
password: newPassword,
retype_password: confirmPassword,
username: user.data.username ?? ''
}); });
setNewPassword(''); setNewPincode('');
setConfirmPassword(''); toast.success('Pin code updated successfully');
setCurrentPassword('');
} catch (error: any) { } catch (error: any) {
const errorMessage = const errorMessage =
error?.response?.data?.message || error?.response?.data?.message ||
error?.message || error?.message ||
'An error occurred while resetting the password.'; 'An error occurred while updating the pin code.';
toast.error(errorMessage); toast.error(errorMessage);
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
} }
}, [currentPassword, newPassword, newPassword, messagePassword, getUser]); }, [newPincode, setPincode, getUser]);
const isButtonDisabled = isSubmitting || !newPassword || !confirmPassword || !messagePassword; const isButtonDisabled = isSubmitting || !newPincode;
const togglePassword = useCallback((event: MouseEvent<HTMLButtonElement>, key: string) => { const togglePassword = useCallback((event: MouseEvent<HTMLButtonElement>, key: PasswordType) => {
event.preventDefault(); event.preventDefault();
setShowPassword((prev) => ({ ...prev, [key]: !prev[key as PasswordType] })); setShowPassword((prev) => ({ ...prev, [key]: !prev[key] }));
}, []); }, []);
return ( return (
@ -99,89 +54,28 @@ const PinCode = () => {
</div> </div>
<div className="card-body grid gap-5"> <div className="card-body grid gap-5">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label max-w-56">Current Pin Code</label> <label className="form-label max-w-56">New Pin Code</label>
<div className="input"> <div className="input relative">
<input <input
type={showPassword.current_password ? 'text' : 'password'} className="form-control pr-10" // untuk kasih jarak buat tombol mata
className="form-control"
placeholder="Current Pin Code"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
disabled={isSubmitting}
/>
<button className="btn btn-icon" onClick={(e) => togglePassword(e, 'current_password')}>
<KeenIcon
icon="eye"
className={clsx('text-gray-500', { hidden: showPassword.current_password })}
/>
<KeenIcon
icon="eye-slash"
className={clsx('text-gray-500', { hidden: !showPassword.current_password })}
/>
</button>
</div>
</div>
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label max-w-56">Pin Code</label>
<div className="input">
<input
className="form-control"
placeholder="New Pin Code" placeholder="New Pin Code"
value={newPassword} value={newPincode}
onChange={(e) => setNewPassword(e.target.value)} onChange={(e) => setNewPincode(e.target.value)} // ✅ fix
disabled={isSubmitting} disabled={isSubmitting}
type={showPassword.password ? 'text' : 'password'} type={showPassword.password ? 'text' : 'password'}
/> />
<button className="btn btn-icon" onClick={(e) => togglePassword(e, 'password')}> <button
className="btn btn-icon absolute right-2 top-1/2 transform -translate-y-1/2"
onClick={(e) => togglePassword(e, 'password')}
type="button"
>
<KeenIcon <KeenIcon
icon="eye" icon={showPassword.password ? 'eye-slash' : 'eye'}
className={clsx('text-gray-500', { hidden: showPassword.password })} className="text-gray-500"
/>
<KeenIcon
icon="eye-slash"
className={clsx('text-gray-500', { hidden: !showPassword.password })}
/> />
</button> </button>
</div> </div>
</div> </div>
<div className="mb-2.5">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label max-w-56">Confirm New Pin Code</label>
<div className="input">
<input
type={showPassword.retype_password ? 'text' : 'password'}
className="form-control"
placeholder="Confirm New Pin Code"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={isSubmitting}
/>
<button
className="btn btn-icon"
onClick={(e) => togglePassword(e, 'retype_password')}
>
<KeenIcon
icon="eye"
className={clsx('text-gray-500', { hidden: showPassword.retype_password })}
/>
<KeenIcon
icon="eye-slash"
className={clsx('text-gray-500', { hidden: !showPassword.retype_password })}
/>
</button>
</div>
</div>
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label max-w-56 text-white">Confirm New Pin Code</label>
{passwordErrors.length > 0 && (
<div className="text-xs text-red-500 mt-1 ms-3">
{passwordErrors.map((error, index) => (
<p key={index}>{error}</p>
))}
</div>
)}
</div>
</div>
<div className="flex justify-end"> <div className="flex justify-end">
<button <button

View File

@ -18,9 +18,15 @@ interface Profile {
username: string; username: string;
} }
interface PinCode {
newpincode: string;
}
interface ContextProps { interface ContextProps {
password: Password | null; password: Password | null;
profile: Profile | null; profile: Profile | null;
pincode: PinCode | null;
setPincode: (pincode: PinCode) => Promise<void>;
setPassword: (password: Password) => Promise<void>; setPassword: (password: Password) => Promise<void>;
setProfile: (profile: Profile) => Promise<void>; setProfile: (profile: Profile) => Promise<void>;
} }
@ -28,8 +34,10 @@ interface ContextProps {
const initialProps: ContextProps = { const initialProps: ContextProps = {
profile: null, profile: null,
password: null, password: null,
pincode: null,
setPassword: async () => {}, setPassword: async () => {},
setProfile: async () => {} setProfile: async () => {},
setPincode: async () => {}
}; };
const AccountUserProfileContext = createContext<ContextProps>(initialProps); const AccountUserProfileContext = createContext<ContextProps>(initialProps);
@ -39,25 +47,29 @@ const API_URL = apiConfig.service_dashboard;
const AccountUserProfileContextProvider = ({ children }: { children: React.ReactNode }) => { const AccountUserProfileContextProvider = ({ children }: { children: React.ReactNode }) => {
/* state */ /* state */
const { login } = useAuthContext(); const { login } = useAuthContext();
const [profile, setProfile] = useState<Profile | null>(null); const [profile, setProfileState] = useState<Profile | null>(null);
const [password, setPassword] = useState<Password | null>(null); const [password, setPasswordState] = useState<Password | null>(null);
const [pinCode, setPincodeState] = useState<PinCode | null>(null);
const [alert, setAlert] = useState({ const [alert, setAlert] = useState({
show: false, show: false,
message: '' message: ''
}); });
const { PutData } = useCallApi(); const { PutData, PostData } = useCallApi();
const { PostData } = useCallApi();
const handleSetPassword = async (newPassword: Password) => {
setPassword(newPassword);
const handleError = (error: any, defaultMessage: string) => { // Helper function to handle API errors
const errorMessage = error?.response?.data?.message || error?.message || defaultMessage; const handleError = (error: any, defaultMessage: string) => {
setAlert({ show: true, message: errorMessage }); const errorMessage = error?.response?.data?.message || error?.message || defaultMessage;
toast.error(errorMessage); setAlert({ show: true, message: errorMessage });
}; toast.error(errorMessage);
};
// Function to handle password update
const handleSetPassword = async (newPassword: Password) => {
setPasswordState(newPassword);
try { try {
// Validate the current password
const validate = await PostData(`${API_URL}/login`, { const validate = await PostData(`${API_URL}/login`, {
password: newPassword.current_password, password: newPassword.current_password,
username: newPassword.username username: newPassword.username
@ -68,6 +80,7 @@ const AccountUserProfileContextProvider = ({ children }: { children: React.React
return; return;
} }
// Update the password
const response = await PutData(`${API_URL}/user/update_password`, { const response = await PutData(`${API_URL}/user/update_password`, {
password: newPassword.password, password: newPassword.password,
retype_password: newPassword.retype_password retype_password: newPassword.retype_password
@ -83,9 +96,10 @@ const AccountUserProfileContextProvider = ({ children }: { children: React.React
} }
}; };
// Function to handle profile update
const handleSetProfile = async (newProfile: Profile) => { const handleSetProfile = async (newProfile: Profile) => {
try { try {
setProfile(newProfile); setProfileState(newProfile);
const response = await PutData(`${API_URL}/user/update_profile/`, { const response = await PutData(`${API_URL}/user/update_profile/`, {
name: newProfile.name, name: newProfile.name,
@ -95,9 +109,19 @@ const AccountUserProfileContextProvider = ({ children }: { children: React.React
toast.success('Profile updated successfully.'); toast.success('Profile updated successfully.');
} catch (error: any) { } catch (error: any) {
const errorMessage = error?.message || 'Failed to update Profile. Please try again.'; handleError(error, 'Failed to update Profile. Please try again.');
setAlert({ show: true, message: errorMessage }); }
toast.error(errorMessage); };
// Function to handle pin code update
const handleSetPinCode = async (newPinCode: PinCode) => {
try {
setPincodeState(newPinCode);
// You can make an API call here to update the pin code in the backend if needed
toast.success('Pin code updated successfully.');
} catch (error: any) {
handleError(error, 'Failed to update Pin Code. Please try again.');
} }
}; };
@ -106,8 +130,10 @@ const AccountUserProfileContextProvider = ({ children }: { children: React.React
value={{ value={{
password, password,
profile, profile,
pincode: pinCode,
setPassword: handleSetPassword, setPassword: handleSetPassword,
setProfile: handleSetProfile setProfile: handleSetProfile,
setPincode: handleSetPinCode
}} }}
> >
{children} {children}

View File

@ -125,7 +125,17 @@ const DetailApprovalTransaction = () => {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<p className="text-sm text-gray-500">Transaction Date</p> <p className="text-sm text-gray-500">Transaction Date</p>
<p className="font-medium">{transactionDetails?.transaction_date}</p> <p className="font-medium">{transactionDetails?.transaction_date
? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}</p>
</div> </div>
<div> <div>
<p className="text-sm text-gray-500">Full Name</p> <p className="text-sm text-gray-500">Full Name</p>
@ -299,8 +309,17 @@ const DetailApprovalTransaction = () => {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<p className="text-sm text-gray-500">Transaction Date</p> <p className="text-sm text-gray-500">Transaction Date</p>
<p className="font-medium">{transactionDetails?.transaction_date}</p> <p className="font-medium">{transactionDetails?.transaction_date
</div> ? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}</p> </div>
<div> <div>
<p className="text-sm text-gray-500">Full Name</p> <p className="text-sm text-gray-500">Full Name</p>
<p className="font-medium"> <p className="font-medium">
@ -607,7 +626,7 @@ const DetailApprovalTransaction = () => {
<tr className="bg-gray-100"> <tr className="bg-gray-100">
<th className="px-4 py-2 text-left text-sm text-gray-500">Type</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Type</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
@ -618,8 +637,29 @@ const DetailApprovalTransaction = () => {
transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
<tr key={index} className="border-t"> <tr key={index} className="border-t">
<td className="px-4 py-2 text-sm text-gray-500">{log.type}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.type}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.request_date
<td className="px-4 py-2 text-sm text-gray-500">{log.response_date}</td> ? new Date(log.request_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_date
? new Date(log.response_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_body}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.request_body}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td>

View File

@ -45,8 +45,8 @@ const DetailTransaction = () => {
id: selectedTransactionId id: selectedTransactionId
}); });
setTransactionDetails(response?.data); setTransactionDetails(response?.data);
console.log(response?.data); // console.log(response?.data);
console.log(selectedTransactionId); // console.log(selectedTransactionId);
} catch (error) { } catch (error) {
console.error('Error fetching transaction', error); console.error('Error fetching transaction', error);
} }
@ -141,7 +141,20 @@ const DetailTransaction = () => {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<p className="text-sm text-gray-500">Transaction Date</p> <p className="text-sm text-gray-500">Transaction Date</p>
<p className="font-medium">{transactionDetails?.transaction_date}</p> <p className="font-medium">
{transactionDetails?.transaction_date
? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}
</p>
</div> </div>
<div> <div>
<p className="text-sm text-gray-500">Full Name</p> <p className="text-sm text-gray-500">Full Name</p>
@ -312,7 +325,17 @@ const DetailTransaction = () => {
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<p className="text-sm text-gray-500">Transaction Date</p> <p className="text-sm text-gray-500">Transaction Date</p>
<p className="font-medium">{transactionDetails?.transaction_date}</p> <p className="font-medium">{transactionDetails?.transaction_date
? new Date(transactionDetails.transaction_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}</p>
</div> </div>
<div> <div>
<p className="text-sm text-gray-500">Full Name</p> <p className="text-sm text-gray-500">Full Name</p>
@ -614,7 +637,7 @@ const DetailTransaction = () => {
<tr className="bg-gray-100"> <tr className="bg-gray-100">
<th className="px-4 py-2 text-left text-sm text-gray-500">Type</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Type</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Response Date</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th> <th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
@ -625,8 +648,29 @@ const DetailTransaction = () => {
transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => (
<tr key={index} className="border-t"> <tr key={index} className="border-t">
<td className="px-4 py-2 text-sm text-gray-500">{log.type}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.type}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.request_date
<td className="px-4 py-2 text-sm text-gray-500">{log.response_date}</td> ? new Date(log.request_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_date
? new Date(log.response_date).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
})
: ''}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_body}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.request_body}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td> <td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td>