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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user