fix: all errors

This commit is contained in:
Sabda Yagra 2026-01-29 09:11:43 +07:00
parent a3b4870092
commit 5b6ec0342b
11 changed files with 387 additions and 158 deletions

View File

@ -21,7 +21,7 @@ export default function AdminPage() {
return ( return (
<motion.div <motion.div
className="h-full overflow-auto bg-gray-50" className="h-full overflow-auto bg-gray-50 dark:bg-black"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}

View File

@ -32,7 +32,7 @@ export default function ManagementUser() {
return ( return (
<div> <div>
<SiteBreadcrumb /> <SiteBreadcrumb />
<section className="flex flex-col gap-2 bg-white dark:bg-slate-200 rounded-lg p-3 mt-5 border"> <section className="flex flex-col gap-2 bg-white dark:bg-black rounded-lg p-3 mt-5 border">
<div className="flex justify-between py-3"> <div className="flex justify-between py-3">
<p className="text-lg"> <p className="text-lg">
Data User Data User

View File

@ -199,7 +199,7 @@ function TenantSettingsContentTable() {
<div className="container mx-auto p-6 space-y-6 border rounded-lg"> <div className="container mx-auto p-6 space-y-6 border rounded-lg">
<div className="flex items-center justify-between "> <div className="flex items-center justify-between ">
<div> <div>
<h1 className="text-3xl font-bold text-gray-900">Tenant Settings</h1> <h1 className="text-3xl font-bold text-gray-900 dark:text-white">Tenant Settings</h1>
<p className="text-gray-600 mt-2"> <p className="text-gray-600 mt-2">
Manage approval workflows and user levels for your tenant Manage approval workflows and user levels for your tenant
</p> </p>
@ -255,7 +255,8 @@ function TenantSettingsContentTable() {
Approval Workflow Setup Approval Workflow Setup
</h2> </h2>
{workflow && !isEditingWorkflow && ( {workflow && !isEditingWorkflow && (
<Button variant="outline" <Button
variant="outline"
onClick={() => setIsEditingWorkflow(true)} onClick={() => setIsEditingWorkflow(true)}
className="flex items-center gap-2" className="flex items-center gap-2"
> >
@ -265,7 +266,7 @@ function TenantSettingsContentTable() {
)} )}
</div> </div>
{isEditingWorkflow ? ( {isEditingWorkflow && workflow && workflow.workflow?.id ? (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between">
@ -280,6 +281,8 @@ function TenantSettingsContentTable() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ApprovalWorkflowForm <ApprovalWorkflowForm
key={workflow.workflow.id}
workflowId={workflow.workflow.id}
initialData={ initialData={
workflow workflow
? { ? {
@ -287,8 +290,12 @@ function TenantSettingsContentTable() {
description: workflow.workflow.description, description: workflow.workflow.description,
isDefault: workflow.workflow.isDefault, isDefault: workflow.workflow.isDefault,
isActive: workflow.workflow.isActive, isActive: workflow.workflow.isActive,
requiresApproval: workflow.workflow.requiresApproval, requiresApproval:
autoPublish: workflow.workflow.autoPublish, workflow.clientSettings.requiresApproval,
// workflow.workflow.requiresApproval,
autoPublish:
workflow.clientSettings.autoPublishArticles,
// workflow.workflow.autoPublish,
steps: steps:
workflow.steps?.map((step) => ({ workflow.steps?.map((step) => ({
stepOrder: step.stepOrder, stepOrder: step.stepOrder,
@ -372,12 +379,18 @@ function TenantSettingsContentTable() {
<div className="text-center p-4 bg-gray-50 rounded-lg"> <div className="text-center p-4 bg-gray-50 rounded-lg">
<div <div
className={`text-2xl font-bold ${ className={`text-2xl font-bold ${
workflow.workflow.requiresApproval // workflow.workflow.requiresApproval
workflow.clientSettings.requiresApproval
? "text-green-600" ? "text-green-600"
: "text-red-600" : "text-red-600"
}`} }`}
> >
{workflow.workflow.requiresApproval ? "Yes" : "No"} {
// workflow.workflow.requiresApproval
workflow.clientSettings.requiresApproval
? "Yes"
: "No"
}
</div> </div>
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600">
Requires Approval Requires Approval
@ -387,12 +400,18 @@ function TenantSettingsContentTable() {
<div className="text-center p-4 bg-gray-50 rounded-lg"> <div className="text-center p-4 bg-gray-50 rounded-lg">
<div <div
className={`text-2xl font-bold ${ className={`text-2xl font-bold ${
workflow.workflow.autoPublish // workflow.workflow.autoPublish
workflow.clientSettings.autoPublishArticles
? "text-green-600" ? "text-green-600"
: "text-red-600" : "text-red-600"
}`} }`}
> >
{workflow.workflow.autoPublish ? "Yes" : "No"} {
// workflow.workflow.autoPublish
workflow.clientSettings.autoPublishArticles
? "Yes"
: "No"
}
</div> </div>
<div className="text-sm text-gray-600">Auto Publish</div> <div className="text-sm text-gray-600">Auto Publish</div>
</div> </div>
@ -413,7 +432,7 @@ function TenantSettingsContentTable() {
{step.stepOrder} {step.stepOrder}
</div> </div>
<div> <div>
<div className="font-medium">{step.stepName}</div> <div className="font-medium dark:text-black">{step.stepName}</div>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
{step.conditionType && {step.conditionType &&
`Condition: ${step.conditionType}`} `Condition: ${step.conditionType}`}

View File

@ -70,7 +70,9 @@ const AuthPage = () => {
const handleOTPSuccess = async () => { const handleOTPSuccess = async () => {
if (loginCredentials) { if (loginCredentials) {
try { try {
await login(loginCredentials); await login(loginCredentials, { skipRedirect: false });
// await login(loginCredentials);
} catch (error: any) { } catch (error: any) {
toast.error(error.message || "Login failed after OTP verification"); toast.error(error.message || "Login failed after OTP verification");
} }

View File

@ -28,7 +28,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const { login } = useAuth(); const { login } = useAuth();
const t = useTranslations("MediaUpdate"); const t = useTranslations("MediaUpdate");
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [rememberMe, setRememberMe] = useState(true); const [rememberMe, setRememberMe] = useState(false);
const [roles, setRoles] = useState<Role[]>([]); const [roles, setRoles] = useState<Role[]>([]);
const [selectedCategory, setSelectedCategory] = useState("5"); const [selectedCategory, setSelectedCategory] = useState("5");
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
@ -62,7 +62,8 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const handleLogin = async (data: LoginFormData) => { const handleLogin = async (data: LoginFormData) => {
try { try {
await login(data); await login(data, { skipRedirect: true });
// await login(data);
onSuccess?.(data); onSuccess?.(data);
} catch (error: any) { } catch (error: any) {
const message = getLoginErrorMessage(error); const message = getLoginErrorMessage(error);
@ -72,7 +73,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const onSubmit = async (data: LoginFormData) => { const onSubmit = async (data: LoginFormData) => {
try { try {
// onSuccess?.(data); // onSuccess?.(data);
await handleLogin(data); await handleLogin(data);
} catch (error: any) { } catch (error: any) {
@ -193,6 +193,20 @@ export const LoginForm: React.FC<LoginFormProps> = ({
{/* Remember Me and Forgot Password */} {/* Remember Me and Forgot Password */}
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<input
id="rememberMe"
type="checkbox"
checked={rememberMe}
onChange={(e) => setRememberMe(e.target.checked)}
disabled={isSubmitting}
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<label htmlFor="rememberMe" className="text-sm cursor-pointer">
{t("rememberMe")}
</label>
</div>
{/* <div className="flex gap-2 items-center">
<Checkbox <Checkbox
id="rememberMe" id="rememberMe"
checked={rememberMe} checked={rememberMe}
@ -202,7 +216,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
<Label htmlFor="rememberMe" className="text-sm"> <Label htmlFor="rememberMe" className="text-sm">
{t("rememberMe")} {t("rememberMe")}
</Label> </Label>
</div> </div> */}
<Link <Link
href="/auth/forgot-password" href="/auth/forgot-password"
className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium hover:underline" className="text-sm text-default-800 dark:text-default-400 leading-6 font-medium hover:underline"

View File

@ -23,6 +23,7 @@ export const OTPForm: React.FC<OTPFormProps> = ({
const [otpValue, setOtpValue] = useState(""); const [otpValue, setOtpValue] = useState("");
const t = useTranslations("MediaUpdate"); const t = useTranslations("MediaUpdate");
const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => { const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => {
const { key } = event; const { key } = event;
const target = event.currentTarget; const target = event.currentTarget;
@ -57,9 +58,12 @@ export const OTPForm: React.FC<OTPFormProps> = ({
try { try {
const isValid = await verifyOTP(loginCredentials.username, otpValue); const isValid = await verifyOTP(loginCredentials.username, otpValue);
if (isValid) { if (isValid) {
onSuccess?.(); onSuccess?.();
} else { }
else {
onError?.("Invalid OTP code"); onError?.("Invalid OTP code");
} }
} catch (error: any) { } catch (error: any) {
@ -154,7 +158,6 @@ export const OTPForm: React.FC<OTPFormProps> = ({
{loading ? ( {loading ? (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
</div> </div>
) : ( ) : (
t("enterOTP4") t("enterOTP4")

View File

@ -33,6 +33,18 @@ interface ApprovalWorkflowFormProps {
isLoading?: boolean; isLoading?: boolean;
} }
const normalizeClientSettings = (settings: ClientApprovalSettingsRequest) => ({
approvalExemptCategories: settings.approvalExemptCategories ?? [],
approvalExemptRoles: settings.approvalExemptRoles ?? [],
approvalExemptUsers: settings.approvalExemptUsers ?? [],
requireApprovalFor: settings.requireApprovalFor ?? [],
skipApprovalFor: settings.skipApprovalFor ?? [],
autoPublishArticles: settings.autoPublishArticles ?? false,
requiresApproval: settings.requiresApproval ?? false,
isActive: settings.isActive ?? false,
});
export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
initialData, initialData,
workflowId, workflowId,
@ -41,31 +53,34 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
isLoading = false, isLoading = false,
}) => { }) => {
// Form state // Form state
const [formData, setFormData] = useState<CreateApprovalWorkflowWithClientSettingsRequest>({ const [formData, setFormData] =
name: "", useState<CreateApprovalWorkflowWithClientSettingsRequest>({
description: "", name: "",
isActive: true, description: "",
isDefault: true,
requiresApproval: true,
autoPublish: false,
steps: [],
clientApprovalSettings: {
requiresApproval: true,
autoPublishArticles: false,
approvalExemptUsers: [],
approvalExemptRoles: [],
approvalExemptCategories: [],
requireApprovalFor: [],
skipApprovalFor: [],
isActive: true, isActive: true,
}, isDefault: true,
}); requiresApproval: true,
autoPublish: false,
steps: [],
clientApprovalSettings: {
requiresApproval: true,
autoPublishArticles: false,
approvalExemptUsers: [],
approvalExemptRoles: [],
approvalExemptCategories: [],
requireApprovalFor: [],
skipApprovalFor: [],
isActive: true,
},
});
// API data // API data
const [userLevels, setUserLevels] = useState<UserLevel[]>([]); const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
const [users, setUsers] = useState<User[]>([]); const [users, setUsers] = useState<User[]>([]);
const [userRoles, setUserRoles] = useState<UserRole[]>([]); const [userRoles, setUserRoles] = useState<UserRole[]>([]);
const [articleCategories, setArticleCategories] = useState<ArticleCategory[]>([]); const [articleCategories, setArticleCategories] = useState<ArticleCategory[]>(
[],
);
// UI state // UI state
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({});
@ -76,25 +91,28 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
// Get available user levels for a specific step (excluding already selected ones) // Get available user levels for a specific step (excluding already selected ones)
const getAvailableUserLevels = (currentStepIndex: number) => { const getAvailableUserLevels = (currentStepIndex: number) => {
const usedLevelIds = new Set<number>(); const usedLevelIds = new Set<number>();
// Collect all user level IDs that are already used by other steps // Collect all user level IDs that are already used by other steps
formData.steps.forEach((step, stepIndex) => { formData.steps.forEach((step, stepIndex) => {
if (stepIndex !== currentStepIndex && step.conditionValue) { if (stepIndex !== currentStepIndex && step.conditionValue) {
try { try {
const conditionData = JSON.parse(step.conditionValue); const conditionData = JSON.parse(step.conditionValue);
if (conditionData.applies_to_levels && Array.isArray(conditionData.applies_to_levels)) { if (
conditionData.applies_to_levels &&
Array.isArray(conditionData.applies_to_levels)
) {
conditionData.applies_to_levels.forEach((levelId: number) => { conditionData.applies_to_levels.forEach((levelId: number) => {
usedLevelIds.add(levelId); usedLevelIds.add(levelId);
}); });
} }
} catch (error) { } catch (error) {
console.error('Error parsing conditionValue:', error); console.error("Error parsing conditionValue:", error);
} }
} }
}); });
// Filter out used levels and return available ones // Filter out used levels and return available ones
return userLevels.filter(level => !usedLevelIds.has(level.id)); return userLevels.filter((level) => !usedLevelIds.has(level.id));
}; };
// Load initial data // Load initial data
@ -108,17 +126,20 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
useEffect(() => { useEffect(() => {
const loadData = async () => { const loadData = async () => {
try { try {
const [userLevelsRes, usersRes, userRolesRes, categoriesRes] = await Promise.all([ const [userLevelsRes, usersRes, userRolesRes, categoriesRes] =
getUserLevels(), await Promise.all([
getUsers(), getUserLevels(),
getUserRoles(), getUsers(),
getArticleCategories(), getUserRoles(),
]); getArticleCategories(),
]);
if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []); if (!userLevelsRes?.error)
setUserLevels(userLevelsRes?.data?.data || []);
if (!usersRes?.error) setUsers(usersRes?.data || []); if (!usersRes?.error) setUsers(usersRes?.data || []);
if (!userRolesRes?.error) setUserRoles(userRolesRes?.data || []); if (!userRolesRes?.error) setUserRoles(userRolesRes?.data || []);
if (!categoriesRes?.error) setArticleCategories(categoriesRes?.data || []); if (!categoriesRes?.error)
setArticleCategories(categoriesRes?.data || []);
} catch (error) { } catch (error) {
console.error("Error loading form data:", error); console.error("Error loading form data:", error);
} finally { } finally {
@ -155,10 +176,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
newErrors[`steps.${index}.stepName`] = "Step name is required"; newErrors[`steps.${index}.stepName`] = "Step name is required";
} }
if (!step.requiredUserLevelId) { if (!step.requiredUserLevelId) {
newErrors[`steps.${index}.requiredUserLevelId`] = "Required user level is required"; newErrors[`steps.${index}.requiredUserLevelId`] =
"Required user level is required";
} }
if (step.stepOrder <= 0) { if (step.stepOrder <= 0) {
newErrors[`steps.${index}.stepOrder`] = "Step order must be greater than 0"; newErrors[`steps.${index}.stepOrder`] =
"Step order must be greater than 0";
} }
}); });
@ -167,10 +190,13 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
}; };
// Form handlers // Form handlers
const handleBasicInfoChange = (field: keyof CreateApprovalWorkflowWithClientSettingsRequest, value: any) => { const handleBasicInfoChange = (
setFormData(prev => ({ ...prev, [field]: value })); field: keyof CreateApprovalWorkflowWithClientSettingsRequest,
value: any,
) => {
setFormData((prev) => ({ ...prev, [field]: value }));
if (errors[field]) { if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: "" })); setErrors((prev) => ({ ...prev, [field]: "" }));
} }
}; };
@ -188,17 +214,26 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
// Keep existing stepOrder if manually set // Keep existing stepOrder if manually set
return step; return step;
}); });
setFormData(prev => ({ ...prev, steps: updatedSteps })); setFormData((prev) => ({ ...prev, steps: updatedSteps }));
}; };
const renderStepForm = (step: ApprovalWorkflowStepRequest, index: number, onUpdate: (step: ApprovalWorkflowStepRequest) => void, onDelete: () => void) => { const renderStepForm = (
const stepErrors = Object.keys(errors).filter(key => key.startsWith(`steps.${index}`)); step: ApprovalWorkflowStepRequest,
index: number,
onUpdate: (step: ApprovalWorkflowStepRequest) => void,
onDelete: () => void,
) => {
const stepErrors = Object.keys(errors).filter((key) =>
key.startsWith(`steps.${index}`),
);
// Check if this step has parallel steps (same stepOrder) // Check if this step has parallel steps (same stepOrder)
const parallelSteps = formData.steps.filter((s, i) => s.stepOrder === step.stepOrder && i !== index); const parallelSteps = formData.steps.filter(
(s, i) => s.stepOrder === step.stepOrder && i !== index,
);
const isParallelStep = parallelSteps.length > 0; const isParallelStep = parallelSteps.length > 0;
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{/* Parallel Step Indicator */} {/* Parallel Step Indicator */}
@ -222,7 +257,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
type="number" type="number"
placeholder="1" placeholder="1"
value={step.stepOrder} value={step.stepOrder}
onChange={(value) => onUpdate({ ...step, stepOrder: value ? Number(value) : index + 1 })} onChange={(value) =>
onUpdate({
...step,
stepOrder: value ? Number(value) : index + 1,
})
}
error={errors[`steps.${index}.stepOrder`]} error={errors[`steps.${index}.stepOrder`]}
min={1} min={1}
helpText="Same order = parallel steps" helpText="Same order = parallel steps"
@ -243,17 +283,31 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
label="Required User Level" label="Required User Level"
name={`requiredUserLevelId-${index}`} name={`requiredUserLevelId-${index}`}
type="select" type="select"
placeholder={userLevels.length > 0 ? "Select user level" : "No user levels available"} placeholder={
userLevels.length > 0
? "Select user level"
: "No user levels available"
}
value={step.requiredUserLevelId} value={step.requiredUserLevelId}
onChange={(value) => onUpdate({ ...step, requiredUserLevelId: Number(value) })} onChange={(value) =>
onUpdate({ ...step, requiredUserLevelId: Number(value) })
}
error={errors[`steps.${index}.requiredUserLevelId`]} error={errors[`steps.${index}.requiredUserLevelId`]}
options={userLevels.length > 0 ? userLevels.map(level => ({ options={
value: level.id, userLevels.length > 0
label: `${level.name} (Level ${level.levelNumber})`, ? userLevels.map((level) => ({
})) : []} value: level.id,
label: `${level.name} (Level ${level.levelNumber})`,
}))
: []
}
required required
disabled={userLevels.length === 0} disabled={userLevels.length === 0}
helpText={userLevels.length === 0 ? "No user levels found. Please create user levels first." : undefined} helpText={
userLevels.length === 0
? "No user levels found. Please create user levels first."
: undefined
}
/> />
</div> </div>
@ -264,7 +318,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
type="number" type="number"
placeholder="Leave empty for manual approval" placeholder="Leave empty for manual approval"
value={step.autoApproveAfterHours} value={step.autoApproveAfterHours}
onChange={(value) => onUpdate({ ...step, autoApproveAfterHours: value ? Number(value) : undefined })} onChange={(value) =>
onUpdate({
...step,
autoApproveAfterHours: value ? Number(value) : undefined,
})
}
helpText="Automatically approve after specified hours" helpText="Automatically approve after specified hours"
min={1} min={1}
/> />
@ -277,7 +336,7 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
value={step.canSkip || false} value={step.canSkip || false}
onChange={(value) => onUpdate({ ...step, canSkip: value })} onChange={(value) => onUpdate({ ...step, canSkip: value })}
/> />
<FormField <FormField
label="Is Active" label="Is Active"
name={`isActive-${index}`} name={`isActive-${index}`}
@ -294,32 +353,42 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
label="Applies to User Levels" label="Applies to User Levels"
placeholder={(() => { placeholder={(() => {
const availableLevels = getAvailableUserLevels(index); const availableLevels = getAvailableUserLevels(index);
return availableLevels.length > 0 ? "Select user levels..." : "No available user levels"; return availableLevels.length > 0
? "Select user levels..."
: "No available user levels";
})()} })()}
options={(() => { options={(() => {
const availableLevels = getAvailableUserLevels(index); const availableLevels = getAvailableUserLevels(index);
return availableLevels.map(level => ({ return availableLevels.map((level) => ({
value: level.id, value: level.id,
label: `${level.name} (Level ${level.levelNumber})`, label: `${level.name} (Level ${level.levelNumber})`,
})); }));
})()} })()}
value={(() => { value={(() => {
try { try {
return step.conditionValue ? JSON.parse(step.conditionValue).applies_to_levels || [] : []; return step.conditionValue
? JSON.parse(step.conditionValue).applies_to_levels || []
: [];
} catch { } catch {
return []; return [];
} }
})()} })()}
onChange={(value) => { onChange={(value) => {
const conditionValue = JSON.stringify({ applies_to_levels: value }); const conditionValue = JSON.stringify({
onUpdate({ ...step, conditionType: "user_level_hierarchy", conditionValue }); applies_to_levels: value,
});
onUpdate({
...step,
conditionType: "user_level_hierarchy",
conditionValue,
});
}} }}
searchable={true} searchable={true}
disabled={getAvailableUserLevels(index).length === 0} disabled={getAvailableUserLevels(index).length === 0}
helpText={(() => { helpText={(() => {
const availableLevels = getAvailableUserLevels(index); const availableLevels = getAvailableUserLevels(index);
const usedLevels = userLevels.length - availableLevels.length; const usedLevels = userLevels.length - availableLevels.length;
if (availableLevels.length === 0) { if (availableLevels.length === 0) {
return "All user levels are already assigned to other steps"; return "All user levels are already assigned to other steps";
} else if (usedLevels > 0) { } else if (usedLevels > 0) {
@ -334,9 +403,13 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
); );
}; };
function normalizeBoolean(value?: boolean): boolean {
return value ?? false;
}
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!validateForm()) { if (!validateForm()) {
Swal.fire({ Swal.fire({
title: "Validation Error", title: "Validation Error",
@ -344,14 +417,14 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "error", icon: "error",
confirmButtonText: "OK", confirmButtonText: "OK",
customClass: { customClass: {
popup: 'swal-z-index-9999' popup: "swal-z-index-9999",
} },
}); });
return; return;
} }
setIsSubmitting(true); setIsSubmitting(true);
try { try {
// Hardcoded client approval settings // Hardcoded client approval settings
const hardcodedClientSettings = { const hardcodedClientSettings = {
@ -362,7 +435,23 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
isActive: true, isActive: true,
requireApprovalFor: [], requireApprovalFor: [],
requiresApproval: true, requiresApproval: true,
skipApprovalFor: [] skipApprovalFor: [],
};
const clientSettingsPayload = {
approvalExemptCategories: [],
approvalExemptRoles: [],
approvalExemptUsers: [],
requireApprovalFor: [],
skipApprovalFor: [],
autoPublishArticles:
formData.clientApprovalSettings.autoPublishArticles ?? false,
requiresApproval:
formData.clientApprovalSettings.requiresApproval ?? false,
isActive: formData.clientApprovalSettings.isActive ?? false,
}; };
if (workflowId) { if (workflowId) {
@ -371,30 +460,60 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
workflowId, workflowId,
name: formData.name, name: formData.name,
description: formData.description, description: formData.description,
isActive: formData.isActive || false,
isDefault: formData.isDefault || false, isActive: normalizeBoolean(formData.isActive),
steps: formData.steps.map(step => ({ isDefault: normalizeBoolean(formData.isDefault),
steps: formData.steps.map((step) => ({
...step, ...step,
branchName: step.stepName, // branchName should be same as stepName branchName: step.stepName,
})), })),
clientSettings: hardcodedClientSettings
clientSettings: {
approvalExemptCategories: [],
approvalExemptRoles: [],
approvalExemptUsers: [],
requireApprovalFor: [],
skipApprovalFor: [],
// 🔥 AMBIL LANGSUNG DARI CHECKBOX UI
requiresApproval: normalizeBoolean(formData.requiresApproval),
autoPublishArticles: normalizeBoolean(formData.autoPublish),
isActive: normalizeBoolean(formData.isActive),
},
}; };
// const updateData: UpdateApprovalWorkflowWithClientSettingsRequest = {
// workflowId,
// name: formData.name,
// description: formData.description,
// isActive: formData.isActive,
// isDefault: formData.isDefault,
// steps: formData.steps.map(step => ({
// ...step,
// branchName: step.stepName, // branchName should be same as stepName
// })),
// clientSettings: hardcodedClientSettings
// };
console.log("Update Data: ", updateData); console.log("Update Data: ", updateData);
const response = await updateApprovalWorkflowWithClientSettings(updateData); const response =
await updateApprovalWorkflowWithClientSettings(updateData);
console.log("Update Response: ", response); console.log("Update Response: ", response);
if (response?.error) { if (response?.error) {
Swal.fire({ Swal.fire({
title: "Error", title: "Error",
text: response?.message?.messages?.[0] || "Failed to update approval workflow", text:
response?.message?.messages?.[0] ||
"Failed to update approval workflow",
icon: "error", icon: "error",
confirmButtonText: "OK", confirmButtonText: "OK",
customClass: { customClass: {
popup: 'swal-z-index-9999' popup: "swal-z-index-9999",
} },
}); });
} else { } else {
Swal.fire({ Swal.fire({
@ -403,8 +522,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "success", icon: "success",
confirmButtonText: "OK", confirmButtonText: "OK",
customClass: { customClass: {
popup: 'swal-z-index-9999' popup: "swal-z-index-9999",
} },
}).then(() => { }).then(() => {
// Call onSave to trigger parent refresh // Call onSave to trigger parent refresh
if (onSave) { if (onSave) {
@ -416,24 +535,27 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
// Create mode // Create mode
const submitData = { const submitData = {
...formData, ...formData,
clientApprovalSettings: hardcodedClientSettings clientApprovalSettings: hardcodedClientSettings,
}; };
console.log("Create Data: ", submitData); console.log("Create Data: ", submitData);
const response = await createApprovalWorkflowWithClientSettings(submitData); const response =
await createApprovalWorkflowWithClientSettings(submitData);
console.log("Create Response: ", response); console.log("Create Response: ", response);
if (response?.error) { if (response?.error) {
Swal.fire({ Swal.fire({
title: "Error", title: "Error",
text: response?.message?.messages?.[0] || "Failed to create approval workflow", text:
response?.message?.messages?.[0] ||
"Failed to create approval workflow",
icon: "error", icon: "error",
confirmButtonText: "OK", confirmButtonText: "OK",
customClass: { customClass: {
popup: 'swal-z-index-9999' popup: "swal-z-index-9999",
} },
}); });
} else { } else {
Swal.fire({ Swal.fire({
@ -442,8 +564,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "success", icon: "success",
confirmButtonText: "OK", confirmButtonText: "OK",
customClass: { customClass: {
popup: 'swal-z-index-9999' popup: "swal-z-index-9999",
} },
}).then(() => { }).then(() => {
// Call onSave to trigger parent refresh // Call onSave to trigger parent refresh
if (onSave) { if (onSave) {
@ -460,8 +582,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "error", icon: "error",
confirmButtonText: "OK", confirmButtonText: "OK",
customClass: { customClass: {
popup: 'swal-z-index-9999' popup: "swal-z-index-9999",
} },
}); });
} finally { } finally {
setIsSubmitting(false); setIsSubmitting(false);
@ -477,8 +599,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
confirmButtonText: "Yes, reset", confirmButtonText: "Yes, reset",
cancelButtonText: "Cancel", cancelButtonText: "Cancel",
customClass: { customClass: {
popup: 'swal-z-index-9999' popup: "swal-z-index-9999",
} },
}).then((result) => { }).then((result) => {
if (result.isConfirmed) { if (result.isConfirmed) {
setFormData({ setFormData({
@ -513,12 +635,18 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
<span className="ml-2 text-gray-600">Loading form data...</span> <span className="ml-2 text-gray-600">Loading form data...</span>
</div> </div>
)} )}
<Tabs defaultValue="basic" className="w-full"> <Tabs defaultValue="basic" className="w-full">
<TabsList className="grid w-full grid-cols-3"> <TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="basic" disabled={isLoadingData}>Basic Information</TabsTrigger> <TabsTrigger value="basic" disabled={isLoadingData}>
<TabsTrigger value="steps" disabled={isLoadingData}>Workflow Steps</TabsTrigger> Basic Information
<TabsTrigger value="settings" disabled={isLoadingData}>Client Settings</TabsTrigger> </TabsTrigger>
<TabsTrigger value="steps" disabled={isLoadingData}>
Workflow Steps
</TabsTrigger>
<TabsTrigger value="settings" disabled={isLoadingData}>
Client Settings
</TabsTrigger>
</TabsList> </TabsList>
{/* Basic Information Tab */} {/* Basic Information Tab */}
@ -545,7 +673,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
type="textarea" type="textarea"
placeholder="Describe the purpose and process of this workflow" placeholder="Describe the purpose and process of this workflow"
value={formData.description} value={formData.description}
onChange={(value) => handleBasicInfoChange("description", value)} onChange={(value) =>
handleBasicInfoChange("description", value)
}
error={errors.description} error={errors.description}
required required
rows={4} rows={4}
@ -565,7 +695,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
name="isDefault" name="isDefault"
type="checkbox" type="checkbox"
value={formData.isDefault} value={formData.isDefault}
onChange={(value) => handleBasicInfoChange("isDefault", value)} onChange={(value) =>
handleBasicInfoChange("isDefault", value)
}
/> />
<FormField <FormField
@ -573,7 +705,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
name="requiresApproval" name="requiresApproval"
type="checkbox" type="checkbox"
value={formData.requiresApproval} value={formData.requiresApproval}
onChange={(value) => handleBasicInfoChange("requiresApproval", value)} onChange={(value) =>
handleBasicInfoChange("requiresApproval", value)
}
/> />
<FormField <FormField
@ -581,7 +715,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
name="autoPublish" name="autoPublish"
type="checkbox" type="checkbox"
value={formData.autoPublish} value={formData.autoPublish}
onChange={(value) => handleBasicInfoChange("autoPublish", value)} onChange={(value) =>
handleBasicInfoChange("autoPublish", value)
}
/> />
</div> </div>
</CardContent> </CardContent>
@ -595,14 +731,22 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
<CardTitle className="flex items-center justify-between"> <CardTitle className="flex items-center justify-between">
<span>Workflow Steps Configuration</span> <span>Workflow Steps Configuration</span>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
{formData.steps.length} step{formData.steps.length !== 1 ? 's' : ''} {formData.steps.length} step
{formData.steps.length !== 1 ? "s" : ""}
{(() => { {(() => {
const parallelGroups = formData.steps.reduce((acc, step) => { const parallelGroups = formData.steps.reduce(
acc[step.stepOrder] = (acc[step.stepOrder] || 0) + 1; (acc, step) => {
return acc; acc[step.stepOrder] = (acc[step.stepOrder] || 0) + 1;
}, {} as Record<number, number>); return acc;
const parallelCount = Object.values(parallelGroups).filter(count => count > 1).length; },
return parallelCount > 0 ? `${parallelCount} parallel group${parallelCount !== 1 ? 's' : ''}` : ''; {} as Record<number, number>,
);
const parallelCount = Object.values(parallelGroups).filter(
(count) => count > 1,
).length;
return parallelCount > 0
? `${parallelCount} parallel group${parallelCount !== 1 ? "s" : ""}`
: "";
})()} })()}
</div> </div>
</CardTitle> </CardTitle>
@ -636,21 +780,44 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
<div className="h-12 w-12 mx-auto mb-2 bg-gray-100 rounded-full flex items-center justify-center"> <div className="h-12 w-12 mx-auto mb-2 bg-gray-100 rounded-full flex items-center justify-center">
<span className="text-gray-400 text-xl"></span> <span className="text-gray-400 text-xl"></span>
</div> </div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Settings Pre-configured</h3> <h3 className="text-lg font-medium text-gray-900 mb-2">
Settings Pre-configured
</h3>
<p className="text-gray-600"> <p className="text-gray-600">
Client approval settings are automatically configured with optimal defaults. Client approval settings are automatically configured with
optimal defaults.
</p> </p>
</div> </div>
<div className="bg-gray-50 rounded-lg p-4 text-left max-w-md mx-auto"> <div className="bg-gray-50 rounded-lg p-4 text-left max-w-md mx-auto">
<h4 className="font-medium text-gray-900 mb-2">Default Settings:</h4> <h4 className="font-medium text-gray-900 mb-2">
Default Settings:
</h4>
<ul className="text-sm text-gray-600 space-y-1"> <ul className="text-sm text-gray-600 space-y-1">
<li> Requires Approval: <span className="font-medium text-green-600">Yes</span></li> <li>
<li> Auto Publish Articles: <span className="font-medium text-green-600">Yes</span></li> Requires Approval:{" "}
<li> Is Active: <span className="font-medium text-green-600">Yes</span></li> <span className="font-medium text-green-600">Yes</span>
<li> Exempt Users: <span className="font-medium text-gray-500">None</span></li> </li>
<li> Exempt Roles: <span className="font-medium text-gray-500">None</span></li> <li>
<li> Exempt Categories: <span className="font-medium text-gray-500">None</span></li> Auto Publish Articles:{" "}
<span className="font-medium text-green-600">Yes</span>
</li>
<li>
Is Active:{" "}
<span className="font-medium text-green-600">Yes</span>
</li>
<li>
Exempt Users:{" "}
<span className="font-medium text-gray-500">None</span>
</li>
<li>
Exempt Roles:{" "}
<span className="font-medium text-gray-500">None</span>
</li>
<li>
Exempt Categories:{" "}
<span className="font-medium text-gray-500">None</span>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -684,7 +851,7 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
Cancel Cancel
</Button> </Button>
)} )}
<Button <Button
type="submit" type="submit"
variant="outline" variant="outline"
@ -692,7 +859,13 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
className="flex items-center gap-2" className="flex items-center gap-2"
> >
<SaveIcon className="h-4 w-4" /> <SaveIcon className="h-4 w-4" />
{isSubmitting ? "Saving..." : isLoadingData ? "Loading..." : workflowId ? "Update Workflow" : "Save Workflow"} {isSubmitting
? "Saving..."
: isLoadingData
? "Loading..."
: workflowId
? "Update Workflow"
: "Save Workflow"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@ const Loader = () => {
<div className="flex gap-2 items-center "> <div className="flex gap-2 items-center ">
{/* <DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" /> */} {/* <DashCodeLogo className=" text-default-900 h-8 w-8 [&>path:nth-child(3)]:text-background [&>path:nth-child(2)]:text-background" /> */}
<Image <Image
src="/assets/mediahub-logo-min.png" src="/assets/logo1.png"
alt="" alt=""
width={80} width={80}
height={80} height={80}

View File

@ -193,7 +193,7 @@ const UserInternalTable = () => {
}; };
return ( return (
<div className="bg-white dark:bg-slate-200"> <div className="bg-white dark:bg-black">
<div className="flex justify-between py-3"> <div className="flex justify-between py-3">
<Input <Input
type="text" type="text"

View File

@ -64,18 +64,21 @@ export const useAuth = (): AuthContextType => {
}, []); }, []);
const login = useCallback( const login = useCallback(
async (credentials: LoginFormData): Promise<void> => { async (
credentials: LoginFormData,
options?: { skipRedirect?: boolean },
): Promise<void> => {
try { try {
setState((prev) => ({ ...prev, loading: true, error: null })); setState((prev) => ({ ...prev, loading: true, error: null }));
// Check rate limiting // Check rate limiting
if (!loginRateLimiter.canAttempt(credentials.username)) { if (!loginRateLimiter.canAttempt(credentials.username)) {
const remainingTime = loginRateLimiter.getRemainingTime( const remainingTime = loginRateLimiter.getRemainingTime(
credentials.username credentials.username,
); );
const minutes = Math.ceil(remainingTime / (60 * 1000)); const minutes = Math.ceil(remainingTime / (60 * 1000));
throw new Error( throw new Error(
`Too many login attempts. Please try again in ${minutes} minutes.` `Too many login attempts. Please try again in ${minutes} minutes.`,
); );
} }
@ -119,10 +122,14 @@ export const useAuth = (): AuthContextType => {
Cookies.set("time_refresh", newTime, { Cookies.set("time_refresh", newTime, {
expires: 1, expires: 1,
}); });
if (response?.data?.data?.approvalWorkflowInfo?.hasWorkflowSetup) { if (response?.data?.data?.approvalWorkflowInfo?.hasWorkflowSetup) {
Cookies.set("default_workflow", response?.data?.data?.approvalWorkflowInfo?.defaultWorkflowId, { Cookies.set(
expires: 1, "default_workflow",
}); response?.data?.data?.approvalWorkflowInfo?.defaultWorkflowId,
{
expires: 1,
},
);
} }
Cookies.set("is_first_login", "true", { Cookies.set("is_first_login", "true", {
@ -176,12 +183,20 @@ export const useAuth = (): AuthContextType => {
// Reset rate limiter on successful login // Reset rate limiter on successful login
loginRateLimiter.resetAttempts(credentials.username); loginRateLimiter.resetAttempts(credentials.username);
if (profile?.userRoleId === 4 || profile?.userRoleId === 5) { if (!options?.skipRedirect) {
router.push("/"); if (profile?.userRoleId === 4 || profile?.userRoleId === 5) {
} else { router.push("/");
router.push("/admin/dashboard"); } else {
router.push("/admin/dashboard");
}
} }
// if (profile?.userRoleId === 4 || profile?.userRoleId === 5) {
// router.push("/");
// } else {
// router.push("/admin/dashboard");
// }
} catch (error: any) { } catch (error: any) {
const errorMessage = error?.message || "Login failed"; const errorMessage = error?.message || "Login failed";
setState((prev) => ({ setState((prev) => ({
@ -192,7 +207,7 @@ export const useAuth = (): AuthContextType => {
showAuthError(error, "Login failed"); showAuthError(error, "Login failed");
} }
}, },
[router] [router],
); );
const logout = useCallback((): void => { const logout = useCallback((): void => {
@ -266,7 +281,7 @@ export const useEmailValidation = () => {
setLoading(false); setLoading(false);
} }
}, },
[] [],
); );
return { return {
@ -284,7 +299,7 @@ export const useEmailSetup = () => {
const setupEmail = useCallback( const setupEmail = useCallback(
async ( async (
credentials: LoginFormData, credentials: LoginFormData,
emailData: EmailValidationData emailData: EmailValidationData,
): Promise<string> => { ): Promise<string> => {
try { try {
setLoading(true); setLoading(true);
@ -322,7 +337,7 @@ export const useEmailSetup = () => {
setLoading(false); setLoading(false);
} }
}, },
[] [],
); );
return { return {
@ -349,7 +364,7 @@ export const useOTPVerification = () => {
const data = { const data = {
username: username, username: username,
otpCode: otp, otpCode: otp,
} };
const response = await verifyOTPByUsername(data); const response = await verifyOTPByUsername(data);
if (response?.error) { if (response?.error) {
@ -366,7 +381,7 @@ export const useOTPVerification = () => {
setLoading(false); setLoading(false);
} }
}, },
[] [],
); );
return { return {

View File

@ -53,7 +53,7 @@ export interface ProfileData {
fullname: string; fullname: string;
email: string; email: string;
roleId: number; roleId: number;
userRoleId:number; userRoleId: number;
role: { role: {
name: string; name: string;
}; };
@ -131,7 +131,10 @@ export interface AuthState {
} }
export interface AuthContextType extends AuthState { export interface AuthContextType extends AuthState {
login: (credentials: LoginFormData) => Promise<void>; login: (
credentials: LoginFormData,
options?: { skipRedirect?: boolean },
) => Promise<void>;
logout: () => void; logout: () => void;
refreshToken: () => Promise<void>; refreshToken: () => Promise<void>;
} }
@ -171,4 +174,4 @@ export interface AuthCookies {
uinse: string; // user institute id encrypted uinse: string; // user institute id encrypted
status: string; status: string;
username: string; username: string;
} }