fix: all create, edit dan detail content

This commit is contained in:
Sabda Yagra 2026-02-03 23:45:15 +07:00
parent 2b545ec51a
commit 82062bc3ad
41 changed files with 671 additions and 8435 deletions

View File

@ -12,7 +12,7 @@ const AudioTabs = () => {
<div className="w-full">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<div className="flex items-center justify-between mb-6">
<TabsList className="inline-flex h-10 bg-gray-50 border border-gray-200 p-1 rounded-lg shadow-sm">
{/* <TabsList className="inline-flex h-10 bg-gray-50 border border-gray-200 p-1 rounded-lg shadow-sm">
<TabsTrigger
value="submitted"
className="text-sm font-medium px-6 py-2 h-8 rounded-md transition-all duration-200 data-[state=active]:bg-white data-[state=active]:text-blue-600 data-[state=active]:shadow-sm data-[state=inactive]:text-gray-600 data-[state=inactive]:hover:text-gray-800"
@ -31,7 +31,7 @@ const AudioTabs = () => {
Waiting Approval
</div>
</TabsTrigger>
</TabsList>
</TabsList> */}
</div>
<TabsContent value="submitted" className="mt-0">
@ -40,11 +40,11 @@ const AudioTabs = () => {
</div>
</TabsContent>
<TabsContent value="pending" className="mt-0">
{/* <TabsContent value="pending" className="mt-0">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm">
<PendingApprovalTable typeId={4} />
</div>
</TabsContent>
</TabsContent> */}
</Tabs>
</div>
);

View File

@ -12,7 +12,7 @@ const DocumentTabs = () => {
<div className="w-full">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<div className="flex items-center justify-between mb-6">
<TabsList className="inline-flex h-10 bg-gray-50 border border-gray-200 p-1 rounded-lg shadow-sm">
{/* <TabsList className="inline-flex h-10 bg-gray-50 border border-gray-200 p-1 rounded-lg shadow-sm">
<TabsTrigger
value="submitted"
className="text-sm font-medium px-6 py-2 h-8 rounded-md transition-all duration-200 data-[state=active]:bg-white data-[state=active]:text-blue-600 data-[state=active]:shadow-sm data-[state=inactive]:text-gray-600 data-[state=inactive]:hover:text-gray-800"
@ -31,7 +31,7 @@ const DocumentTabs = () => {
Waiting Approval
</div>
</TabsTrigger>
</TabsList>
</TabsList> */}
</div>
<TabsContent value="submitted" className="mt-0">
@ -40,11 +40,11 @@ const DocumentTabs = () => {
</div>
</TabsContent>
<TabsContent value="pending" className="mt-0">
{/* <TabsContent value="pending" className="mt-0">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm">
<PendingApprovalTable typeId={3} />
</div>
</TabsContent>
</TabsContent> */}
</Tabs>
</div>
);

View File

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

View File

@ -750,7 +750,7 @@ function TenantSettingsContentTable() {
onOpenChange={setIsHierarchyExpanded}
>
<CollapsibleTrigger asChild>
<CardHeader className="cursor-pointer hover:bg-gray-50 dark:hover:bg-black transition-colors">
<CardHeader className="cursor-pointer hover:bg-gray-50 transition-colors">
<CardTitle className="flex items-center justify-between">
<div className="flex items-center gap-2">
<UsersIcon className="h-5 w-5" />

View File

@ -359,11 +359,12 @@ export default function FormVideoDetail() {
</div>
</div>
<div className="px-3 py-3">
<div className="px-3 py-3 gap-2">
<Label>Publish Target</Label>
{[5, 6].map((target) => (
<div key={target} className="flex items-center gap-2">
<Checkbox
<input
type="checkbox"
id={String(target)}
checked={selectedPublishers.includes(target)}
onChange={() => handleCheckboxChange(target)}

View File

@ -379,7 +379,7 @@ type Option = {
label: string;
};
export default function FormVideo() {
export default function FormVideo() {
const MySwal = withReactContent(Swal);
const router = useRouter();
const editor = useRef(null);

View File

@ -43,7 +43,7 @@ import { TimeIcon, TimesIcon } from "@/components/icons";
const CustomEditor = dynamic(
() => import("@/components/editor/custom-editor"),
{ ssr: false }
{ ssr: false },
);
const videoSchema = z.object({
@ -59,12 +59,12 @@ const videoSchema = z.object({
files.every(
(file: File) =>
["video/mp4", "video/mov", "video/avi"].includes(file.type) &&
file.size <= 100 * 1024 * 1024
file.size <= 100 * 1024 * 1024,
),
{
message:
"Hanya file .mp4, .mov, .avi, maksimal 100MB yang diperbolehkan.",
}
},
),
publishedFor: z
.array(z.string())
@ -129,7 +129,7 @@ export default function FormVideoUpdate() {
}
const filesWithPreview = acceptedFiles.map((file) =>
Object.assign(file, { preview: URL.createObjectURL(file) })
Object.assign(file, { preview: URL.createObjectURL(file) }),
);
setFiles((prev) => {
@ -201,7 +201,7 @@ export default function FormVideoUpdate() {
const handleCheckboxChange = (value: string) => {
setPublishedFor((prev) =>
prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]
prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value],
);
};
@ -210,20 +210,31 @@ export default function FormVideoUpdate() {
loading();
const payload = {
aiArticleId: detail?.aiArticleId ?? "",
categoryIds: selectedCategory ?? "",
createdById: detail?.createdById ?? "",
aiArticleId: detail.aiArticleId,
categoryIds: selectedCategory,
description: htmlToString(data.description),
htmlDescription: data.description,
isDraft: false,
isPublish: true,
slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
statusId: detail?.statusId ?? 1,
tags: tags.join(","),
title: data.title,
typeId: detail?.typeId ?? 2,
typeId: detail.typeId,
slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
};
// const payload = {
// aiArticleId: detail?.aiArticleId ?? "",
// categoryIds: selectedCategory ?? "",
// createdById: detail?.createdById ?? "",
// description: htmlToString(data.description),
// htmlDescription: data.description,
// isDraft: false,
// isPublish: true,
// slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
// statusId: detail?.statusId ?? 1,
// tags: tags.join(","),
// title: data.title,
// typeId: detail?.typeId ?? 2,
// };
console.log("📤 Payload Update:", payload);
const res = await updateArticle(Number(id), payload);
@ -399,7 +410,7 @@ export default function FormVideoUpdate() {
<Controller
control={control}
name="creatorName"
render={({ field }) => <Input {...field} />}
render={({ field }) => <Input {...field} />}
/>
<div className="mt-3 space-y-2">
@ -470,23 +481,19 @@ export default function FormVideoUpdate() {
? isAllChecked
: field.value.includes(option.id);
const handleChange = () => {
const handleChange = (checked: boolean) => {
let updated: string[] = [];
if (option.id === "all") {
updated = isAllChecked
? []
: options
updated = checked
? options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id);
.map((opt: any) => opt.id)
: [];
} else {
updated = isChecked
? field.value.filter((val) => val !== option.id)
: [...field.value, option.id];
if (isAllChecked && option.id !== "all") {
updated = updated.filter((val) => val !== "all");
}
updated = checked
? [...field.value, option.id]
: field.value.filter((val) => val !== option.id);
}
field.onChange(updated);
@ -498,12 +505,20 @@ export default function FormVideoUpdate() {
key={option.id}
className="flex gap-2 items-center"
>
<Checkbox
<input
type="checkbox"
id={option.id}
checked={isChecked}
onChange={(e) => handleChange(e.target.checked)}
className="border"
/>
{/* <Checkbox
id={option.id}
checked={isChecked}
onCheckedChange={handleChange}
className="border"
/>
/> */}
<Label htmlFor={option.id}>{option.label}</Label>
</div>
);
@ -521,14 +536,19 @@ export default function FormVideoUpdate() {
</div>
<div className="flex justify-end gap-3 mt-5">
<Button type="submit" className="border rounded-lg">Update</Button>
<Button
<button
type="submit"
className="border border-black hover:bg-black hover:text-white rounded-lg px-8 py-4"
>
Update
</button>
<button
type="button"
variant="outline"
className="border border-black hover:bg-black hover:text-white rounded-lg px-8 py-4"
onClick={() => router.back()}
>
Cancel
</Button>
</button>
</div>
</Card>
</div>

View File

@ -84,9 +84,10 @@ type Category = {
type FileType = {
id: number;
secondaryUrl: string;
thumbnailFileUrl: string;
fileName: string;
secondaryUrl?: string | null;
fileUrl?: string | null;
preview?: string;
};
type Detail = {
@ -526,6 +527,47 @@ export default function FormAudioDetail() {
<div className="w-full">
<Label className="text-xl space-y-2">File Media</Label>
<div className="space-y-4 mt-4">
{files.length === 0 ? (
<p className="text-center text-gray-500">
Tidak ada file media
</p>
) : (
files.map((file, idx) => {
const audioSrc = file.secondaryUrl || file.fileUrl;
return (
<div
key={idx}
className="flex flex-col gap-2 border p-2 rounded-md"
>
<p className="text-sm font-medium truncate">
{file.fileName}
</p>
{audioSrc ? (
<audio
controls
src={audioSrc}
className="w-full rounded"
>
Browser tidak mendukung audio.
</audio>
) : (
<p className="text-xs text-red-500">
Audio source tidak tersedia
</p>
)}
</div>
);
})
)}
</div>
</div>
{/* <div className="w-full">
<Label className="text-xl space-y-2">File Media</Label>
<div className="w-full">
{files.length === 0 ? (
<p className="text-center text-gray-500">
@ -541,7 +583,7 @@ export default function FormAudioDetail() {
))
)}
</div>
</div>
</div> */}
</div>
</div>
</Card>

View File

@ -82,7 +82,7 @@ const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
{ ssr: false },
);
export default function FormAudio() {
@ -118,7 +118,7 @@ export default function FormAudio() {
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
null,
);
const [selectedMainKeyword, setSelectedMainKeyword] = useState("");
const [selectedSize, setSelectedSize] = useState("");
@ -149,7 +149,7 @@ export default function FormAudio() {
type FileWithPreview = File & { preview: string };
const userId = Cookies.get("userId");
const options: Option[] = [
const options: Option[] = [
{ id: "all", label: "SEMUA" },
{ id: "4", label: "UMUM" },
{ id: "5", label: "JOURNALIS" },
@ -181,7 +181,7 @@ export default function FormAudio() {
}
const filesWithPreview = acceptedFiles.map((file) =>
Object.assign(file, { preview: URL.createObjectURL(file) })
Object.assign(file, { preview: URL.createObjectURL(file) }),
);
setFiles((prevFiles) => [...prevFiles, ...filesWithPreview]);
@ -216,11 +216,11 @@ export default function FormAudio() {
files.every(
(file: File) =>
["audio/mpeg", "audio/wav", "audio/mp3"].includes(file.type) &&
file.size <= 100 * 1024 * 1024
file.size <= 100 * 1024 * 1024,
),
{
message: "Hanya file .mp3, .wav, maksimal 100MB yang diperbolehkan.",
}
},
),
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
tags: z
@ -430,7 +430,7 @@ export default function FormAudio() {
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
"",
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
@ -504,7 +504,7 @@ export default function FormAudio() {
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
o.name.toLowerCase().includes("pers rilis"),
);
if (findCategory) {
@ -537,7 +537,7 @@ export default function FormAudio() {
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
.map((opt: any) => opt.id),
);
}
} else {
@ -569,8 +569,8 @@ export default function FormAudio() {
const finalDescription = isSwitchOn
? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
? data.rewriteDescription
: data.descriptionOri;
if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
return;
@ -661,7 +661,7 @@ export default function FormAudio() {
MySwal.fire(
"Error",
response.message || "Failed to create article",
"error"
"error",
);
return false;
}
@ -689,7 +689,7 @@ export default function FormAudio() {
MySwal.fire(
"Error",
uploadResponse.message || "Failed to upload files",
"error"
"error",
);
return false;
}
@ -706,19 +706,19 @@ export default function FormAudio() {
try {
const thumbnailResponse = await uploadArticleThumbnail(
articleId,
thumbnailFormData
thumbnailFormData,
);
if (thumbnailResponse?.error) {
console.warn(
"Thumbnail upload failed:",
thumbnailResponse.message
thumbnailResponse.message,
);
// Don't fail the whole process if thumbnail upload fails
} else {
console.log(
"Thumbnail uploaded successfully:",
thumbnailResponse
thumbnailResponse,
);
}
} catch (thumbnailError) {
@ -731,7 +731,7 @@ export default function FormAudio() {
MySwal.fire(
"Error",
"Failed to upload files. Please try again.",
"error"
"error",
);
return false;
}
@ -774,7 +774,7 @@ export default function FormAudio() {
idx: number,
id: string,
file: any,
duration: string
duration: string,
) {
console.log(idx, id, file, duration);
@ -810,7 +810,7 @@ export default function FormAudio() {
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
bytesTotal: any,
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen;
@ -1053,6 +1053,36 @@ export default function FormAudio() {
<div className="flex flex-row items-center gap-3 py-2">
<Label>Ai Assistance</Label>
<div className="flex items-center gap-3">
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={isSwitchOn}
onChange={(e) => setIsSwitchOn(e.target.checked)}
className="sr-only peer"
/>
<div
className="
w-11 h-6
bg-gray-300
rounded-full
peer
peer-checked:bg-blue-600
transition-colors
after:content-['']
after:absolute
after:top-[2px]
after:left-[2px]
after:bg-white
after:rounded-full
after:h-5
after:w-5
after:transition-transform
peer-checked:after:translate-x-5
"
/>
</label>
</div>
{/* <div className="flex items-center gap-3">
<Switch
defaultChecked={isSwitchOn}
color="primary"
@ -1061,7 +1091,7 @@ export default function FormAudio() {
setIsSwitchOn(checked)
}
/>
</div>
</div> */}
</div>
{isSwitchOn && (
<div>
@ -1564,19 +1594,19 @@ export default function FormAudio() {
? isAllChecked
: field.value.includes(option.id);
const handleChange = () => {
const handleChange = (checked: boolean) => {
let updated: string[] = [];
if (option.id === "all") {
updated = isAllChecked
? []
: options
updated = checked
? options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id);
.map((opt: any) => opt.id)
: [];
} else {
updated = isChecked
? field.value.filter((val) => val !== option.id)
: [...field.value, option.id];
updated = checked
? [...field.value, option.id]
: field.value.filter((val) => val !== option.id);
if (isAllChecked && option.id !== "all") {
updated = updated.filter((val) => val !== "all");
@ -1587,17 +1617,47 @@ export default function FormAudio() {
setPublishedFor(updated);
};
// const handleChange = () => {
// let updated: string[] = [];
// if (option.id === "all") {
// updated = isAllChecked
// ? []
// : options
// .filter((opt: any) => opt.id !== "all")
// .map((opt: any) => opt.id);
// } else {
// updated = isChecked
// ? field.value.filter((val) => val !== option.id)
// : [...field.value, option.id];
// if (isAllChecked && option.id !== "all") {
// updated = updated.filter((val) => val !== "all");
// }
// }
// field.onChange(updated);
// setPublishedFor(updated);
// };
return (
<div
key={option.id}
className="flex gap-2 items-center"
>
<Checkbox
<input
type="checkbox"
id={option.id}
checked={isChecked}
onChange={(e) => handleChange(e.target.checked)}
className="h-4 w-4 border border-gray-300 rounded text-blue-600 focus:ring-blue-500"
/>
{/* <Checkbox
id={option.id}
checked={isChecked}
onCheckedChange={handleChange}
className="border"
/>
/> */}
<Label htmlFor={option.id}>{option.label}</Label>
</div>
);

View File

@ -71,12 +71,12 @@ const audioSchema = z.object({
"audio/x-wav",
"audio/mp4",
"audio/aac",
].includes(file.type) && file.size <= 20 * 1024 * 1024
].includes(file.type) && file.size <= 20 * 1024 * 1024,
),
{
message:
"Hanya file audio (.mp3, .wav, .m4a, .aac) dengan ukuran maksimal 20MB yang diperbolehkan.",
}
},
),
publishedFor: z
@ -118,7 +118,7 @@ const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
{ ssr: false },
);
export default function FormAudioUpdate() {
@ -232,7 +232,7 @@ export default function FormAudioUpdate() {
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
prevTags.map((tag, i) => (i === index ? newValue : tag)),
);
};
@ -283,7 +283,7 @@ export default function FormAudioUpdate() {
const filesData = details.files || [];
const fileUrls = filesData.map((file: { secondaryUrl: string }) =>
file.secondaryUrl ? file.secondaryUrl : "default-image.jpg"
file.secondaryUrl ? file.secondaryUrl : "default-image.jpg",
);
setDetailThumb(fileUrls);
}
@ -310,7 +310,7 @@ export default function FormAudioUpdate() {
.filter(Boolean);
const allSelected = ["nasional", "wilayah", "internasional"].every((opt) =>
options.includes(opt)
options.includes(opt),
);
return allSelected ? ["all", ...options] : options;
@ -323,12 +323,12 @@ export default function FormAudioUpdate() {
.filter((opt) => opt.id !== "all")
.map((opt) => opt.id);
setPublishedFor(
publishedFor.length === allOptions.length ? [] : allOptions
publishedFor.length === allOptions.length ? [] : allOptions,
);
} else {
// Toggle individual option
setPublishedFor((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
);
}
};
@ -352,20 +352,31 @@ export default function FormAudioUpdate() {
// isYoutube: false,
// isInternationalMedia: false,
// };
const payload = {
aiArticleId: detail?.aiArticleId ?? "",
categoryIds: selectedCategory ?? "",
createdById: detail?.createdById ?? "",
aiArticleId: detail.aiArticleId,
categoryIds: selectedCategory,
description: htmlToString(data.description),
htmlDescription: data.description,
isDraft: false,
isPublish: true,
slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
statusId: detail?.statusId ?? 1,
tags: tags.join(","),
title: data.title,
typeId: detail?.typeId ?? 4,
typeId: detail.typeId,
slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
};
// const payload = {
// aiArticleId: detail?.aiArticleId ?? "",
// categoryIds: selectedCategory ?? "",
// createdById: detail?.createdById ?? "",
// description: htmlToString(data.description),
// htmlDescription: data.description,
// isDraft: false,
// isPublish: true,
// slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
// statusId: detail?.statusId ?? 1,
// tags: tags.join(","),
// title: data.title,
// typeId: detail?.typeId ?? 4,
// };
const res = await updateArticle(Number(id), payload);
if (res?.error) {
@ -421,7 +432,7 @@ export default function FormAudioUpdate() {
idx: number,
id: string,
file: any,
duration: string
duration: string,
) {
console.log(idx, id, file, duration);
@ -458,7 +469,7 @@ export default function FormAudioUpdate() {
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
bytesTotal: any,
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen;
@ -614,7 +625,7 @@ export default function FormAudioUpdate() {
// If all individual options are selected, include "all" automatically
const isAllSelected = ["nasional", "wilayah", "internasional"].every(
(opt) => updatedSelections.includes(opt)
(opt) => updatedSelections.includes(opt),
);
return {
...prev,
@ -667,7 +678,7 @@ export default function FormAudioUpdate() {
// Jika berhasil, hapus file dari state lokal
setFiles((prevFiles: any) =>
prevFiles.filter((file: any) => file.id !== id)
prevFiles.filter((file: any) => file.id !== id),
);
success();
} catch (err) {
@ -812,7 +823,7 @@ export default function FormAudioUpdate() {
) : (
<>
{(Math.round(file.size / 100) / 10).toFixed(
1
1,
)}
</>
)}
@ -1029,13 +1040,13 @@ export default function FormAudioUpdate() {
} else {
updated = isChecked
? field.value.filter(
(val) => val !== option.id
(val) => val !== option.id,
)
: [...field.value, option.id];
if (isAllChecked && option.id !== "all") {
updated = updated.filter(
(val) => val !== "all"
(val) => val !== "all",
);
}
}

View File

@ -73,7 +73,7 @@ const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
{ ssr: false },
);
type Option = {
@ -114,7 +114,7 @@ export default function FormTeks() {
const [isGeneratedArticle, setIsGeneratedArticle] = useState(false);
const [articleBody, setArticleBody] = useState<string>("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
null,
);
const [isContentRewriteClicked, setIsContentRewriteClicked] = useState(false);
const [showRewriteEditor, setShowRewriteEditor] = useState(false);
@ -168,13 +168,13 @@ export default function FormTeks() {
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
].includes(file.type) && file.size <= 20 * 1024 * 1024
].includes(file.type) && file.size <= 20 * 1024 * 1024,
);
const filesWithPreview = filtered.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
}),
);
setFiles(filesWithPreview);
@ -200,12 +200,12 @@ export default function FormTeks() {
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain",
].includes(file.type) && file.size <= 100 * 1024 * 1024
].includes(file.type) && file.size <= 100 * 1024 * 1024,
),
{
message:
"Hanya file .pdf, .doc, .docx, .txt, maksimal 100MB yang diperbolehkan.",
}
},
),
categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }),
tags: z
@ -420,7 +420,7 @@ export default function FormTeks() {
const articleData = await waitForStatusUpdate();
const cleanArticleBody = articleData?.articleBody?.replace(
/<img[^>]*>/g,
""
"",
);
const articleImagesData = articleData?.imagesUrl?.split(",");
setArticleBody(cleanArticleBody || "");
@ -500,7 +500,7 @@ export default function FormTeks() {
if (scheduleId && scheduleType === "3") {
const findCategory = resCategory.find((o) =>
o.name.toLowerCase().includes("pers rilis")
o.name.toLowerCase().includes("pers rilis"),
);
if (findCategory) {
@ -533,7 +533,7 @@ export default function FormTeks() {
setPublishedFor(
options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id)
.map((opt: any) => opt.id),
);
}
} else {
@ -572,8 +572,8 @@ export default function FormTeks() {
const finalDescription = isSwitchOn
? data.description
: selectedFileType === "rewrite"
? data.rewriteDescription
: data.descriptionOri;
? data.rewriteDescription
: data.descriptionOri;
if (!finalDescription?.trim()) {
MySwal.fire("Error", "Deskripsi tidak boleh kosong.", "error");
@ -637,10 +637,10 @@ export default function FormTeks() {
if (id == undefined) {
const articleData: CreateArticleData = {
aiArticleId: 0,
aiArticleId: 0,
categoryIds: selectedCategory.toString(),
createdAt: formatDateForBackend(new Date()),
createdById: Number(userId),
createdAt: formatDateForBackend(new Date()),
createdById: Number(userId),
description: htmlToString(finalDescription),
htmlDescription: finalDescription,
isDraft: true,
@ -652,7 +652,7 @@ export default function FormTeks() {
.replace(/[^a-z0-9-]/g, ""),
tags: finalTags,
title: finalTitle,
typeId: 3,
typeId: 3,
};
const response = await createArticle(articleData);
console.log("Article Data Submitted:", articleData);
@ -662,7 +662,7 @@ export default function FormTeks() {
MySwal.fire(
"Error",
response.message || "Failed to create article",
"error"
"error",
);
return false;
}
@ -686,7 +686,7 @@ export default function FormTeks() {
MySwal.fire(
"Error",
uploadResponse.message || "Failed to upload files",
"error"
"error",
);
return false;
}
@ -702,18 +702,18 @@ export default function FormTeks() {
try {
const thumbnailResponse = await uploadArticleThumbnail(
articleId,
thumbnailFormData
thumbnailFormData,
);
if (thumbnailResponse?.error) {
console.warn(
"Thumbnail upload failed:",
thumbnailResponse.message
thumbnailResponse.message,
);
} else {
console.log(
"Thumbnail uploaded successfully:",
thumbnailResponse
thumbnailResponse,
);
}
} catch (thumbnailError) {
@ -725,7 +725,7 @@ export default function FormTeks() {
MySwal.fire(
"Error",
"Failed to upload files. Please try again.",
"error"
"error",
);
return false;
}
@ -767,7 +767,7 @@ export default function FormTeks() {
idx: number,
id: string,
file: any,
duration: string
duration: string,
) {
console.log(idx, id, file, duration);
@ -804,7 +804,7 @@ export default function FormTeks() {
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
bytesTotal: any,
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen;
@ -1036,6 +1036,36 @@ export default function FormTeks() {
<div className="flex flex-row items-center gap-3 py-2">
<Label>Ai Assistance</Label>
<div className="flex items-center gap-3">
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={isSwitchOn}
onChange={(e) => setIsSwitchOn(e.target.checked)}
className="sr-only peer"
/>
<div
className="
w-11 h-6
bg-gray-300
rounded-full
peer
peer-checked:bg-blue-600
transition-colors
after:content-['']
after:absolute
after:top-[2px]
after:left-[2px]
after:bg-white
after:rounded-full
after:h-5
after:w-5
after:transition-transform
peer-checked:after:translate-x-5
"
/>
</label>
</div>
{/* <div className="flex items-center gap-3">
<Switch
defaultChecked={isSwitchOn}
color="primary"
@ -1044,7 +1074,7 @@ export default function FormTeks() {
setIsSwitchOn(checked)
}
/>
</div>
</div> */}
</div>
{isSwitchOn && (
<div>
@ -1517,19 +1547,42 @@ export default function FormTeks() {
? isAllChecked
: field.value.includes(option.id);
const handleChange = () => {
// const handleChange = () => {
// let updated: string[] = [];
// if (option.id === "all") {
// updated = isAllChecked
// ? []
// : options
// .filter((opt: any) => opt.id !== "all")
// .map((opt: any) => opt.id);
// } else {
// updated = isChecked
// ? field.value.filter((val) => val !== option.id)
// : [...field.value, option.id];
// if (isAllChecked && option.id !== "all") {
// updated = updated.filter((val) => val !== "all");
// }
// }
// field.onChange(updated);
// setPublishedFor(updated);
// };
const handleChange = (checked: boolean) => {
let updated: string[] = [];
if (option.id === "all") {
updated = isAllChecked
? []
: options
updated = checked
? options
.filter((opt: any) => opt.id !== "all")
.map((opt: any) => opt.id);
.map((opt: any) => opt.id)
: [];
} else {
updated = isChecked
? field.value.filter((val) => val !== option.id)
: [...field.value, option.id];
updated = checked
? [...field.value, option.id]
: field.value.filter((val) => val !== option.id);
if (isAllChecked && option.id !== "all") {
updated = updated.filter((val) => val !== "all");
@ -1545,12 +1598,19 @@ export default function FormTeks() {
key={option.id}
className="flex gap-2 items-center"
>
<Checkbox
<input
type="checkbox"
id={option.id}
checked={isChecked}
onChange={(e) => handleChange(e.target.checked)}
className="h-4 w-4 border border-gray-300 rounded text-blue-600 focus:ring-blue-500"
/>
{/* <Checkbox
id={option.id}
checked={isChecked}
onCheckedChange={handleChange}
className="border"
/>
/> */}
<Label htmlFor={option.id}>{option.label}</Label>
</div>
);

View File

@ -70,12 +70,12 @@ const teksSchema = z.object({
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain",
].includes(file.type) && file.size <= 10 * 1024 * 1024
].includes(file.type) && file.size <= 10 * 1024 * 1024,
),
{
message:
"Hanya file .pdf, .doc, .docx, atau .txt dengan ukuran maksimal 10MB yang diperbolehkan.",
}
},
),
publishedFor: z
@ -121,7 +121,7 @@ const CustomEditor = dynamic(
() => {
return import("@/components/editor/custom-editor");
},
{ ssr: false }
{ ssr: false },
);
export default function FormTeksUpdate() {
@ -273,7 +273,7 @@ export default function FormTeksUpdate() {
.filter(Boolean);
const allSelected = ["nasional", "wilayah", "internasional"].every((opt) =>
options.includes(opt)
options.includes(opt),
);
return allSelected ? ["all", ...options] : options;
@ -286,12 +286,12 @@ export default function FormTeksUpdate() {
.filter((opt) => opt.id !== "all")
.map((opt) => opt.id);
setPublishedFor(
publishedFor.length === allOptions.length ? [] : allOptions
publishedFor.length === allOptions.length ? [] : allOptions,
);
} else {
// Toggle individual option
setPublishedFor((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id],
);
}
};
@ -316,20 +316,31 @@ export default function FormTeksUpdate() {
// isYoutube: false,
// isInternationalMedia: false,
// };
const payload = {
aiArticleId: detail?.aiArticleId ?? "",
aiArticleId: detail.aiArticleId,
categoryIds: selectedCategory,
createdById: detail?.createdById ?? "",
description: htmlToString(data.description),
htmlDescription: data.description,
isDraft: false,
isPublish: true,
slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
statusId: detail?.statusId ?? 1,
tags: tags.join(","),
title: data.title,
typeId: detail?.typeId ?? 3,
typeId: detail.typeId,
slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
};
// const payload = {
// aiArticleId: detail?.aiArticleId ?? "",
// categoryIds: selectedCategory,
// createdById: detail?.createdById ?? "",
// description: htmlToString(data.description),
// htmlDescription: data.description,
// isDraft: false,
// isPublish: true,
// slug: detail?.slug ?? data.title.toLowerCase().replace(/\s+/g, "-"),
// statusId: detail?.statusId ?? 1,
// tags: tags.join(","),
// title: data.title,
// typeId: detail?.typeId ?? 3,
// };
const res = await updateArticle(Number(id), payload);
if (res?.error) {
@ -386,7 +397,7 @@ export default function FormTeksUpdate() {
idx: number,
id: string,
file: any,
duration: string
duration: string,
) {
console.log(idx, id, file, duration);
@ -424,7 +435,7 @@ export default function FormTeksUpdate() {
onChunkComplete: (
chunkSize: any,
bytesAccepted: any,
bytesTotal: any
bytesTotal: any,
) => {
const uploadPersen = Math.floor((bytesAccepted / bytesTotal) * 100);
progressInfo[idx].percentage = uploadPersen;
@ -568,7 +579,7 @@ export default function FormTeksUpdate() {
// If all individual options are selected, include "all" automatically
const isAllSelected = ["nasional", "wilayah", "internasional"].every(
(opt) => updatedSelections.includes(opt)
(opt) => updatedSelections.includes(opt),
);
return {
...prev,
@ -599,7 +610,7 @@ export default function FormTeksUpdate() {
const handleEditTag = (index: number, newValue: string) => {
setTags((prevTags) =>
prevTags.map((tag, i) => (i === index ? newValue : tag))
prevTags.map((tag, i) => (i === index ? newValue : tag)),
);
};
@ -692,12 +703,12 @@ export default function FormTeksUpdate() {
<Fragment>
<div>{fileList}</div>
<div className=" flex justify-between gap-2">
<div className="flex flex-row items-center gap-3 py-3">
{/* <div className="flex flex-row items-center gap-3 py-3">
<Label>Watermark</Label>
<div className="flex items-center gap-3">
<Switch defaultChecked color="primary" id="c2" />
</div>
</div>
</div> */}
<Button
color="destructive"
onClick={handleRemoveAllFiles}
@ -708,111 +719,112 @@ export default function FormTeksUpdate() {
</Fragment>
) : null}
{files.length > 0 && (
<div className="mt-4 space-y-2">
<Label className="text-lg font-semibold">
{" "}
File Media
</Label>
<div className="grid gap-4">
{files.map((file: any) => (
<div
key={file.id}
className="flex items-center border p-2 rounded-md"
>
<img
src={file.thumbnailFileUrl}
alt={file.fileName}
className="w-16 h-16 object-cover rounded-md mr-4"
/>
<div className="flex flex-wrap gap-3 items-center ">
<div className="flex-grow">
<p className="font-medium">{file.fileName}</p>
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 text-sm"
>
View File
</a>
</div>
<div>
<Label className="flex items-center space-x-2">
<input
type="checkbox"
checked={selectedOptions[
file.id
]?.includes("all")}
onChange={() =>
handleCheckboxChangeImage(
file.id,
"all"
)
}
className="form-checkbox"
/>
<span>All</span>
</Label>
</div>
<div>
<Label className="flex items-center space-x-2">
<input
type="checkbox"
checked={selectedOptions[
file.id
]?.includes("nasional")}
onChange={() =>
handleCheckboxChangeImage(
file.id,
"nasional"
)
}
className="form-checkbox"
/>
<span>Nasional</span>
</Label>
</div>
<div>
<Label className="flex items-center space-x-2">
<input
type="checkbox"
checked={selectedOptions[
file.id
]?.includes("wilayah")}
onChange={() =>
handleCheckboxChangeImage(
file.id,
"wilayah"
)
}
className="form-checkbox"
/>
<span>Wilayah</span>
</Label>
</div>
<div>
<Label className="flex items-center space-x-2">
<input
type="checkbox"
checked={selectedOptions[
file.id
]?.includes("internasional")}
onChange={() =>
handleCheckboxChangeImage(
file.id,
"internasional"
)
}
className="form-checkbox"
/>
<span>Internasional</span>
</Label>
</div>
</div>
</div>
))}
</div>
</div>
<></>
// <div className="mt-4 space-y-2">
// <Label className="text-lg font-semibold">
// {" "}
// File Media
// </Label>
// <div className="grid gap-4">
// {files.map((file: any) => (
// <div
// key={file.id}
// className="flex items-center border p-2 rounded-md"
// >
// <img
// src={file.thumbnailFileUrl}
// alt={file.fileName}
// className="w-16 h-16 object-cover rounded-md mr-4"
// />
// <div className="flex flex-wrap gap-3 items-center ">
// <div className="flex-grow">
// <p className="font-medium">{file.fileName}</p>
// <a
// href={file.url}
// target="_blank"
// rel="noopener noreferrer"
// className="text-blue-500 text-sm"
// >
// View File
// </a>
// </div>
// <div>
// <Label className="flex items-center space-x-2">
// <input
// type="checkbox"
// checked={selectedOptions[
// file.id
// ]?.includes("all")}
// onChange={() =>
// handleCheckboxChangeImage(
// file.id,
// "all"
// )
// }
// className="form-checkbox"
// />
// <span>All</span>
// </Label>
// </div>
// <div>
// <Label className="flex items-center space-x-2">
// <input
// type="checkbox"
// checked={selectedOptions[
// file.id
// ]?.includes("nasional")}
// onChange={() =>
// handleCheckboxChangeImage(
// file.id,
// "nasional"
// )
// }
// className="form-checkbox"
// />
// <span>Nasional</span>
// </Label>
// </div>
// <div>
// <Label className="flex items-center space-x-2">
// <input
// type="checkbox"
// checked={selectedOptions[
// file.id
// ]?.includes("wilayah")}
// onChange={() =>
// handleCheckboxChangeImage(
// file.id,
// "wilayah"
// )
// }
// className="form-checkbox"
// />
// <span>Wilayah</span>
// </Label>
// </div>
// <div>
// <Label className="flex items-center space-x-2">
// <input
// type="checkbox"
// checked={selectedOptions[
// file.id
// ]?.includes("internasional")}
// onChange={() =>
// handleCheckboxChangeImage(
// file.id,
// "internasional"
// )
// }
// className="form-checkbox"
// />
// <span>Internasional</span>
// </Label>
// </div>
// </div>
// </div>
// ))}
// </div>
// </div>
)}
</Fragment>
</div>
@ -914,13 +926,13 @@ export default function FormTeksUpdate() {
} else {
updated = isChecked
? field.value.filter(
(val) => val !== option.id
(val) => val !== option.id,
)
: [...field.value, option.id];
if (isAllChecked && option.id !== "all") {
updated = updated.filter(
(val) => val !== "all"
(val) => val !== "all",
);
}
}

View File

@ -1119,6 +1119,37 @@ export default function FormImageDetail() {
</DialogContent>
</Dialog>
</Card>
{/* {(Number(detail.needApprovalFromLevel || 0) ==
Number(userLevelId) ||
(detail.isPublish === false && detail.statusId == 1)) &&
Number(detail.uploadedById || detail.createdById) !=
Number(userId) ? (
<div className="flex flex-col gap-2 p-3">
<Button
onClick={() => actionApproval("2")}
color="primary"
type="button"
>
<Icon icon="fa:check" className="mr-3" /> Accept
</Button>
<Button
onClick={() => actionApproval("3")}
className="bg-orange-400 hover:bg-orange-300"
type="button"
>
<Icon icon="fa:comment-o" className="mr-3" /> Revision
</Button>
<Button
onClick={() => actionApproval("4")}
color="destructive"
type="button"
>
<Icon icon="fa:times" className="mr-3" /> Reject
</Button>
</div>
) : null} */}
{/* {Number(detail?.needApprovalFromLevel || 0) ==
Number(userLevelId) ||
(detail?.isInternationalMedia == true &&

View File

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

View File

@ -1,338 +0,0 @@
"use client";
import { useState, useCallback, useEffect } from "react";
import { useRouter } from "@/components/navigation";
import { toast } from "sonner";
import {
LoginFormData,
ProfileData,
AuthState,
AuthContextType,
EmailValidationData,
OTPData,
} from "@/types/auth";
import {
login,
getProfile,
postEmailValidation,
postSetupEmail,
verifyOTPByUsername,
doLogin,
} from "@/service/auth";
import {
setAuthCookies,
setProfileCookies,
clearAllCookies,
isUserEligible,
isProtectedRole,
getNavigationPath,
showAuthError,
showAuthSuccess,
loginRateLimiter,
AUTH_CONSTANTS,
} from "@/lib/auth-utils";
import { warning } from "@/lib/swal";
export const useAuth = (): AuthContextType => {
const router = useRouter();
const [state, setState] = useState<AuthState>({
isAuthenticated: false,
user: null,
loading: false,
error: null,
});
// Check if user is authenticated on mount
useEffect(() => {
const checkAuth = async () => {
try {
setState((prev) => ({ ...prev, loading: true }));
// Add logic to check if user is authenticated
// This could check for valid tokens, etc.
} catch (error) {
setState((prev) => ({
...prev,
isAuthenticated: false,
user: null,
error: "Authentication check failed",
}));
} finally {
setState((prev) => ({ ...prev, loading: false }));
}
};
checkAuth();
}, []);
const login = useCallback(
async (credentials: LoginFormData): Promise<void> => {
try {
setState((prev) => ({ ...prev, loading: true, error: null }));
// Check rate limiting
if (!loginRateLimiter.canAttempt(credentials.username)) {
const remainingTime = loginRateLimiter.getRemainingTime(
credentials.username
);
const minutes = Math.ceil(remainingTime / (60 * 1000));
throw new Error(
`Too many login attempts. Please try again in ${minutes} minutes.`
);
}
// Attempt login
const response = await doLogin({
...credentials,
grantType: AUTH_CONSTANTS.GRANT_TYPE,
clientId: AUTH_CONSTANTS.CLIENT_ID,
});
if (response?.error) {
loginRateLimiter.recordAttempt(credentials.username);
throw new Error("Invalid username or password");
}
const { access_token, refresh_token } = response?.data || {};
if (!access_token || !refresh_token) {
throw new Error("Invalid response from server");
}
// Set auth cookies
setAuthCookies(access_token, refresh_token);
// Get user profile
const profileResponse = await getProfile(access_token);
const profile: ProfileData = profileResponse?.data?.data;
if (!profile) {
throw new Error("Failed to fetch user profile");
}
// Validate user eligibility
// if (!isUserEligible(profile)) {
// clearAllCookies();
// warning(
// "Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri",
// "/auth"
// );
// return;
// }
// Set profile cookies
setProfileCookies(profile);
// Reset rate limiter on successful login
loginRateLimiter.resetAttempts(credentials.username);
// Navigate based on user role
const navigationPath = getNavigationPath(
profile.roleId,
profile.userLevel?.id,
profile.userLevel?.parentLevelId
);
// Update state
setState({
isAuthenticated: true,
user: profile,
loading: false,
error: null,
});
// Navigate to appropriate dashboard
window.location.href = navigationPath;
} catch (error: any) {
const errorMessage = error?.message || "Login failed";
setState((prev) => ({
...prev,
loading: false,
error: errorMessage,
}));
showAuthError(error, "Login failed");
}
},
[router]
);
const logout = useCallback((): void => {
clearAllCookies();
setState({
isAuthenticated: false,
user: null,
loading: false,
error: null,
});
router.push("/auth");
}, [router]);
const refreshToken = useCallback(async (): Promise<void> => {
try {
setState((prev) => ({ ...prev, loading: true }));
// Add token refresh logic here
// This would typically call an API to refresh the access token
} catch (error) {
logout();
} finally {
setState((prev) => ({ ...prev, loading: false }));
}
}, [logout]);
return {
...state,
login,
logout,
refreshToken,
};
};
// Hook for email validation step
export const useEmailValidation = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const validateEmail = useCallback(
async (credentials: LoginFormData): Promise<string> => {
try {
setLoading(true);
setError(null);
const response = await postEmailValidation(credentials);
if (response?.error) {
throw new Error(response?.message || "Email validation failed");
}
const message = response?.data?.message;
switch (message) {
case "Continue to setup email":
return "setup";
case "Email is valid and OTP has been sent":
return "otp";
case "Username & password valid":
return "success";
case "Skip to login":
return "skip";
default:
return "login";
}
} catch (error: any) {
const errorMessage = error?.message || "Email validation failed";
setError(errorMessage);
showAuthError(error, "Email validation failed");
throw error;
} finally {
setLoading(false);
}
},
[]
);
return {
validateEmail,
loading,
error,
};
};
// Hook for email setup step
export const useEmailSetup = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const setupEmail = useCallback(
async (
credentials: LoginFormData,
emailData: EmailValidationData
): Promise<string> => {
try {
setLoading(true);
setError(null);
const data = {
username: credentials.username,
password: credentials.password,
oldEmail: emailData.oldEmail,
newEmail: emailData.newEmail,
};
const response = await postSetupEmail(data);
if (response?.error) {
throw new Error(response.message || "Email setup failed");
}
const message = response?.data?.message;
switch (message) {
case "Email is valid and OTP has been sent":
return "otp";
case "The old email is not same":
throw new Error("Email is invalid");
default:
return "success";
}
} catch (error: any) {
const errorMessage = error?.message || "Email setup failed";
setError(errorMessage);
showAuthError(error, "Email setup failed");
throw error;
} finally {
setLoading(false);
}
},
[]
);
return {
setupEmail,
loading,
error,
};
};
// Hook for OTP verification
export const useOTPVerification = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const verifyOTP = useCallback(
async (username: string, otp: string): Promise<boolean> => {
try {
setLoading(true);
setError(null);
if (otp.length !== 6) {
throw new Error("OTP must be exactly 6 digits");
}
const data = {
username: username,
otpCode: otp,
}
const response = await verifyOTPByUsername(data);
if (response?.error) {
throw new Error(response.message || "OTP verification failed");
}
return response?.message === "success";
} catch (error: any) {
const errorMessage = error?.message || "OTP verification failed";
setError(errorMessage);
showAuthError(error, "OTP verification failed");
throw error;
} finally {
setLoading(false);
}
},
[]
);
return {
verifyOTP,
loading,
error,
};
};

View File

@ -1,18 +0,0 @@
import { atom, useAtom } from "jotai";
export type ChatConfig = {
isOpen: boolean;
showInfo: boolean;
showProfile: boolean;
};
const chatConfigAtom = atom<ChatConfig>({
isOpen: false,
showInfo: false,
showProfile: false,
});
export function useChatConfig() {
return useAtom(chatConfigAtom);
}

View File

@ -1,57 +0,0 @@
import { useAtom } from "jotai"
import { atomWithStorage } from "jotai/utils"
import { layoutType, sidebarType, navBarType} from "@/lib/type"
export type Config = {
collapsed: boolean
theme: string
skin: 'default' | 'bordered'
layout: layoutType
sidebar: sidebarType
menuHidden: boolean,
showSearchBar: boolean,
showSwitcher: boolean
topHeader: 'default' | 'links'
contentWidth: 'wide' | 'boxed'
navbar: navBarType
footer: 'sticky' | 'default' | 'hidden'
isRtl: boolean
subMenu: boolean
hasSubMenu: boolean
sidebarTheme: string,
headerTheme: string,
sidebarBgImage?: string
radius: number
}
export const defaultConfig: Config = {
collapsed: false,
theme: "zinc",
skin: 'default',
layout: "vertical",
sidebar: 'classic',
menuHidden: false,
showSearchBar: true,
topHeader: 'default',
contentWidth: 'wide',
navbar: 'sticky',
footer: 'default',
isRtl: false,
showSwitcher: true,
subMenu: false,
hasSubMenu: false,
sidebarTheme: 'dark',
headerTheme: 'light',
sidebarBgImage: undefined,
radius: 0.5,
}
const configAtom = atomWithStorage<Config>("config", defaultConfig)
export function useConfig() {
return useAtom(configAtom)
}

View File

@ -1,124 +0,0 @@
import { useEffect, useState, useCallback } from 'react';
import {
FacebookLoginResponse,
FacebookLoginError,
FacebookUser,
FacebookSDKInitOptions
} from '@/types/facebook-login';
export interface UseFacebookLoginOptions extends FacebookSDKInitOptions {}
export const useFacebookLogin = (options: UseFacebookLoginOptions) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [user, setUser] = useState<FacebookUser | null>(null);
const { appId, version = 'v18.0', cookie = true, xfbml = true, autoLogAppEvents = true } = options;
// Initialize Facebook SDK
useEffect(() => {
if (typeof window === 'undefined') return;
// Load Facebook SDK if not already loaded
if (!window.FB) {
const script = document.createElement('script');
script.src = `https://connect.facebook.net/en_US/sdk.js`;
script.async = true;
script.defer = true;
script.crossOrigin = 'anonymous';
window.fbAsyncInit = () => {
window.FB.init({
appId,
cookie,
xfbml,
version,
autoLogAppEvents,
});
setIsLoaded(true);
// Check login status
window.FB.getLoginStatus((response: any) => {
if (response.status === 'connected') {
setIsLoggedIn(true);
getUserInfo(response.authResponse.accessToken);
}
});
};
document.head.appendChild(script);
} else {
setIsLoaded(true);
}
return () => {
// Cleanup if needed
};
}, [appId, cookie, xfbml, version, autoLogAppEvents]);
const getUserInfo = useCallback((accessToken: string) => {
window.FB.api('/me', { fields: 'name,email,picture' }, (response: FacebookUser) => {
if (response && !response.error) {
setUser(response);
}
});
}, []);
const login = useCallback((permissions: string[] = ['public_profile', 'email']) => {
return new Promise<FacebookLoginResponse>((resolve, reject) => {
if (!window.FB) {
reject(new Error('Facebook SDK not loaded'));
return;
}
window.FB.login((response: any) => {
if (response.status === 'connected') {
setIsLoggedIn(true);
getUserInfo(response.authResponse.accessToken);
resolve(response.authResponse);
} else if (response.status === 'not_authorized') {
reject({ error: 'not_authorized', errorDescription: 'User denied permissions' });
} else {
reject({ error: 'unknown', errorDescription: 'Login failed' });
}
}, { scope: permissions.join(',') });
});
}, [getUserInfo]);
const logout = useCallback(() => {
return new Promise<void>((resolve, reject) => {
if (!window.FB) {
reject(new Error('Facebook SDK not loaded'));
return;
}
window.FB.logout((response: any) => {
setIsLoggedIn(false);
setUser(null);
resolve();
});
});
}, []);
const getLoginStatus = useCallback(() => {
return new Promise<any>((resolve, reject) => {
if (!window.FB) {
reject(new Error('Facebook SDK not loaded'));
return;
}
window.FB.getLoginStatus((response: any) => {
resolve(response);
});
});
}, []);
return {
isLoaded,
isLoggedIn,
user,
login,
logout,
getLoginStatus,
};
};

View File

@ -1,14 +0,0 @@
import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
export type MailConfig = {
isOpen: boolean;
};
const mailConfigAtom = atomWithStorage<MailConfig>("mailConfig", {
isOpen: false,
});
export function useMailConfig() {
return useAtom(mailConfigAtom);
}

View File

@ -1,19 +0,0 @@
import * as React from "react"
export function useMediaQuery(query: string) {
const [value, setValue] = React.useState(false)
React.useEffect(() => {
function onChange(event: MediaQueryListEvent) {
setValue(event.matches)
}
const result = matchMedia(query)
result.addEventListener("change", onChange)
setValue(result.matches)
return () => result.removeEventListener("change", onChange)
}, [query])
return value
}

View File

@ -1,16 +0,0 @@
import { atom, useAtom } from "jotai";
export type hoverConfig = {
hovered: boolean;
};
const menuHoverConfigAtom = atom<hoverConfig>({
hovered: false,
});
export function useMenuHoverConfig() {
return useAtom(menuHoverConfigAtom);
}

View File

@ -1,14 +0,0 @@
import { atom, useAtom } from "jotai";
export type MobileMenuConfig = {
isOpen: boolean;
};
const mobileMenuConfig = atom<MobileMenuConfig>({
isOpen: false,
});
export function useMobileMenuConfig() {
return useAtom(mobileMenuConfig);
}

View File

@ -1,11 +0,0 @@
import * as React from "react"
export function useMounted() {
const [mounted, setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
}, [])
return mounted
}

View File

@ -1,20 +0,0 @@
import * as React from "react"
export const useMutationObserver = (
ref: React.MutableRefObject<HTMLElement | null>,
callback: MutationCallback,
options = {
attributes: true,
characterData: true,
childList: true,
subtree: true,
}
) => {
React.useEffect(() => {
if (ref.current) {
const observer = new MutationObserver(callback)
observer.observe(ref.current, options)
return () => observer.disconnect()
}
}, [ref, callback, options])
}

View File

@ -1,114 +0,0 @@
"use client";
import { useEffect, useCallback } from "react";
// Extend Window interface for gtag
declare global {
interface Window {
gtag?: (command: string, targetId: string, config: any) => void;
}
}
interface PerformanceMetrics {
FCP: number | null;
LCP: number | null;
FID: number | null;
CLS: number | null;
TTFB: number | null;
}
export const usePerformance = () => {
const reportMetric = useCallback((name: string, value: number) => {
// Send to analytics service
if (typeof window !== "undefined" && window.gtag) {
window.gtag("event", name, {
value: Math.round(name === "CLS" ? value * 1000 : value),
metric_id: name,
metric_value: value,
metric_delta: 0,
});
}
// Log to console in development
if (process.env.NODE_ENV === "development") {
console.log(`Performance Metric - ${name}:`, value);
}
}, []);
useEffect(() => {
if (typeof window === "undefined") return;
// First Contentful Paint
const fcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const fcp = entries[entries.length - 1];
if (fcp) {
reportMetric("FCP", fcp.startTime);
}
});
fcpObserver.observe({ entryTypes: ["paint"] });
// Largest Contentful Paint
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
if (lcp) {
reportMetric("LCP", lcp.startTime);
}
});
lcpObserver.observe({ entryTypes: ["largest-contentful-paint"] });
// First Input Delay
const fidObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry) => {
const firstInputEntry = entry as PerformanceEventTiming;
if (firstInputEntry.processingStart) {
const fid = firstInputEntry.processingStart - firstInputEntry.startTime;
reportMetric("FID", fid);
}
});
});
fidObserver.observe({ entryTypes: ["first-input"] });
// Cumulative Layout Shift
let clsValue = 0;
const clsObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach((entry: any) => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
});
clsObserver.observe({ entryTypes: ["layout-shift"] });
// Time to First Byte
const navigationEntry = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming;
if (navigationEntry) {
const ttfb = navigationEntry.responseStart - navigationEntry.requestStart;
reportMetric("TTFB", ttfb);
}
// Report CLS on page unload
const handleBeforeUnload = () => {
if (clsValue > 0) {
reportMetric("CLS", clsValue);
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
fcpObserver.disconnect();
lcpObserver.disconnect();
fidObserver.disconnect();
clsObserver.disconnect();
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, [reportMetric]);
return {
reportMetric,
};
};

View File

@ -1,624 +0,0 @@
"use client";
import { useState, useCallback, useEffect } from "react";
import { useRouter } from "@/components/navigation";
import { toast } from "sonner";
import {
RegistrationFormData,
JournalistRegistrationData,
PersonnelRegistrationData,
GeneralRegistrationData,
InstituteData,
UserCategory,
PasswordValidation,
TimerState,
Association,
OTPRequestResponse,
OTPVerificationResponse,
LocationResponse,
InstituteResponse,
RegistrationResponse,
LocationData,
} from "@/types/registration";
import {
requestOTP,
verifyRegistrationOTP,
listProvince,
listCity,
listDistricts,
listInstitusi,
saveInstitutes,
postRegistration,
getDataJournalist,
getDataPersonil,
verifyOTP,
} from "@/service/auth";
import {
validatePassword,
validateUsername,
validateEmail,
validatePhoneNumber,
createTimer,
formatTime,
sanitizeRegistrationData,
sanitizeInstituteData,
isValidCategory,
getCategoryRoleId,
showRegistrationError,
showRegistrationSuccess,
showRegistrationInfo,
transformRegistrationData,
createInitialFormData,
validateIdentityData,
RegistrationRateLimiter,
REGISTRATION_CONSTANTS,
} from "@/lib/registration-utils";
// Global rate limiter instance
const registrationRateLimiter = new RegistrationRateLimiter();
// Hook for OTP operations
export const useOTP = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [timer, setTimer] = useState<TimerState>(createTimer());
const startTimer = useCallback(() => {
setTimer(createTimer());
}, []);
const stopTimer = useCallback(() => {
setTimer((prev) => ({
...prev,
isActive: false,
isExpired: true,
}));
}, []);
// Timer effect
useEffect(() => {
if (!timer.isActive || timer.countdown <= 0) {
setTimer((prev) => ({
...prev,
isActive: false,
isExpired: true,
}));
return;
}
const interval = setInterval(() => {
setTimer((prev) => ({
...prev,
countdown: Math.max(0, prev.countdown - 1000),
}));
}, 1000);
return () => clearInterval(interval);
}, [timer.isActive, timer.countdown]);
const requestOTPCode = useCallback(
async (
email: string,
category: UserCategory,
memberIdentity?: string
): Promise<boolean> => {
try {
setLoading(true);
setError(null);
// Check rate limiting
const identifier = `${email}-${category}`;
if (!registrationRateLimiter.canAttempt(identifier)) {
const remainingAttempts =
registrationRateLimiter.getRemainingAttempts(identifier);
throw new Error(
`Too many OTP requests. Please try again later. Remaining attempts: ${remainingAttempts}`
);
}
const data = {
memberIdentity: memberIdentity || null,
email,
category: getCategoryRoleId(category),
name: email.split("@")[0],
};
// Debug logging
console.log("OTP Request Data:", data);
console.log("Category before conversion:", category);
console.log("Category after conversion:", getCategoryRoleId(category));
const response = await requestOTP(data);
if (response?.error) {
registrationRateLimiter.recordAttempt(identifier);
throw new Error(response.message || "Failed to send OTP");
}
// Start timer on successful OTP request
startTimer();
showRegistrationInfo("OTP sent successfully. Please check your email.");
return true;
} catch (error: any) {
const errorMessage = error?.message || "Failed to send OTP";
setError(errorMessage);
showRegistrationError(error, "Failed to send OTP");
return false;
} finally {
setLoading(false);
}
},
[startTimer]
);
const verifyOTPCode = useCallback(
async (
email: string,
otp: string,
category: UserCategory,
memberIdentity?: string
): Promise<any> => {
try {
setLoading(true);
setError(null);
if (otp.length !== 6) {
throw new Error("OTP must be exactly 6 digits");
}
const data = {
memberIdentity: memberIdentity || null,
email,
otp,
category: getCategoryRoleId(category),
};
const response = await verifyOTP(data.email, data.otp);
if (response?.error) {
throw new Error(response.message || "OTP verification failed");
}
stopTimer();
showRegistrationSuccess("OTP verified successfully");
return response?.data?.userData;
} catch (error: any) {
const errorMessage = error?.message || "OTP verification failed";
setError(errorMessage);
showRegistrationError(error, "OTP verification failed");
throw error;
} finally {
setLoading(false);
}
},
[stopTimer]
);
const resendOTP = useCallback(
async (
email: string,
category: UserCategory,
memberIdentity?: string
): Promise<boolean> => {
return await requestOTPCode(email, category, memberIdentity);
},
[requestOTPCode]
);
return {
requestOTP: requestOTPCode,
verifyOTP: verifyOTPCode,
resendOTP,
loading,
error,
timer,
formattedTime: formatTime(timer.countdown),
canResend: timer.isExpired,
};
};
// Hook for location data
export const useLocationData = () => {
const [provinces, setProvinces] = useState<LocationData[]>([]);
const [cities, setCities] = useState<LocationData[]>([]);
const [districts, setDistricts] = useState<LocationData[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchProvinces = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await listProvince();
if (!response || response.error) {
throw new Error(response?.message || "Failed to fetch provinces");
}
setProvinces(response?.data?.data || []);
} catch (error: any) {
const errorMessage = error?.message || "Failed to fetch provinces";
setError(errorMessage);
showRegistrationError(error, "Failed to fetch provinces");
} finally {
setLoading(false);
}
}, []);
const fetchCities = useCallback(async (provinceId: string) => {
try {
setLoading(true);
setError(null);
const response = await listCity(provinceId);
if (response?.error) {
throw new Error(response.message || "Failed to fetch cities");
}
setCities(response?.data?.data || []);
setDistricts([]); // Reset districts when province changes
} catch (error: any) {
const errorMessage = error?.message || "Failed to fetch cities";
setError(errorMessage);
showRegistrationError(error, "Failed to fetch cities");
} finally {
setLoading(false);
}
}, []);
const fetchDistricts = useCallback(async (cityId: string) => {
try {
setLoading(true);
setError(null);
const response = await listDistricts(cityId);
if (response?.error) {
throw new Error(response.message || "Failed to fetch districts");
}
setDistricts(response?.data?.data || []);
} catch (error: any) {
const errorMessage = error?.message || "Failed to fetch districts";
setError(errorMessage);
showRegistrationError(error, "Failed to fetch districts");
} finally {
setLoading(false);
}
}, []);
// Load provinces on mount
useEffect(() => {
fetchProvinces();
}, [fetchProvinces]);
return {
provinces,
cities,
districts,
loading,
error,
fetchProvinces,
fetchCities,
fetchDistricts,
};
};
// Hook for institute data
export const useInstituteData = (category?: number) => {
const [institutes, setInstitutes] = useState<InstituteData[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchInstitutes = useCallback(
async (categoryId?: number) => {
try {
setLoading(true);
setError(null);
const response = await listInstitusi(categoryId || category);
if (response?.error) {
throw new Error(response.message || "Failed to fetch institutes");
}
setInstitutes(response?.data?.data || []);
} catch (error: any) {
const errorMessage = error?.message || "Failed to fetch institutes";
setError(errorMessage);
showRegistrationError(error, "Failed to fetch institutes");
} finally {
setLoading(false);
}
},
[category]
);
const saveInstitute = useCallback(
async (instituteData: InstituteData): Promise<number> => {
try {
setLoading(true);
setError(null);
const sanitizedData = sanitizeInstituteData(instituteData);
const response = await saveInstitutes({
name: sanitizedData.name,
address: sanitizedData.address,
categoryRoleId: category || 6, // Use provided category or default to Journalist category
});
if (response?.error) {
throw new Error(response.message || "Failed to save institute");
}
return response?.data?.data?.id || 1;
} catch (error: any) {
const errorMessage = error?.message || "Failed to save institute";
setError(errorMessage);
showRegistrationError(error, "Failed to save institute");
throw error;
} finally {
setLoading(false);
}
},
[category]
);
// Load institutes on mount if category is provided
useEffect(() => {
if (category) {
fetchInstitutes();
}
}, [fetchInstitutes, category]);
return {
institutes,
loading,
error,
fetchInstitutes,
saveInstitute,
};
};
// Hook for user data validation
export const useUserDataValidation = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const validateJournalistData = useCallback(
async (certificateNumber: string): Promise<any> => {
try {
setLoading(true);
setError(null);
const response = await getDataJournalist(certificateNumber);
if (response?.error) {
throw new Error(
response.message || "Invalid journalist certificate number"
);
}
return response?.data?.data;
} catch (error: any) {
const errorMessage =
error?.message || "Failed to validate journalist data";
setError(errorMessage);
showRegistrationError(error, "Failed to validate journalist data");
throw error;
} finally {
setLoading(false);
}
},
[]
);
const validatePersonnelData = useCallback(
async (policeNumber: string): Promise<any> => {
try {
setLoading(true);
setError(null);
const response = await getDataPersonil(policeNumber);
if (response?.error) {
throw new Error(response.message || "Invalid police number");
}
return response?.data?.data;
} catch (error: any) {
const errorMessage =
error?.message || "Failed to validate personnel data";
setError(errorMessage);
showRegistrationError(error, "Failed to validate personnel data");
throw error;
} finally {
setLoading(false);
}
},
[]
);
return {
validateJournalistData,
validatePersonnelData,
loading,
error,
};
};
// Hook for registration submission
export const useRegistration = () => {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const submitRegistration = useCallback(
async (
data: RegistrationFormData,
category: UserCategory,
userData: any,
instituteId?: number
): Promise<boolean> => {
try {
setLoading(true);
setError(null);
// Sanitize and validate data
const sanitizedData = sanitizeRegistrationData(data);
// Validate password
const passwordValidation = validatePassword(
sanitizedData.password,
sanitizedData.passwordConf
);
if (!passwordValidation.isValid) {
throw new Error(passwordValidation.errors[0]);
}
// Validate username
const usernameValidation = validateUsername(sanitizedData.username);
if (!usernameValidation.isValid) {
throw new Error(usernameValidation.error!);
}
// Transform data for API
const transformedData = transformRegistrationData(
sanitizedData,
category,
userData,
instituteId
);
const response = await postRegistration(transformedData);
console.log("PPPP", transformedData);
if (response?.error) {
throw new Error(response.message || "Registration failed");
}
showRegistrationSuccess(
"Registration successful! Please check your email for verification."
);
// Redirect to login page
setTimeout(() => {
router.push("/auth");
}, 2000);
return true;
} catch (error: any) {
const errorMessage = error?.message || "Registration failed";
setError(errorMessage);
showRegistrationError(error, "Registration failed");
return false;
} finally {
setLoading(false);
}
},
[router]
);
return {
submitRegistration,
loading,
error,
};
};
// Hook for form validation
export const useFormValidation = () => {
const validateIdentityForm = useCallback(
(
data:
| JournalistRegistrationData
| PersonnelRegistrationData
| GeneralRegistrationData,
category: UserCategory
): { isValid: boolean; errors: string[] } => {
return validateIdentityData(data, category);
},
[]
);
const validateProfileForm = useCallback(
(data: RegistrationFormData): { isValid: boolean; errors: string[] } => {
const errors: string[] = [];
// Validate required fields
if (!data.firstName?.trim()) {
errors.push("Full name is required");
}
if (!data.username?.trim()) {
errors.push("Username is required");
} else {
const usernameValidation = validateUsername(data.username);
if (!usernameValidation.isValid) {
errors.push(usernameValidation.error!);
}
}
if (!data.email?.trim()) {
errors.push("Email is required");
} else {
const emailValidation = validateEmail(data.email);
if (!emailValidation.isValid) {
errors.push(emailValidation.error!);
}
}
if (!data.phoneNumber?.trim()) {
errors.push("Phone number is required");
} else {
const phoneValidation = validatePhoneNumber(data.phoneNumber);
if (!phoneValidation.isValid) {
errors.push(phoneValidation.error!);
}
}
if (!data.address?.trim()) {
errors.push("Address is required");
}
if (!data.provinsi) {
errors.push("Province is required");
}
if (!data.kota) {
errors.push("City is required");
}
if (!data.kecamatan) {
errors.push("Subdistrict is required");
}
if (!data.password) {
errors.push("Password is required");
} else {
const passwordValidation = validatePassword(
data.password,
data.passwordConf
);
if (!passwordValidation.isValid) {
errors.push(passwordValidation.errors[0]);
}
}
return {
isValid: errors.length === 0,
errors,
};
},
[]
);
return {
validateIdentityForm,
validateProfileForm,
};
};

View File

@ -1,16 +0,0 @@
import { useAtom } from "jotai"
import { atomWithStorage } from "jotai/utils"
export type TodoConfig = {
isOpen: boolean
}
const todoConfigAtom = atomWithStorage<TodoConfig>("todoConfig", {
isOpen: false,
})
export function useTodoConfig() {
return useAtom(todoConfigAtom)
}

View File

@ -1,24 +0,0 @@
import { useTranslations } from "next-intl";
/**
* Custom hook that provides translation function with default values
* This ensures that when translations are not ready, a default value is shown
* Format: t("key", { defaultValue: "Default Value" })
*/
export function useTranslationWithDefaults(namespace: string) {
const t = useTranslations(namespace);
return (key: string, options?: { defaultValue?: string }) => {
try {
const translation = t(key);
// If translation returns the same key (fallback behavior), use default value
if (translation === key && options?.defaultValue) {
return options.defaultValue;
}
return translation || options?.defaultValue || key;
} catch (error) {
// If translation fails (e.g., key not found), return default value or key
return options?.defaultValue || key;
}
};
}

View File

@ -1,44 +0,0 @@
export const getYAxisConfig = (colors: any) => ({
labels: getLabel(colors),
});
export const getXAxisConfig = (colors: string): { } => ({
categories: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
labels: getLabel(colors),
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
});
export const getLabel = (colors:any): { } => ({
style: {
colors: colors,
fontFamily: "Inter",
},
});
export const getGridConfig = (): { show: boolean; borderColor: string; strokeDashArray: number; position: string; } => ({
show: false,
borderColor: 'transparent',
strokeDashArray: 10,
position: "back",
});

View File

@ -1,246 +0,0 @@
import Cookies from "js-cookie";
import { toast } from "sonner";
import {
AuthCookies,
ProfileData,
LoginFormData,
NavigationConfig
} from "@/types/auth";
import { getCookiesDecrypt, setCookiesEncrypt } from "@/lib/utils";
// Navigation configuration based on user roles
export const NAVIGATION_CONFIG: NavigationConfig[] = [
{ roleId: 2, path: "/in/dashboard/executive", label: "Executive Dashboard" },
{ roleId: 3, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 4, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 9, path: "/in/supervisor/ticketing/all", label: "Ticketing" },
{ roleId: 10, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 11, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 12, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 13, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 14, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 15, path: "/in/dashboard", label: "Dashboard" },
{ roleId: 18, path: "/in/dashboard/executive-data", label: "Executive Data Dashboard" },
{ roleId: 19, path: "/in/dashboard", label: "Dashboard" },
];
// Cookie management utilities
export const setAuthCookies = (accessToken: string, refreshToken: string): void => {
const dateTime = new Date();
const newTime = dateTime.getTime() + 10 * 60 * 1000; // 10 minutes
Cookies.set("access_token", accessToken, { expires: 1 });
Cookies.set("refresh_token", refreshToken, { expires: 1 });
Cookies.set("time_refresh", new Date(newTime).toISOString(), { expires: 1 });
Cookies.set("is_first_login", String(true), { secure: true, sameSite: "strict" });
};
export const setProfileCookies = (profile: ProfileData): void => {
Cookies.set("home_path", profile.homePath || "", { expires: 1 });
Cookies.set("profile_picture", profile.profilePictureUrl || "", { expires: 1 });
Cookies.set("state", profile.userLevel?.name || "", { expires: 1 });
Cookies.set("state-prov", profile.userLevel?.province?.provName || "", { expires: 1 });
setCookiesEncrypt("uie", profile.id, { expires: 1 });
setCookiesEncrypt("urie", profile.roleId.toString(), { expires: 1 });
setCookiesEncrypt("urne", profile.role?.name || "", { expires: 1 });
setCookiesEncrypt("ulie", profile.userLevel?.id.toString() || "", { expires: 1 });
setCookiesEncrypt("uplie", profile.userLevel?.parentLevelId?.toString() || "", { expires: 1 });
setCookiesEncrypt("ulne", profile.userLevel?.levelNumber.toString() || "", { expires: 1 });
setCookiesEncrypt("ufne", profile.fullname, { expires: 1 });
setCookiesEncrypt("ulnae", profile.userLevel?.name || "", { expires: 1 });
setCookiesEncrypt("uinse", profile.instituteId || "", { expires: 1 });
Cookies.set("status", "login", { expires: 1 });
};
export const clearAllCookies = (): void => {
Object.keys(Cookies.get()).forEach((cookieName) => {
Cookies.remove(cookieName);
});
};
export const getAuthCookies = (): Partial<AuthCookies> => {
return {
access_token: Cookies.get("access_token"),
refresh_token: Cookies.get("refresh_token"),
time_refresh: Cookies.get("time_refresh"),
is_first_login: Cookies.get("is_first_login"),
home_path: Cookies.get("home_path"),
profile_picture: Cookies.get("profile_picture"),
state: Cookies.get("state"),
"state-prov": Cookies.get("state-prov"),
uie: getCookiesDecrypt("uie"),
urie: getCookiesDecrypt("urie"),
urne: getCookiesDecrypt("urne"),
ulie: getCookiesDecrypt("ulie"),
uplie: getCookiesDecrypt("uplie"),
ulne: getCookiesDecrypt("ulne"),
ufne: getCookiesDecrypt("ufne"),
ulnae: getCookiesDecrypt("ulnae"),
uinse: getCookiesDecrypt("uinse"),
status: Cookies.get("status"),
};
};
// User validation utilities
export const isUserEligible = (profile: ProfileData): boolean => {
return !(
profile.isInternational ||
!profile.isActive ||
profile.isDelete
);
};
export const isProtectedRole = (roleId: number): boolean => {
const protectedRoles = [2, 3, 4, 9, 10, 11, 12, 18, 19];
return protectedRoles.includes(roleId);
};
export const isSpecialLevel = (userLevelId: number, parentLevelId?: number): boolean => {
return userLevelId === 794 || parentLevelId === 761;
};
// Navigation utilities
export const getNavigationPath = (roleId: number, userLevelId?: number, parentLevelId?: number): string => {
const config = NAVIGATION_CONFIG.find(nav => nav.roleId === roleId);
console.log("Navigation Config : ", config);
if (config) {
// Special handling for role 2 with specific level conditions
if (roleId === 2 && userLevelId && parentLevelId && isSpecialLevel(userLevelId, parentLevelId)) {
return "/in/dashboard";
}
return config.path;
}
return "/"; // Default fallback
};
// Error handling utilities
export const handleAuthError = (error: any, defaultMessage: string = "Authentication failed"): string => {
if (typeof error === "string") {
return error;
}
if (error?.message) {
return error.message;
}
if (error?.response?.data?.message) {
return error.response.data.message;
}
return defaultMessage;
};
export const showAuthError = (error: any, defaultMessage: string = "Authentication failed"): void => {
const message = handleAuthError(error, defaultMessage);
toast.error(message);
};
export const showAuthSuccess = (message: string): void => {
toast.success(message);
};
// Form validation utilities
export const validateEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
export const validatePassword = (password: string): { isValid: boolean; errors: string[] } => {
const errors: string[] = [];
if (password.length < 6) {
errors.push("Password must be at least 6 characters long");
}
if (password.length > 100) {
errors.push("Password must be less than 100 characters");
}
return {
isValid: errors.length === 0,
errors
};
};
// Loading state utilities
export const createLoadingState = () => {
let isLoading = false;
return {
get: () => isLoading,
set: (loading: boolean) => { isLoading = loading; },
withLoading: async <T>(fn: () => Promise<T>): Promise<T> => {
isLoading = true;
try {
return await fn();
} finally {
isLoading = false;
}
}
};
};
// Constants
export const AUTH_CONSTANTS = {
CLIENT_ID: "mediahub-app",
GRANT_TYPE: "password",
TOKEN_REFRESH_INTERVAL: 10 * 60 * 1000, // 10 minutes
MAX_LOGIN_ATTEMPTS: 3,
LOCKOUT_DURATION: 15 * 60 * 1000, // 15 minutes
} as const;
// Rate limiting utilities
export class LoginRateLimiter {
private attempts: Map<string, { count: number; lastAttempt: number }> = new Map();
canAttempt(username: string): boolean {
const userAttempts = this.attempts.get(username);
if (!userAttempts) {
return true;
}
const timeSinceLastAttempt = Date.now() - userAttempts.lastAttempt;
if (timeSinceLastAttempt > AUTH_CONSTANTS.LOCKOUT_DURATION) {
this.attempts.delete(username);
return true;
}
return userAttempts.count < AUTH_CONSTANTS.MAX_LOGIN_ATTEMPTS;
}
recordAttempt(username: string): void {
const userAttempts = this.attempts.get(username);
if (userAttempts) {
userAttempts.count++;
userAttempts.lastAttempt = Date.now();
} else {
this.attempts.set(username, { count: 1, lastAttempt: Date.now() });
}
}
resetAttempts(username: string): void {
this.attempts.delete(username);
}
getRemainingTime(username: string): number {
const userAttempts = this.attempts.get(username);
if (!userAttempts) {
return 0;
}
const timeSinceLastAttempt = Date.now() - userAttempts.lastAttempt;
return Math.max(0, AUTH_CONSTANTS.LOCKOUT_DURATION - timeSinceLastAttempt);
}
}
// Global rate limiter instance
export const loginRateLimiter = new LoginRateLimiter();

View File

@ -1,48 +0,0 @@
// import NextAuth from "next-auth";
// import GitHub from "next-auth/providers/github";
// import Google from "next-auth/providers/google";
// import CredentialsProvider from "next-auth/providers/credentials";
// import { getUserByEmail, type User } from "./data";
// export const { auth, handlers, signIn, signOut } = NextAuth({
// session: {
// strategy: "jwt",
// },
// providers: [
// Google,
// GitHub,
// CredentialsProvider({
// credentials: {
// email: {},
// password: {},
// },
// async authorize(credentials) {
// if (credentials === null) return null;
// try {
// const user = getUserByEmail(credentials?.email as string);
// if (user) {
// const isMatch = user?.password === credentials.password;
// if (isMatch) {
// return user;
// } else {
// throw new Error("Email or Password is not correct");
// }
// } else {
// throw new Error("User not found");
// }
// } catch (error) {
// throw new Error(error as string);
// }
// },
// }),
// ],
// });

View File

@ -1,14 +0,0 @@
export const colors = {
primary: "#4669FA",
secondary: "#A0AEC0",
danger: "#F1595C",
warning: "#FA916B",
info: "#0CE7FA",
light: "#425466",
success: "#50C793",
default: "#0f172a",
'default-800': "#1e293b",
'default-600': "#475569",
'default-300': "#cbd5e1"
};

View File

@ -1,16 +0,0 @@
// user data
const users = [
{
name: "dashcode",
email: "dashcode@codeshaper.net",
password: "password",
image: '/images/users/user-1.jpg',
},
]
export type User = (typeof users)[number]
export const getUserByEmail = (email: string) => {
return users.find((user) => user.email === email)
}

View File

@ -1,31 +0,0 @@
import va from "@vercel/analytics"
import { z } from "zod"
const eventSchema = z.object({
name: z.enum([
"copy_npm_command",
"copy_usage_import_code",
"copy_usage_code",
"copy_primitive_code",
"copy_theme_code",
"copy_block_code",
"copy_chunk_code",
"enable_lift_mode",
"copy_chart_code",
"copy_chart_theme",
"copy_chart_data",
"copy_color",
]),
properties: z
.record(z.union([z.string(), z.number(), z.boolean(), z.null()]))
.optional(),
})
export type Event = z.infer<typeof eventSchema>
export function trackEvent(input: Event): void {
const event = eventSchema.parse(input)
if (event) {
va.track(event.name, event.properties)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,374 +0,0 @@
import {
RegistrationFormData,
JournalistRegistrationData,
PersonnelRegistrationData,
GeneralRegistrationData,
InstituteData,
UserCategory,
PasswordValidation,
TimerState,
Association
} from "@/types/registration";
import { toast } from "sonner";
// Constants
export const REGISTRATION_CONSTANTS = {
OTP_TIMEOUT: 60000, // 1 minute in milliseconds
MAX_OTP_ATTEMPTS: 3,
PASSWORD_MIN_LENGTH: 8,
USERNAME_MIN_LENGTH: 3,
USERNAME_MAX_LENGTH: 50,
} as const;
// Association data
export const ASSOCIATIONS: Association[] = [
{ id: "1", name: "PWI (Persatuan Wartawan Indonesia)", value: "PWI" },
{ id: "2", name: "IJTI (Ikatan Jurnalis Televisi Indonesia)", value: "IJTI" },
{ id: "3", name: "PFI (Pewarta Foto Indonesia)", value: "PFI" },
{ id: "4", name: "AJI (Asosiasi Jurnalis Indonesia)", value: "AJI" },
{ id: "5", name: "Other Identity", value: "Wartawan" },
];
// Password validation utility
export const validatePassword = (password: string, confirmPassword?: string): PasswordValidation => {
const errors: string[] = [];
let strength: 'weak' | 'medium' | 'strong' = 'weak';
// Check minimum length
if (password.length < REGISTRATION_CONSTANTS.PASSWORD_MIN_LENGTH) {
errors.push(`Password must be at least ${REGISTRATION_CONSTANTS.PASSWORD_MIN_LENGTH} characters`);
}
// Check for uppercase letter
if (!/[A-Z]/.test(password)) {
errors.push("Password must contain at least one uppercase letter");
}
// Check for lowercase letter
if (!/[a-z]/.test(password)) {
errors.push("Password must contain at least one lowercase letter");
}
// Check for number
if (!/\d/.test(password)) {
errors.push("Password must contain at least one number");
}
// Check for special character
if (!/[@$!%*?&]/.test(password)) {
errors.push("Password must contain at least one special character (@$!%*?&)");
}
// Check password confirmation
if (confirmPassword && password !== confirmPassword) {
errors.push("Passwords don't match");
}
// Determine strength
if (password.length >= 12 && errors.length === 0) {
strength = 'strong';
} else if (password.length >= 8 && errors.length <= 1) {
strength = 'medium';
}
return {
isValid: errors.length === 0,
errors,
strength,
};
};
// Username validation utility
export const validateUsername = (username: string): { isValid: boolean; error?: string } => {
if (username.length < REGISTRATION_CONSTANTS.USERNAME_MIN_LENGTH) {
return { isValid: false, error: `Username must be at least ${REGISTRATION_CONSTANTS.USERNAME_MIN_LENGTH} characters` };
}
if (username.length > REGISTRATION_CONSTANTS.USERNAME_MAX_LENGTH) {
return { isValid: false, error: `Username must be less than ${REGISTRATION_CONSTANTS.USERNAME_MAX_LENGTH} characters` };
}
if (!/^[a-zA-Z0-9._-]+$/.test(username)) {
return { isValid: false, error: "Username can only contain letters, numbers, dots, underscores, and hyphens" };
}
return { isValid: true };
};
// Email validation utility
export const validateEmail = (email: string): { isValid: boolean; error?: string } => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email) {
return { isValid: false, error: "Email is required" };
}
if (!emailRegex.test(email)) {
return { isValid: false, error: "Please enter a valid email address" };
}
return { isValid: true };
};
// Phone number validation utility
export const validatePhoneNumber = (phoneNumber: string): { isValid: boolean; error?: string } => {
const phoneRegex = /^[0-9+\-\s()]+$/;
if (!phoneNumber) {
return { isValid: false, error: "Phone number is required" };
}
if (!phoneRegex.test(phoneNumber)) {
return { isValid: false, error: "Please enter a valid phone number" };
}
return { isValid: true };
};
// Timer utility
export const createTimer = (duration: number = REGISTRATION_CONSTANTS.OTP_TIMEOUT): TimerState => {
return {
countdown: duration,
isActive: true,
isExpired: false,
};
};
export const formatTime = (milliseconds: number): string => {
const minutes = Math.floor(milliseconds / 60000);
const seconds = Math.floor((milliseconds % 60000) / 1000);
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
};
// Data sanitization utility
export const sanitizeRegistrationData = (data: RegistrationFormData): RegistrationFormData => {
return {
...data,
firstName: data.firstName.trim(),
username: data.username.trim().toLowerCase(),
email: data.email.trim().toLowerCase(),
phoneNumber: data.phoneNumber.trim(),
address: data.address.trim(),
};
};
export const sanitizeInstituteData = (data: InstituteData): InstituteData => {
return {
id : data.id,
name: data.name.trim(),
address: data.address.trim(),
};
};
// Category validation utility
export const isValidCategory = (category: string): category is UserCategory => {
return category === "6" || category === "7" || category === "general";
};
export const getCategoryLabel = (category: UserCategory): string => {
switch (category) {
case "6":
return "Journalist";
case "7":
return "Personnel";
case "general":
return "Public";
default:
return "Unknown";
}
};
// Category to role ID conversion utility
export const getCategoryRoleId = (category: UserCategory): number => {
switch (category) {
case "6":
return 6; // Journalist
case "7":
return 7; // Personnel
case "general":
return 5; // Public (general)
default:
return 5; // Default to public
}
};
// Error handling utilities
export const showRegistrationError = (error: any, defaultMessage: string = "Registration failed") => {
const message = error?.message || error?.data?.message || defaultMessage;
toast.error(message);
console.error("Registration error:", error);
};
export const showRegistrationSuccess = (message: string = "Registration successful") => {
toast.success(message);
};
export const showRegistrationInfo = (message: string) => {
toast.info(message);
};
// Data transformation utilities
export const transformRegistrationData = (
data: RegistrationFormData,
category: UserCategory,
userData: any,
instituteId?: number
): any => {
const baseData = {
firstName: data.firstName,
lastName: data.firstName, // Using firstName as lastName for now
username: data.username,
phoneNumber: data.phoneNumber,
email: data.email,
address: data.address,
provinceId: Number(data.provinsi),
cityId: Number(data.kota),
districtId: Number(data.kecamatan),
password: data.password,
roleId: getCategoryRoleId(category),
};
// Add category-specific data
if (category === "6") {
return {
...baseData,
memberIdentity: userData?.journalistCertificate,
instituteId: instituteId || 1,
};
} else if (category === "7") {
return {
...baseData,
memberIdentity: userData?.policeNumber,
instituteId: 1,
};
} else {
return {
...baseData,
memberIdentity: null,
instituteId: 1,
};
}
};
// Form data utilities
export const createInitialFormData = (category: UserCategory) => {
const baseData = {
firstName: "",
username: "",
phoneNumber: "",
email: "",
address: "",
provinsi: "",
kota: "",
kecamatan: "",
password: "",
passwordConf: "",
};
if (category === "6") {
return {
...baseData,
journalistCertificate: "",
association: "",
};
} else if (category === "7") {
return {
...baseData,
policeNumber: "",
};
}
return baseData;
};
// Validation utilities
export const validateIdentityData = (
data: JournalistRegistrationData | PersonnelRegistrationData | GeneralRegistrationData,
category: UserCategory
): { isValid: boolean; errors: string[] } => {
const errors: string[] = [];
// Email validation
const emailValidation = validateEmail(data.email);
if (!emailValidation.isValid) {
errors.push(emailValidation.error!);
}
// Category-specific validation
if (category === "6") {
const journalistData = data as JournalistRegistrationData;
if (!journalistData.journalistCertificate?.trim()) {
errors.push("Journalist certificate number is required");
}
if (!journalistData.association?.trim()) {
errors.push("Association is required");
}
} else if (category === "7") {
const personnelData = data as PersonnelRegistrationData;
if (!personnelData.policeNumber?.trim()) {
errors.push("Police number is required");
}
}
return {
isValid: errors.length === 0,
errors,
};
};
// Rate limiting utility
export class RegistrationRateLimiter {
private attempts: Map<string, { count: number; lastAttempt: number }> = new Map();
private readonly maxAttempts: number;
private readonly windowMs: number;
constructor(maxAttempts: number = REGISTRATION_CONSTANTS.MAX_OTP_ATTEMPTS, windowMs: number = 300000) {
this.maxAttempts = maxAttempts;
this.windowMs = windowMs;
}
canAttempt(identifier: string): boolean {
const attempt = this.attempts.get(identifier);
if (!attempt) return true;
const now = Date.now();
if (now - attempt.lastAttempt > this.windowMs) {
this.attempts.delete(identifier);
return true;
}
return attempt.count < this.maxAttempts;
}
recordAttempt(identifier: string): void {
const attempt = this.attempts.get(identifier);
const now = Date.now();
if (attempt) {
attempt.count++;
attempt.lastAttempt = now;
} else {
this.attempts.set(identifier, { count: 1, lastAttempt: now });
}
}
getRemainingAttempts(identifier: string): number {
const attempt = this.attempts.get(identifier);
if (!attempt) return this.maxAttempts;
const now = Date.now();
if (now - attempt.lastAttempt > this.windowMs) {
this.attempts.delete(identifier);
return this.maxAttempts;
}
return Math.max(0, this.maxAttempts - attempt.count);
}
reset(identifier: string): void {
this.attempts.delete(identifier);
}
}
// Export rate limiter instance
export const registrationRateLimiter = new RegistrationRateLimiter();

View File

@ -1,54 +0,0 @@
export const generateStaticUUIDs = (prefix: string, count: number): string[] => {
const uuids = [];
for (let i = 1; i <= count; i++) {
uuids.push(`${prefix}-${i.toString().padStart(3, '0')}`);
}
return uuids;
};
export const generateStaticNumbers = (min: number, max: number, count: number): number[] => {
const numbers = [];
for (let i = 0; i < count; i++) {
// Generate deterministic but varied numbers
const seed = i * 7 + 13; // Simple seed for variety
const range = max - min;
const value = min + (seed % range);
numbers.push(value);
}
return numbers;
};
export const generateStaticChartData = (labels: string[], datasets: number): number[][] => {
const result = [];
for (let i = 0; i < datasets; i++) {
const data = generateStaticNumbers(-100, 100, labels.length);
result.push(data);
}
return result;
};
// Pre-generated static UUIDs for common use cases
export const STATIC_UUIDS = {
PRODUCTS: generateStaticUUIDs('product', 20),
EVENTS: generateStaticUUIDs('event', 20),
TASKS: generateStaticUUIDs('task', 20),
PROJECTS: generateStaticUUIDs('project', 20),
USERS: generateStaticUUIDs('user', 20),
COLUMNS: generateStaticUUIDs('col', 10),
};
// Pre-generated chart data
export const CHART_DATA = {
LINE_CHART_3_DATASETS: [
[45, -23, 67, -12, 89, -34, 56],
[-67, 34, -89, 12, -45, 78, -23],
[23, -56, 78, -34, 45, -67, 89]
],
LINE_CHART_2_DATASETS: [
[25, 45, 67, 34, 89, 56, 78],
[67, 23, 45, 78, 34, 89, 12]
],
POSITIVE_DATA: [25, 45, 67, 34, 89, 56, 78, 90, 45, 67],
MIXED_DATA: [45, -23, 67, -12, 89, -34, 56, 78, -45, 23],
TIME_SERIES: [12, 34, 56, 78, 90, 45, 67, 89, 23, 45, 67, 89]
};

View File

@ -1,92 +0,0 @@
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
const MySwal = withReactContent(Swal);
const Toast = MySwal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener("mouseenter", Swal.stopTimer);
toast.addEventListener("mouseleave", Swal.resumeTimer);
},
});
export function loading(msg?: any) {
let timerInterval: any;
MySwal.fire({
title: msg || "Loading...",
allowOutsideClick: false,
timerProgressBar: true,
didOpen: () => {
MySwal.showLoading();
timerInterval = setInterval(() => {}, 100);
},
willClose: () => {
clearInterval(timerInterval);
},
});
}
export function error(msg?: any) {
MySwal.fire({
icon: "error",
title: "Failed...",
text: msg || "Unknown Error",
});
}
export function successRouter(redirect: string, router?: any) {
MySwal.fire({
title: "Success!",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "Ok",
allowOutsideClick: false,
}).then((result) => {
if (result.isConfirmed) {
router.push(redirect);
}
});
}
export function success(title: string) {
MySwal.fire({
title: title || "Success!",
icon: "success",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
return true;
}
});
}
export function close() {
MySwal.close();
}
export function warning(text: string, redirect: string, router?: any) {
MySwal.fire({
title: text,
icon: "warning",
confirmButtonColor: "#3085d6",
confirmButtonText: "OK",
}).then((result) => {
if (result.isConfirmed) {
router.push(redirect);
}
});
}
export function successToast(title: string, text: string) {
Toast.fire({
icon: "error",
title: title,
text: text,
});
}

View File

@ -1,15 +0,0 @@
export type color = "default" | "primary" | "secondary" | "success" | "info" | "warning" | "destructive"
export type InputColor = "default" | "primary" | "secondary" | "success" | "info" | "warning" | "destructive"
export type shadow = "sm" | "md" | "lg" | "xl"
export type size = "default" | "sm" | "md" | "lg"
export type rounded = "sm" | "md" | "lg" | "full"
export type radius = "sm" | "md" | "lg" | "xl" | "none"
// config
export type layoutType = "vertical" | "horizontal" | "semi-box" | "compact";
export type contentType = "wide" | "boxed";
export type skinType = "default" | "bordered";
export type sidebarType = 'classic' | 'draggable' | 'two-column' | 'compact'
export type navBarType = 'floating' | 'sticky' | 'hidden' | 'default'
export type headerColorType = 'default' | 'coloured' | 'transparent'

View File

@ -1,106 +0,0 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import Cookies from "js-cookie";
import CryptoJS from "crypto-js";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
const MySwal = withReactContent(Swal);
const Toast = MySwal.mixin({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener("mouseenter", Swal.stopTimer);
toast.addEventListener("mouseleave", Swal.resumeTimer);
},
});
export const hexToRGB = (hex: any, alpha?: number): any => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
if (alpha) {
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
} else {
return `rgb(${r}, ${g}, ${b})`;
}
};
export function getCookiesDecrypt(param: any) {
const cookiesEncrypt = Cookies.get(param);
try {
if (cookiesEncrypt != undefined) {
const output = CryptoJS.AES.decrypt(
cookiesEncrypt.toString(),
`${param}_EncryptKey@mediahub`
).toString(CryptoJS.enc.Utf8);
if (output.startsWith('"')) {
return output.slice(1, -1);
}
return output;
}
} catch (e) {
console.log("Error", cookiesEncrypt);
}
}
export function successToast(title: string, text: string) {
Toast.fire({
icon: "error",
title: title,
text: text,
});
}
export function setCookiesEncrypt(
param: string,
data: any,
options?: Cookies.CookieAttributes
) {
// Enkripsi data
const cookiesEncrypt = CryptoJS.AES.encrypt(
JSON.stringify(data),
`${param}_EncryptKey@mediahub`
).toString(); // Tambahkan .toString() di sini
// Simpan data terenkripsi di cookie
Cookies.set(param, cookiesEncrypt, options);
}
export function checkAuthorization(page: any) {
const roleId = getCookiesDecrypt("urie");
const levelNumber = getCookiesDecrypt("ulne");
if (
(Number(roleId) !== 3 &&
Number(roleId) !== 4 &&
Number(roleId) !== 11 &&
Number(roleId) !== 12 &&
Number(roleId) !== 2) ||
roleId == undefined
) {
console.log("Wrong Authentication");
// window.location.href = "/";
} else if (page == "admin" && Number(levelNumber) !== 1) {
console.log("Wrong Authentication Admin");
// window.location.href = "/";
}
}
export function checkLoginSession() {
const userId = getCookiesDecrypt("uie");
const jwt = Cookies.get("access_token");
const data = {
userId,
jwt,
};
// doCheckSession(data);
}

View File

@ -36,6 +36,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
const userLevelId = getCookiesDecrypt("ulie");
const userParentLevelId = getCookiesDecrypt("uplie");
const hideForRole14 = roleId === "14";
const userRoleId: number = Number(getCookiesDecrypt("urie"));
let menusSelected = <any>[];
@ -56,7 +57,7 @@ export function getMenuList(pathname: string, t: any): Group[] {
},
],
},
...(Number(roleId) === 3
...(Number(userRoleId) === 3
? [
{
groupLabel: "",
@ -151,45 +152,45 @@ export function getMenuList(pathname: string, t: any): Group[] {
},
],
},
{
groupLabel: "",
id: "settings",
menus: [
{
id: "settings",
href: "/admin/settings",
label: t("settings"),
active: pathname.includes("/settinng"),
icon: "material-symbols:settings",
submenus: [
{
href: "/admin/categories",
label: t("category"),
active: pathname === "/admin/settings/category",
icon: "heroicons:arrow-trending-up",
children: [],
},
{
href: "/admin/settings/tag",
label: "Tag",
active: pathname === "/admin/settings/tag",
icon: "heroicons:arrow-trending-up",
children: [],
},
{
href: "/admin/settings/banner",
label: "Banner",
active: pathname === "/admin/settings/banner",
icon: "heroicons:arrow-trending-up",
children: [],
},
],
},
],
},
// {
// groupLabel: "",
// id: "settings",
// menus: [
// {
// id: "settings",
// href: "/admin/settings",
// label: t("settings"),
// active: pathname.includes("/settinng"),
// icon: "material-symbols:settings",
// submenus: [
// {
// href: "/admin/categories",
// label: t("category"),
// active: pathname === "/admin/settings/category",
// icon: "heroicons:arrow-trending-up",
// children: [],
// },
// // {
// // href: "/admin/settings/tag",
// // label: "Tag",
// // active: pathname === "/admin/settings/tag",
// // icon: "heroicons:arrow-trending-up",
// // children: [],
// // },
// // {
// // href: "/admin/settings/banner",
// // label: "Banner",
// // active: pathname === "/admin/settings/banner",
// // icon: "heroicons:arrow-trending-up",
// // children: [],
// // },
// ],
// },
// ],
// },
]
: []),
...(Number(roleId) === 2
...(Number(userRoleId) === 2
? [
{
groupLabel: "",
@ -219,68 +220,104 @@ export function getMenuList(pathname: string, t: any): Group[] {
},
],
},
{
groupLabel: "",
id: "settings",
menus: [
{
id: "settings",
href: "/admin/settings",
label: t("settings"),
active: pathname.includes("/settinng"),
icon: "material-symbols:settings",
submenus: [
{
href: "/admin/categories",
label: t("category"),
active: pathname === "/admin/settings/category",
icon: "heroicons:arrow-trending-up",
children: [],
},
// {
// href: "/admin/settings/tag",
// label: "Tag",
// active: pathname === "/admin/settings/tag",
// icon: "heroicons:arrow-trending-up",
// children: [],
// },
// {
// href: "/admin/settings/banner",
// label: "Banner",
// active: pathname === "/admin/settings/banner",
// icon: "heroicons:arrow-trending-up",
// children: [],
// },
],
},
],
},
]
: []),
...(Number(roleId) === 1
? [
{
groupLabel: "",
id: "management-user",
menus: [
{
id: "management-user-menu",
href: "/admin/management-user",
label: "Management User",
active: pathname.includes("/management-user"),
icon: "clarity:users-solid",
submenus: [],
},
],
},
{
groupLabel: "",
id: "tenant",
menus: [
{
id: "tenant",
href: "/admin/tenants",
label: "Tenant",
active: pathname.includes("/tenant"),
icon: "material-symbols:domain",
submenus: [],
},
],
},
{
groupLabel: "",
id: "menu-management",
menus: [
{
id: "menu-management",
href: "/admin/settings/menu-management",
label: "Menu Management",
active: pathname === "/admin/settings/menu-management",
icon: "heroicons:bars-3",
submenus: [],
},
],
},
// {
// groupLabel: "",
// id: "module-management",
// menus: [
// {
// id: "module-management",
// href: "/admin/settings/module-management",
// label: "Module Management",
// active: pathname === "/admin/settings/module-management",
// icon: "heroicons:puzzle-piece",
// submenus: [],
// },
// ],
// },
]
: []),
? [
{
groupLabel: "",
id: "management-user",
menus: [
{
id: "management-user-menu",
href: "/admin/management-user",
label: "Management User",
active: pathname.includes("/management-user"),
icon: "clarity:users-solid",
submenus: [],
},
],
},
{
groupLabel: "",
id: "tenant",
menus: [
{
id: "tenant",
href: "/admin/tenants",
label: "Tenant",
active: pathname.includes("/tenant"),
icon: "material-symbols:domain",
submenus: [],
},
],
},
{
groupLabel: "",
id: "menu-management",
menus: [
{
id: "menu-management",
href: "/admin/settings/menu-management",
label: "Menu Management",
active: pathname === "/admin/settings/menu-management",
icon: "heroicons:bars-3",
submenus: [],
},
],
},
// {
// groupLabel: "",
// id: "module-management",
// menus: [
// {
// id: "module-management",
// href: "/admin/settings/module-management",
// label: "Module Management",
// active: pathname === "/admin/settings/module-management",
// icon: "heroicons:puzzle-piece",
// submenus: [],
// },
// ],
// },
]
: []),
];
return menusSelected;
}
@ -5470,7 +5507,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-area",
label: t("areaCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-area"
"/charts/appex-charts/charts-appex-area",
),
children: [],
},
@ -5478,7 +5515,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-bar",
label: t("barCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-bar"
"/charts/appex-charts/charts-appex-bar",
),
children: [],
},
@ -5486,7 +5523,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-boxplot",
label: t("boxplotCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-boxplot"
"/charts/appex-charts/charts-appex-boxplot",
),
children: [],
},
@ -5494,7 +5531,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-bubble",
label: t("bubbleCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-bubble"
"/charts/appex-charts/charts-appex-bubble",
),
children: [],
},
@ -5502,7 +5539,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-candlestick",
label: t("candlestickCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-candlestick"
"/charts/appex-charts/charts-appex-candlestick",
),
children: [],
},
@ -5510,7 +5547,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-column",
label: t("columnCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-column"
"/charts/appex-charts/charts-appex-column",
),
children: [],
},
@ -5518,7 +5555,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-combo",
label: t("comboCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-combo"
"/charts/appex-charts/charts-appex-combo",
),
children: [],
},
@ -5527,7 +5564,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-funnel",
label: t("funnelCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-funnel"
"/charts/appex-charts/charts-appex-funnel",
),
children: [],
},
@ -5535,7 +5572,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-heatmap",
label: t("heatmapCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-heatmap"
"/charts/appex-charts/charts-appex-heatmap",
),
children: [],
},
@ -5543,7 +5580,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-line",
label: t("lineCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-line"
"/charts/appex-charts/charts-appex-line",
),
children: [],
},
@ -5551,7 +5588,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-pie",
label: t("pieCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-pie"
"/charts/appex-charts/charts-appex-pie",
),
children: [],
},
@ -5559,7 +5596,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-polararea",
label: t("ploarareaCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-polararea"
"/charts/appex-charts/charts-appex-polararea",
),
children: [],
},
@ -5567,7 +5604,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-radar",
label: t("radarCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-radar"
"/charts/appex-charts/charts-appex-radar",
),
children: [],
},
@ -5575,7 +5612,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-radialbars",
label: t("radialbarCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-radialbars"
"/charts/appex-charts/charts-appex-radialbars",
),
children: [],
},
@ -5583,7 +5620,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-range",
label: t("rangeCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-range"
"/charts/appex-charts/charts-appex-range",
),
children: [],
},
@ -5591,7 +5628,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-scatter",
label: t("scatterCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-scatter"
"/charts/appex-charts/charts-appex-scatter",
),
children: [],
},
@ -5599,7 +5636,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-timeline",
label: t("timelineCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-timeline"
"/charts/appex-charts/charts-appex-timeline",
),
children: [],
},
@ -5607,7 +5644,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/appex-charts/charts-appex-treemap",
label: t("treemapCharts"),
active: pathname.includes(
"/charts/appex-charts/charts-appex-treemap"
"/charts/appex-charts/charts-appex-treemap",
),
children: [],
},
@ -5623,7 +5660,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-area",
label: t("areaCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-area"
"/charts/rechart/charts-rechart-area",
),
children: [],
},
@ -5631,7 +5668,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-bar",
label: t("barCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-bar"
"/charts/rechart/charts-rechart-bar",
),
children: [],
},
@ -5639,7 +5676,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-composed",
label: t("composedCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-composed"
"/charts/rechart/charts-rechart-composed",
),
children: [],
},
@ -5647,7 +5684,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-line",
label: t("lineCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-line"
"/charts/rechart/charts-rechart-line",
),
children: [],
},
@ -5655,7 +5692,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-pie",
label: t("pieCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-pie"
"/charts/rechart/charts-rechart-pie",
),
children: [],
},
@ -5663,7 +5700,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-radar",
label: t("radarCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-radar"
"/charts/rechart/charts-rechart-radar",
),
children: [],
},
@ -5671,7 +5708,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-radialbar",
label: t("radialbarCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-radialbar"
"/charts/rechart/charts-rechart-radialbar",
),
children: [],
},
@ -5679,7 +5716,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-scatter",
label: t("scatterCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-scatter"
"/charts/rechart/charts-rechart-scatter",
),
children: [],
},
@ -5687,7 +5724,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/rechart/charts-rechart-treemap",
label: t("treemapCharts"),
active: pathname.includes(
"/charts/rechart/charts-rechart-treemap"
"/charts/rechart/charts-rechart-treemap",
),
children: [],
},
@ -5703,7 +5740,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-area",
label: t("areaCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-area"
"/charts/chart-js/charts-chartjs-area",
),
children: [],
},
@ -5711,7 +5748,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-bar",
label: t("barCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-bar"
"/charts/chart-js/charts-chartjs-bar",
),
children: [],
},
@ -5719,7 +5756,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-line",
label: t("lineCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-line"
"/charts/chart-js/charts-chartjs-line",
),
children: [],
},
@ -5727,7 +5764,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-animations",
label: t("animationCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-animations"
"/charts/chart-js/charts-chartjs-animations",
),
children: [],
},
@ -5735,7 +5772,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-legend",
label: t("legendCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-legend"
"/charts/chart-js/charts-chartjs-legend",
),
children: [],
},
@ -5743,7 +5780,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-scaleoptions",
label: t("scaleOptionCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-scaleoptions"
"/charts/chart-js/charts-chartjs-scaleoptions",
),
children: [],
},
@ -5751,7 +5788,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-scales",
label: t("scaleCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-scales"
"/charts/chart-js/charts-chartjs-scales",
),
children: [],
},
@ -5759,7 +5796,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-scriptable",
label: t("scriptableCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-scriptable"
"/charts/chart-js/charts-chartjs-scriptable",
),
children: [],
},
@ -5767,7 +5804,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-title",
label: t("titleCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-title"
"/charts/chart-js/charts-chartjs-title",
),
children: [],
},
@ -5775,7 +5812,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-tooltip",
label: t("tooltipChart"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-tooltip"
"/charts/chart-js/charts-chartjs-tooltip",
),
children: [],
},
@ -5783,7 +5820,7 @@ export function getHorizontalMenuList(pathname: string, t: any): Group[] {
href: "/charts/chart-js/charts-chartjs-other",
label: t("otherCharts"),
active: pathname.includes(
"/charts/chart-js/charts-chartjs-other"
"/charts/chart-js/charts-chartjs-other",
),
children: [],
},