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 (
<motion.div
className="h-full overflow-auto bg-gray-50"
className="h-full overflow-auto bg-gray-50 dark:bg-black"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}

View File

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

View File

@ -70,7 +70,9 @@ const AuthPage = () => {
const handleOTPSuccess = async () => {
if (loginCredentials) {
try {
await login(loginCredentials);
await login(loginCredentials, { skipRedirect: false });
// await login(loginCredentials);
} catch (error: any) {
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 t = useTranslations("MediaUpdate");
const [showPassword, setShowPassword] = useState(false);
const [rememberMe, setRememberMe] = useState(true);
const [rememberMe, setRememberMe] = useState(false);
const [roles, setRoles] = useState<Role[]>([]);
const [selectedCategory, setSelectedCategory] = useState("5");
const [isDialogOpen, setIsDialogOpen] = useState(false);
@ -62,7 +62,8 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const handleLogin = async (data: LoginFormData) => {
try {
await login(data);
await login(data, { skipRedirect: true });
// await login(data);
onSuccess?.(data);
} catch (error: any) {
const message = getLoginErrorMessage(error);
@ -72,7 +73,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const onSubmit = async (data: LoginFormData) => {
try {
// onSuccess?.(data);
await handleLogin(data);
} catch (error: any) {
@ -193,6 +193,20 @@ export const LoginForm: React.FC<LoginFormProps> = ({
{/* Remember Me and Forgot Password */}
<div className="flex justify-between 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
id="rememberMe"
checked={rememberMe}
@ -202,7 +216,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
<Label htmlFor="rememberMe" className="text-sm">
{t("rememberMe")}
</Label>
</div>
</div> */}
<Link
href="/auth/forgot-password"
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 t = useTranslations("MediaUpdate");
const handleTypeOTP = (event: React.KeyboardEvent<HTMLInputElement>) => {
const { key } = event;
const target = event.currentTarget;
@ -57,9 +58,12 @@ export const OTPForm: React.FC<OTPFormProps> = ({
try {
const isValid = await verifyOTP(loginCredentials.username, otpValue);
if (isValid) {
onSuccess?.();
} else {
}
else {
onError?.("Invalid OTP code");
}
} catch (error: any) {
@ -154,7 +158,6 @@ export const OTPForm: React.FC<OTPFormProps> = ({
{loading ? (
<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>
) : (
t("enterOTP4")

View File

@ -33,6 +33,18 @@ interface ApprovalWorkflowFormProps {
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> = ({
initialData,
workflowId,
@ -41,31 +53,34 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
isLoading = false,
}) => {
// Form state
const [formData, setFormData] = useState<CreateApprovalWorkflowWithClientSettingsRequest>({
name: "",
description: "",
isActive: true,
isDefault: true,
requiresApproval: true,
autoPublish: false,
steps: [],
clientApprovalSettings: {
requiresApproval: true,
autoPublishArticles: false,
approvalExemptUsers: [],
approvalExemptRoles: [],
approvalExemptCategories: [],
requireApprovalFor: [],
skipApprovalFor: [],
const [formData, setFormData] =
useState<CreateApprovalWorkflowWithClientSettingsRequest>({
name: "",
description: "",
isActive: true,
},
});
isDefault: true,
requiresApproval: true,
autoPublish: false,
steps: [],
clientApprovalSettings: {
requiresApproval: true,
autoPublishArticles: false,
approvalExemptUsers: [],
approvalExemptRoles: [],
approvalExemptCategories: [],
requireApprovalFor: [],
skipApprovalFor: [],
isActive: true,
},
});
// API data
const [userLevels, setUserLevels] = useState<UserLevel[]>([]);
const [users, setUsers] = useState<User[]>([]);
const [userRoles, setUserRoles] = useState<UserRole[]>([]);
const [articleCategories, setArticleCategories] = useState<ArticleCategory[]>([]);
const [articleCategories, setArticleCategories] = useState<ArticleCategory[]>(
[],
);
// UI state
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)
const getAvailableUserLevels = (currentStepIndex: number) => {
const usedLevelIds = new Set<number>();
// Collect all user level IDs that are already used by other steps
formData.steps.forEach((step, stepIndex) => {
if (stepIndex !== currentStepIndex && step.conditionValue) {
try {
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) => {
usedLevelIds.add(levelId);
});
}
} catch (error) {
console.error('Error parsing conditionValue:', error);
console.error("Error parsing conditionValue:", error);
}
}
});
// 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
@ -108,17 +126,20 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
useEffect(() => {
const loadData = async () => {
try {
const [userLevelsRes, usersRes, userRolesRes, categoriesRes] = await Promise.all([
getUserLevels(),
getUsers(),
getUserRoles(),
getArticleCategories(),
]);
const [userLevelsRes, usersRes, userRolesRes, categoriesRes] =
await Promise.all([
getUserLevels(),
getUsers(),
getUserRoles(),
getArticleCategories(),
]);
if (!userLevelsRes?.error) setUserLevels(userLevelsRes?.data?.data || []);
if (!userLevelsRes?.error)
setUserLevels(userLevelsRes?.data?.data || []);
if (!usersRes?.error) setUsers(usersRes?.data || []);
if (!userRolesRes?.error) setUserRoles(userRolesRes?.data || []);
if (!categoriesRes?.error) setArticleCategories(categoriesRes?.data || []);
if (!categoriesRes?.error)
setArticleCategories(categoriesRes?.data || []);
} catch (error) {
console.error("Error loading form data:", error);
} finally {
@ -155,10 +176,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
newErrors[`steps.${index}.stepName`] = "Step name is required";
}
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) {
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
const handleBasicInfoChange = (field: keyof CreateApprovalWorkflowWithClientSettingsRequest, value: any) => {
setFormData(prev => ({ ...prev, [field]: value }));
const handleBasicInfoChange = (
field: keyof CreateApprovalWorkflowWithClientSettingsRequest,
value: any,
) => {
setFormData((prev) => ({ ...prev, [field]: value }));
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
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 stepErrors = Object.keys(errors).filter(key => key.startsWith(`steps.${index}`));
const renderStepForm = (
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)
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;
return (
<div className="space-y-4">
{/* Parallel Step Indicator */}
@ -222,7 +257,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
type="number"
placeholder="1"
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`]}
min={1}
helpText="Same order = parallel steps"
@ -243,17 +283,31 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
label="Required User Level"
name={`requiredUserLevelId-${index}`}
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}
onChange={(value) => onUpdate({ ...step, requiredUserLevelId: Number(value) })}
onChange={(value) =>
onUpdate({ ...step, requiredUserLevelId: Number(value) })
}
error={errors[`steps.${index}.requiredUserLevelId`]}
options={userLevels.length > 0 ? userLevels.map(level => ({
value: level.id,
label: `${level.name} (Level ${level.levelNumber})`,
})) : []}
options={
userLevels.length > 0
? userLevels.map((level) => ({
value: level.id,
label: `${level.name} (Level ${level.levelNumber})`,
}))
: []
}
required
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>
@ -264,7 +318,12 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
type="number"
placeholder="Leave empty for manual approval"
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"
min={1}
/>
@ -277,7 +336,7 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
value={step.canSkip || false}
onChange={(value) => onUpdate({ ...step, canSkip: value })}
/>
<FormField
label="Is Active"
name={`isActive-${index}`}
@ -294,32 +353,42 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
label="Applies to User Levels"
placeholder={(() => {
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={(() => {
const availableLevels = getAvailableUserLevels(index);
return availableLevels.map(level => ({
return availableLevels.map((level) => ({
value: level.id,
label: `${level.name} (Level ${level.levelNumber})`,
}));
})()}
value={(() => {
try {
return step.conditionValue ? JSON.parse(step.conditionValue).applies_to_levels || [] : [];
return step.conditionValue
? JSON.parse(step.conditionValue).applies_to_levels || []
: [];
} catch {
return [];
}
})()}
onChange={(value) => {
const conditionValue = JSON.stringify({ applies_to_levels: value });
onUpdate({ ...step, conditionType: "user_level_hierarchy", conditionValue });
const conditionValue = JSON.stringify({
applies_to_levels: value,
});
onUpdate({
...step,
conditionType: "user_level_hierarchy",
conditionValue,
});
}}
searchable={true}
disabled={getAvailableUserLevels(index).length === 0}
helpText={(() => {
const availableLevels = getAvailableUserLevels(index);
const usedLevels = userLevels.length - availableLevels.length;
if (availableLevels.length === 0) {
return "All user levels are already assigned to other steps";
} 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) => {
e.preventDefault();
if (!validateForm()) {
Swal.fire({
title: "Validation Error",
@ -344,14 +417,14 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "error",
confirmButtonText: "OK",
customClass: {
popup: 'swal-z-index-9999'
}
popup: "swal-z-index-9999",
},
});
return;
}
setIsSubmitting(true);
try {
// Hardcoded client approval settings
const hardcodedClientSettings = {
@ -362,7 +435,23 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
isActive: true,
requireApprovalFor: [],
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) {
@ -371,30 +460,60 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
workflowId,
name: formData.name,
description: formData.description,
isActive: formData.isActive || false,
isDefault: formData.isDefault || false,
steps: formData.steps.map(step => ({
isActive: normalizeBoolean(formData.isActive),
isDefault: normalizeBoolean(formData.isDefault),
steps: formData.steps.map((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);
const response = await updateApprovalWorkflowWithClientSettings(updateData);
const response =
await updateApprovalWorkflowWithClientSettings(updateData);
console.log("Update Response: ", response);
if (response?.error) {
Swal.fire({
title: "Error",
text: response?.message?.messages?.[0] || "Failed to update approval workflow",
text:
response?.message?.messages?.[0] ||
"Failed to update approval workflow",
icon: "error",
confirmButtonText: "OK",
customClass: {
popup: 'swal-z-index-9999'
}
popup: "swal-z-index-9999",
},
});
} else {
Swal.fire({
@ -403,8 +522,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "success",
confirmButtonText: "OK",
customClass: {
popup: 'swal-z-index-9999'
}
popup: "swal-z-index-9999",
},
}).then(() => {
// Call onSave to trigger parent refresh
if (onSave) {
@ -416,24 +535,27 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
// Create mode
const submitData = {
...formData,
clientApprovalSettings: hardcodedClientSettings
clientApprovalSettings: hardcodedClientSettings,
};
console.log("Create Data: ", submitData);
const response = await createApprovalWorkflowWithClientSettings(submitData);
const response =
await createApprovalWorkflowWithClientSettings(submitData);
console.log("Create Response: ", response);
if (response?.error) {
Swal.fire({
title: "Error",
text: response?.message?.messages?.[0] || "Failed to create approval workflow",
text:
response?.message?.messages?.[0] ||
"Failed to create approval workflow",
icon: "error",
confirmButtonText: "OK",
customClass: {
popup: 'swal-z-index-9999'
}
popup: "swal-z-index-9999",
},
});
} else {
Swal.fire({
@ -442,8 +564,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "success",
confirmButtonText: "OK",
customClass: {
popup: 'swal-z-index-9999'
}
popup: "swal-z-index-9999",
},
}).then(() => {
// Call onSave to trigger parent refresh
if (onSave) {
@ -460,8 +582,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
icon: "error",
confirmButtonText: "OK",
customClass: {
popup: 'swal-z-index-9999'
}
popup: "swal-z-index-9999",
},
});
} finally {
setIsSubmitting(false);
@ -477,8 +599,8 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
confirmButtonText: "Yes, reset",
cancelButtonText: "Cancel",
customClass: {
popup: 'swal-z-index-9999'
}
popup: "swal-z-index-9999",
},
}).then((result) => {
if (result.isConfirmed) {
setFormData({
@ -513,12 +635,18 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
<span className="ml-2 text-gray-600">Loading form data...</span>
</div>
)}
<Tabs defaultValue="basic" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="basic" disabled={isLoadingData}>Basic Information</TabsTrigger>
<TabsTrigger value="steps" disabled={isLoadingData}>Workflow Steps</TabsTrigger>
<TabsTrigger value="settings" disabled={isLoadingData}>Client Settings</TabsTrigger>
<TabsTrigger value="basic" disabled={isLoadingData}>
Basic Information
</TabsTrigger>
<TabsTrigger value="steps" disabled={isLoadingData}>
Workflow Steps
</TabsTrigger>
<TabsTrigger value="settings" disabled={isLoadingData}>
Client Settings
</TabsTrigger>
</TabsList>
{/* Basic Information Tab */}
@ -545,7 +673,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
type="textarea"
placeholder="Describe the purpose and process of this workflow"
value={formData.description}
onChange={(value) => handleBasicInfoChange("description", value)}
onChange={(value) =>
handleBasicInfoChange("description", value)
}
error={errors.description}
required
rows={4}
@ -565,7 +695,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
name="isDefault"
type="checkbox"
value={formData.isDefault}
onChange={(value) => handleBasicInfoChange("isDefault", value)}
onChange={(value) =>
handleBasicInfoChange("isDefault", value)
}
/>
<FormField
@ -573,7 +705,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
name="requiresApproval"
type="checkbox"
value={formData.requiresApproval}
onChange={(value) => handleBasicInfoChange("requiresApproval", value)}
onChange={(value) =>
handleBasicInfoChange("requiresApproval", value)
}
/>
<FormField
@ -581,7 +715,9 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
name="autoPublish"
type="checkbox"
value={formData.autoPublish}
onChange={(value) => handleBasicInfoChange("autoPublish", value)}
onChange={(value) =>
handleBasicInfoChange("autoPublish", value)
}
/>
</div>
</CardContent>
@ -595,14 +731,22 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
<CardTitle className="flex items-center justify-between">
<span>Workflow Steps Configuration</span>
<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) => {
acc[step.stepOrder] = (acc[step.stepOrder] || 0) + 1;
return acc;
}, {} as Record<number, number>);
const parallelCount = Object.values(parallelGroups).filter(count => count > 1).length;
return parallelCount > 0 ? `${parallelCount} parallel group${parallelCount !== 1 ? 's' : ''}` : '';
const parallelGroups = formData.steps.reduce(
(acc, step) => {
acc[step.stepOrder] = (acc[step.stepOrder] || 0) + 1;
return acc;
},
{} as Record<number, number>,
);
const parallelCount = Object.values(parallelGroups).filter(
(count) => count > 1,
).length;
return parallelCount > 0
? `${parallelCount} parallel group${parallelCount !== 1 ? "s" : ""}`
: "";
})()}
</div>
</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">
<span className="text-gray-400 text-xl"></span>
</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">
Client approval settings are automatically configured with optimal defaults.
Client approval settings are automatically configured with
optimal defaults.
</p>
</div>
<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">
<li> Requires Approval: <span className="font-medium text-green-600">Yes</span></li>
<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>
<li>
Requires Approval:{" "}
<span className="font-medium text-green-600">Yes</span>
</li>
<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>
</div>
</div>
@ -684,7 +851,7 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
Cancel
</Button>
)}
<Button
type="submit"
variant="outline"
@ -692,7 +859,13 @@ export const ApprovalWorkflowForm: React.FC<ApprovalWorkflowFormProps> = ({
className="flex items-center gap-2"
>
<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>
</div>
</div>

View File

@ -12,7 +12,7 @@ const Loader = () => {
<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" /> */}
<Image
src="/assets/mediahub-logo-min.png"
src="/assets/logo1.png"
alt=""
width={80}
height={80}

View File

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

View File

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

View File

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