feat: enhance user profile update functionality and error handling

- Added detailed error messages for various HTTP status codes in useUpdateProfile hook.
- Refactored updateProfile service to handle full_name as separate first and last name fields.
- Updated UpdateProfilePayload type to allow profile_picture to be a string or File.
- Introduced resolveFileUrl utility function for consistent file URL resolution.
- Improved loading and error state management in useUpdateProfile hook.
- Added DEFAULT_AVATAR constant for default user avatar.
This commit is contained in:
Kemas M. Aryadary Rasyad
2026-07-13 12:15:15 +07:00
parent 5516d35546
commit a124a2b96e
39 changed files with 729 additions and 392 deletions
+23 -25
View File
@@ -1,17 +1,19 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import logo from "../../assets/logo.png";
import { useNavigate } from "react-router-dom";
import { Mail, Lock, Eye, EyeOff } from 'lucide-react';
import { useLogin } from "../../hooks/useUser";
import { customToast } from "../utils/showToast";
import { CircleQuestionMark } from 'lucide-react';
const decodeJWT = (token: string) => {
try {
const base64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
return JSON.parse(atob(padded));
} catch {
return null;
}
try {
const base64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
return JSON.parse(atob(padded));
} catch {
return null;
}
};
export default function InternifyLogin() {
@@ -22,6 +24,12 @@ export default function InternifyLogin() {
const { login, loading, error, setError } = useLogin();
useEffect(() => {
if (!error) return;
customToast.error("Login gagal", error);
setError(null);
}, [error, setError]);
const handleSignIn = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
@@ -77,7 +85,7 @@ export default function InternifyLogin() {
placeholder="Email Account"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-lg text-sm placeholder-gray-400 focus:outline-none focus:border-red-500 focus:ring-1 focus:ring-red-500 transition-colors"
className="w-full pl-10 pr-4 py-2.5 bg-white border border-card-outline rounded-lg text-sm placeholder-gray-400 focus:outline-none focus:border-red-500 focus:ring-1 focus:ring-red-500 transition-colors"
/>
</div>
</div>
@@ -93,7 +101,7 @@ export default function InternifyLogin() {
placeholder="Account Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pl-10 pr-10 py-2.5 bg-white border border-gray-200 rounded-lg text-sm placeholder-gray-400 focus:outline-none focus:border-red-500 focus:ring-1 focus:ring-red-500 transition-colors"
className="w-full pl-10 pr-10 py-2.5 bg-white border border-card-outline rounded-lg text-sm placeholder-gray-400 focus:outline-none focus:border-red-500 focus:ring-1 focus:ring-red-500 transition-colors"
/>
<button
type="button"
@@ -108,22 +116,14 @@ export default function InternifyLogin() {
</button>
</div>
</div>
{error && (
<p className="text-xs text-red-600 bg-red-50 p-2 rounded-md font-medium">
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className="w-full bg-[#B30000] hover:bg-[#990000] text-white font-medium text-sm py-2.5 rounded-lg transition-colors shadow-sm mt-2"
className="w-full bg-red hover:bg-red/80 text-white font-medium text-sm py-2.5 rounded-lg transition-colors shadow-sm mt-2"
>
{loading ? "Signing in..." : "Sign In"}
</button>
</form>
{/* Forgot Password */}
<div className="text-center mt-6">
<a href="#" className="text-xs font-semibold text-red-600 hover:underline">
@@ -134,17 +134,15 @@ export default function InternifyLogin() {
{/* Divider & Support */}
<div className="mt-1">
<div className="relative flex py-2 items-center">
<div className="flex-grow border-t border-gray-100"></div>
<div className="flex-grow border-t border-card-outline"></div>
<span className="flex-shrink mx-4 text-[10px] font-bold tracking-wider text-font-shade uppercase">
SUPPORT
</span>
<div className="flex-grow border-t border-gray-100"></div>
<div className="flex-grow border-t border-card-outline"></div>
</div>
<button className="w-full mt-2 flex items-center justify-center gap-2 border border-gray-200 rounded-full py-1.5 text-xs text-font-shade hover:bg-gray-50 transition-colors font-medium">
<svg className="w-3.5 h-3.5 text-font-shade" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<button className="w-full mt-2 flex items-center justify-center gap-2 border border-card-outline rounded-full py-1.5 text-xs text-font-shade hover:bg-gray-50 transition-colors font-medium">
<CircleQuestionMark className="w-5 h-5"/>
Frequently Asked Questions
</button>
</div>