feat: fixing role id usage, fixing content website, etc
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
7515f55e88
commit
71c65f9a60
4
.env
4
.env
|
|
@ -1,3 +1,3 @@
|
|||
MEDOLS_CLIENT_KEY=bb65b1ad-e954-4a1a-b4d0-74df5bb0b640
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8800
|
||||
# NEXT_PUBLIC_API_URL=https://qudo.id/api
|
||||
# NEXT_PUBLIC_API_URL=http://localhost:8800
|
||||
NEXT_PUBLIC_API_URL=https://qudo.id/api
|
||||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
import Cookies from "js-cookie";
|
||||
import ContentWebsite from "@/components/main/content-website";
|
||||
import {
|
||||
isApproverOrAdmin,
|
||||
isContributorRole,
|
||||
} from "@/constants/user-roles";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
|
|
@ -13,8 +17,7 @@ export default function ContentWebsitePage() {
|
|||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const ulne = Cookies.get("ulne");
|
||||
setLevelId(ulne);
|
||||
setLevelId(Cookies.get("urie"));
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
|
|
@ -33,10 +36,10 @@ export default function ContentWebsitePage() {
|
|||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<div className="p-6">
|
||||
{levelId === "3" ? (
|
||||
{isApproverOrAdmin(levelId) ? (
|
||||
<ApproverContentWebsite />
|
||||
) : (
|
||||
<ContentWebsite contributorMode={levelId === "2"} />
|
||||
<ContentWebsite contributorMode={isContributorRole(levelId)} />
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
|
|||
|
|
@ -12,9 +12,35 @@ const geistMono = Geist_Mono({
|
|||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const siteDescription =
|
||||
"Qudoco — portal konten dan layanan untuk mengelola website, berita, serta aset media dengan alur kerja yang terstruktur.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
metadataBase: new URL("https://qudo.id"),
|
||||
title: {
|
||||
default: "Qudoco",
|
||||
template: "%s | Qudoco",
|
||||
},
|
||||
description: siteDescription,
|
||||
applicationName: "Qudoco",
|
||||
keywords: ["Qudoco", "portal konten", "CMS", "berita", "layanan"],
|
||||
authors: [{ name: "Qudoco" }],
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "id_ID",
|
||||
siteName: "Qudoco",
|
||||
title: "Qudoco",
|
||||
description: siteDescription,
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Qudoco",
|
||||
description: siteDescription,
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
|
@ -23,7 +49,7 @@ export default function RootLayout({
|
|||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="id">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
|
|
|
|||
17
app/page.tsx
17
app/page.tsx
|
|
@ -1,3 +1,4 @@
|
|||
import type { Metadata } from "next";
|
||||
import Header from "@/components/landing-page/headers";
|
||||
import AboutSection from "@/components/landing-page/about";
|
||||
import ProductSection from "@/components/landing-page/product";
|
||||
|
|
@ -15,6 +16,22 @@ import type {
|
|||
CmsServiceContent,
|
||||
} from "@/types/cms-landing";
|
||||
|
||||
const landingDescription =
|
||||
"Jelajahi layanan, produk, dan informasi terbaru dari Qudoco — satu portal untuk konten profesional dan komunikasi yang jelas.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Beranda",
|
||||
description: landingDescription,
|
||||
openGraph: {
|
||||
title: "Beranda | Qudoco",
|
||||
description: landingDescription,
|
||||
url: "/",
|
||||
},
|
||||
alternates: {
|
||||
canonical: "/",
|
||||
},
|
||||
};
|
||||
|
||||
export default async function Home() {
|
||||
const [hero, aboutList, productList, serviceList, partners, popupList] =
|
||||
await Promise.all([
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ import { useParams, useRouter } from "next/navigation";
|
|||
import GetSeoScore from "./get-seo-score-form";
|
||||
import Link from "next/link";
|
||||
import Cookies from "js-cookie";
|
||||
import {
|
||||
isApproverOrAdmin,
|
||||
isContributorRole,
|
||||
} from "@/constants/user-roles";
|
||||
import {
|
||||
createArticleSchedule,
|
||||
deleteArticleFiles,
|
||||
|
|
@ -162,8 +166,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
|||
const [levelId, setLevelId] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const ulne = Cookies.get("ulne");
|
||||
setLevelId(ulne);
|
||||
setLevelId(Cookies.get("urie"));
|
||||
}, []);
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
|
|
@ -812,7 +815,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
|||
|
||||
{/* ================= ACTION BUTTON ================= */}
|
||||
<div className="space-y-3">
|
||||
{levelId === "2" && !detailData?.isPublish && (
|
||||
{isApproverOrAdmin(levelId) && !detailData?.isPublish && (
|
||||
<>
|
||||
<Button
|
||||
onClick={doPublish}
|
||||
|
|
@ -844,7 +847,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
|
|||
)}
|
||||
|
||||
{/* 🔥 Jika levelId 3 → hanya tampilkan Cancel */}
|
||||
{levelId === "3" && (
|
||||
{isContributorRole(levelId) && (
|
||||
<Link href="/admin/news-article/image">
|
||||
<Button variant="outline" className="w-full py-3 rounded-xl">
|
||||
Cancel
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ interface SidebarSection {
|
|||
children?: SidebarItem[];
|
||||
}
|
||||
|
||||
const getSidebarByLevel = (levelId: string | null) => {
|
||||
if (levelId === "1") {
|
||||
const getSidebarByRole = (roleId: string | null) => {
|
||||
if (roleId === "1") {
|
||||
return [
|
||||
{
|
||||
title: "Dashboard",
|
||||
|
|
@ -64,7 +64,7 @@ const getSidebarByLevel = (levelId: string | null) => {
|
|||
];
|
||||
}
|
||||
|
||||
if (levelId === "3") {
|
||||
if (roleId === "2" || roleId === "3") {
|
||||
return [
|
||||
{
|
||||
title: "Dashboard",
|
||||
|
|
@ -141,66 +141,7 @@ const getSidebarByLevel = (levelId: string | null) => {
|
|||
];
|
||||
}
|
||||
|
||||
if (levelId === "2") {
|
||||
return [
|
||||
{
|
||||
title: "Dashboard",
|
||||
items: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
icon: () => (
|
||||
<Icon icon="material-symbols:dashboard" className="text-lg" />
|
||||
),
|
||||
link: "/admin/dashboard",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [
|
||||
{
|
||||
title: "Content Website",
|
||||
icon: () => <Icon icon="ri:article-line" className="text-lg" />,
|
||||
link: "/admin/content-website",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "News & Article",
|
||||
items: [
|
||||
{
|
||||
title: "News & Article",
|
||||
icon: () => (
|
||||
<Icon icon="grommet-icons:article" className="text-lg" />
|
||||
),
|
||||
children: [
|
||||
{
|
||||
title: "Text",
|
||||
icon: () => <Icon icon="mdi:file-document-outline" />,
|
||||
link: "/admin/news-article/text",
|
||||
},
|
||||
{
|
||||
title: "Image",
|
||||
icon: () => <Icon icon="mdi:image-outline" />,
|
||||
link: "/admin/news-article/image",
|
||||
},
|
||||
{
|
||||
title: "Video",
|
||||
icon: () => <Icon icon="mdi:video-outline" />,
|
||||
link: "/admin/news-article/video",
|
||||
},
|
||||
{
|
||||
title: "Audio",
|
||||
icon: () => <Icon icon="mdi:music-note-outline" />,
|
||||
link: "/admin/news-article/audio",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// fallback kalau Level tidak dikenal
|
||||
// fallback jika role tidak dikenal
|
||||
return [];
|
||||
};
|
||||
|
||||
|
|
@ -334,10 +275,10 @@ const SidebarContent = ({
|
|||
};
|
||||
|
||||
const cookieUsername = getCookie("username");
|
||||
const cookieLevelId = getCookie("ulne");
|
||||
const cookieRoleId = getCookie("urie");
|
||||
|
||||
if (cookieUsername) setUsername(cookieUsername);
|
||||
if (cookieLevelId) setLevelId(cookieLevelId);
|
||||
if (cookieRoleId) setLevelId(cookieRoleId);
|
||||
}, []);
|
||||
|
||||
// ===============================
|
||||
|
|
@ -357,7 +298,7 @@ const SidebarContent = ({
|
|||
);
|
||||
};
|
||||
|
||||
const sidebarSections = getSidebarByLevel(LevelId);
|
||||
const sidebarSections = getSidebarByRole(LevelId);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Filter, Loader2 } from "lucide-react";
|
||||
import { ExternalLink, Filter, Loader2 } from "lucide-react";
|
||||
import {
|
||||
approveCmsContentSubmission,
|
||||
listCmsContentSubmissions,
|
||||
|
|
@ -22,6 +22,7 @@ import {
|
|||
import { apiPayload } from "@/service/cms-landing";
|
||||
import { formatDate } from "@/utils/global";
|
||||
import Swal from "sweetalert2";
|
||||
import ContentWebsite from "@/components/main/content-website";
|
||||
|
||||
const DOMAIN_LABEL: Record<string, string> = {
|
||||
hero: "Hero",
|
||||
|
|
@ -32,6 +33,16 @@ const DOMAIN_LABEL: Record<string, string> = {
|
|||
popup: "Pop Up",
|
||||
};
|
||||
|
||||
/** Maps submission `domain` to ContentWebsite tab `value`. */
|
||||
const DOMAIN_TO_TAB: Record<string, string> = {
|
||||
hero: "hero",
|
||||
about: "about",
|
||||
product: "products",
|
||||
service: "services",
|
||||
partner: "partners",
|
||||
popup: "popup",
|
||||
};
|
||||
|
||||
type Row = {
|
||||
id: string;
|
||||
domain: string;
|
||||
|
|
@ -48,6 +59,9 @@ export default function ApproverContentWebsite() {
|
|||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState("");
|
||||
const [actingId, setActingId] = useState<string | null>(null);
|
||||
const [tabFocusSignal, setTabFocusSignal] = useState(0);
|
||||
const [tabFocusTarget, setTabFocusTarget] = useState("hero");
|
||||
const [liveDataReloadSignal, setLiveDataReloadSignal] = useState(0);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
|
|
@ -78,6 +92,18 @@ export default function ApproverContentWebsite() {
|
|||
);
|
||||
});
|
||||
|
||||
function jumpToLiveTab(domain: string) {
|
||||
const tab = DOMAIN_TO_TAB[domain];
|
||||
if (!tab) return;
|
||||
setTabFocusTarget(tab);
|
||||
setTabFocusSignal((n) => n + 1);
|
||||
requestAnimationFrame(() => {
|
||||
document
|
||||
.getElementById("approver-live-cms")
|
||||
?.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
});
|
||||
}
|
||||
|
||||
async function onApprove(id: string) {
|
||||
const ok = await Swal.fire({
|
||||
icon: "question",
|
||||
|
|
@ -102,6 +128,7 @@ export default function ApproverContentWebsite() {
|
|||
timer: 1600,
|
||||
showConfirmButton: false,
|
||||
});
|
||||
setLiveDataReloadSignal((n) => n + 1);
|
||||
await load();
|
||||
} finally {
|
||||
setActingId(null);
|
||||
|
|
@ -144,17 +171,33 @@ export default function ApproverContentWebsite() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-10">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-slate-800">Content Website</h1>
|
||||
<p className="text-slate-500">
|
||||
Tinjau pengajuan perubahan dari kontributor dan terapkan ke konten live.
|
||||
<p className="mt-1 text-slate-500">
|
||||
Di bagian atas: pengajuan baru yang perlu disetujui. Di bawah: konten
|
||||
live di semua tab (hanya lihat) untuk membandingkan dengan pengajuan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="space-y-4">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h2 className="text-lg font-semibold text-slate-900">
|
||||
Perubahan menunggu persetujuan
|
||||
</h2>
|
||||
<Badge variant="secondary" className="font-normal">
|
||||
{filtered.length} pending
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">
|
||||
Setujui atau tolak di sini. Gunakan{" "}
|
||||
<span className="font-medium text-slate-700">Lihat konten live</span>{" "}
|
||||
untuk membuka tab yang sama dengan bagian yang diajukan.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<Input
|
||||
placeholder="Cari judul, domain, atau nama pengaju…"
|
||||
placeholder="Cari judul, bagian, atau nama pengaju…"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="max-w-md"
|
||||
|
|
@ -189,13 +232,16 @@ export default function ApproverContentWebsite() {
|
|||
<TableHead>Bagian</TableHead>
|
||||
<TableHead>Pengaju</TableHead>
|
||||
<TableHead>Tanggal</TableHead>
|
||||
<TableHead className="text-right">Aksi</TableHead>
|
||||
<TableHead className="text-right w-[280px]">Aksi</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="text-center text-slate-500 py-10">
|
||||
<TableCell
|
||||
colSpan={5}
|
||||
className="text-center text-slate-500 py-10"
|
||||
>
|
||||
Tidak ada pengajuan tertunda.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -211,12 +257,24 @@ export default function ApproverContentWebsite() {
|
|||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-slate-600">
|
||||
{item.submitter_name || `User #${item.submitted_by_id}`}
|
||||
{item.submitter_name ||
|
||||
`User #${item.submitted_by_id}`}
|
||||
</TableCell>
|
||||
<TableCell className="text-slate-600">
|
||||
{formatDate(item.created_at)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right space-x-2">
|
||||
<TableCell className="text-right">
|
||||
<div className="flex flex-wrap justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="gap-1"
|
||||
onClick={() => jumpToLiveTab(item.domain)}
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
Lihat konten live
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
|
|
@ -236,6 +294,7 @@ export default function ApproverContentWebsite() {
|
|||
>
|
||||
Tolak
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
|
|
@ -245,6 +304,30 @@ export default function ApproverContentWebsite() {
|
|||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="approver-live-cms"
|
||||
className="space-y-4 border-t border-slate-200 pt-10 scroll-mt-6"
|
||||
>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900">
|
||||
Konten live (semua tab, hanya lihat)
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Data yang sedang ditampilkan di website. Field tidak dapat diubah di
|
||||
sini — bandingkan dengan baris pengajuan di atas sebelum
|
||||
menyetujui.
|
||||
</p>
|
||||
</div>
|
||||
<ContentWebsite
|
||||
viewOnly
|
||||
hideHeader
|
||||
tabFocusSignal={tabFocusSignal}
|
||||
tabFocusTarget={tabFocusTarget}
|
||||
liveDataReloadSignal={liveDataReloadSignal}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,16 +91,33 @@ function setPickedFile(
|
|||
type ContentWebsiteProps = {
|
||||
/** User level 2: changes go through approval instead of live CMS APIs. */
|
||||
contributorMode?: boolean;
|
||||
/** Approver (or admin): load live CMS data but disable all edits (all tabs). */
|
||||
viewOnly?: boolean;
|
||||
/** Omit page title/actions row — parent supplies section headings (e.g. approver layout). */
|
||||
hideHeader?: boolean;
|
||||
/** Parent increments this (e.g. 1,2,3…) to switch the visible tab. */
|
||||
tabFocusSignal?: number;
|
||||
/** Tab id matching `TabsTrigger` values: hero | about | products | services | partners | popup */
|
||||
tabFocusTarget?: string;
|
||||
/** Increment (e.g. after approver applies CMS) to reload live data from API without remounting. */
|
||||
liveDataReloadSignal?: number;
|
||||
};
|
||||
|
||||
export default function ContentWebsite({
|
||||
contributorMode = false,
|
||||
viewOnly = false,
|
||||
hideHeader = false,
|
||||
tabFocusSignal = 0,
|
||||
tabFocusTarget = "",
|
||||
liveDataReloadSignal = 0,
|
||||
}: ContentWebsiteProps) {
|
||||
const [activeTab, setActiveTab] = useState("hero");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [editMode, setEditMode] = useState(!contributorMode);
|
||||
const canInteract = !contributorMode || editMode;
|
||||
const canInteract = (!contributorMode || editMode) && !viewOnly;
|
||||
const dimContributorPreview =
|
||||
contributorMode && !editMode && !viewOnly;
|
||||
|
||||
const [heroId, setHeroId] = useState<string | null>(null);
|
||||
const [heroImageId, setHeroImageId] = useState<string | null>(null);
|
||||
|
|
@ -262,6 +279,24 @@ export default function ContentWebsite({
|
|||
loadAll();
|
||||
}, [loadAll]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!viewOnly) return;
|
||||
setProductModalOpen(false);
|
||||
setServiceModalOpen(false);
|
||||
setPartnerModalOpen(false);
|
||||
setPopupModalOpen(false);
|
||||
}, [viewOnly]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tabFocusSignal < 1 || !tabFocusTarget) return;
|
||||
setActiveTab(tabFocusTarget);
|
||||
}, [tabFocusSignal, tabFocusTarget]);
|
||||
|
||||
useEffect(() => {
|
||||
if (liveDataReloadSignal < 1) return;
|
||||
void loadAll();
|
||||
}, [liveDataReloadSignal, loadAll]);
|
||||
|
||||
async function saveHeroTab() {
|
||||
if (!heroPrimary.trim()) {
|
||||
await Swal.fire({ icon: "warning", title: "Main title is required" });
|
||||
|
|
@ -1239,11 +1274,16 @@ export default function ContentWebsite({
|
|||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{!hideHeader ? (
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-slate-800">Content Website</h1>
|
||||
<h1 className="text-2xl font-semibold text-slate-800">
|
||||
Content Website
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Update homepage content, products, services, and partners.
|
||||
{viewOnly
|
||||
? "Konten live di semua tab: hanya lihat. Pengajuan perubahan ditangani di bagian atas halaman."
|
||||
: "Update homepage content, products, services, and partners."}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
|
|
@ -1268,8 +1308,21 @@ export default function ContentWebsite({
|
|||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : viewOnly ? (
|
||||
<div className="flex flex-wrap justify-end gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="rounded-lg"
|
||||
onClick={() => window.open("/", "_blank")}
|
||||
>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Preview website
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{contributorMode && !editMode ? (
|
||||
{contributorMode && !editMode && !viewOnly ? (
|
||||
<p className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">
|
||||
Aktifkan <strong>Edit Mode</strong> untuk mengusulkan perubahan. Perubahan akan masuk ke{" "}
|
||||
<strong>My Content</strong> menunggu persetujuan approver. Unggah file gambar tidak tersedia sebagai kontributor;
|
||||
|
|
@ -1279,7 +1332,9 @@ export default function ContentWebsite({
|
|||
|
||||
<div
|
||||
className={
|
||||
canInteract ? "" : "pointer-events-none select-none opacity-50"
|
||||
dimContributorPreview
|
||||
? "pointer-events-none select-none opacity-50"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
|
|
@ -1305,6 +1360,10 @@ export default function ContentWebsite({
|
|||
</TabsList>
|
||||
|
||||
<TabsContent value="hero" className="mt-4">
|
||||
<fieldset
|
||||
disabled={viewOnly}
|
||||
className="min-w-0 border-0 p-0 m-0"
|
||||
>
|
||||
<Card className="rounded-2xl border shadow-sm">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
|
|
@ -1415,9 +1474,14 @@ export default function ContentWebsite({
|
|||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</fieldset>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="about" className="mt-4">
|
||||
<fieldset
|
||||
disabled={viewOnly}
|
||||
className="min-w-0 border-0 p-0 m-0"
|
||||
>
|
||||
<Card className="rounded-2xl border shadow-sm">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
|
|
@ -1562,9 +1626,14 @@ export default function ContentWebsite({
|
|||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</fieldset>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="products" className="mt-4">
|
||||
<fieldset
|
||||
disabled={viewOnly}
|
||||
className="min-w-0 border-0 p-0 m-0"
|
||||
>
|
||||
<Card className="rounded-2xl border shadow-sm">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
|
|
@ -1752,9 +1821,14 @@ export default function ContentWebsite({
|
|||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</fieldset>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="services" className="mt-4">
|
||||
<fieldset
|
||||
disabled={viewOnly}
|
||||
className="min-w-0 border-0 p-0 m-0"
|
||||
>
|
||||
<Card className="rounded-2xl border shadow-sm">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
|
|
@ -1942,9 +2016,14 @@ export default function ContentWebsite({
|
|||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</fieldset>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="partners" className="mt-4">
|
||||
<fieldset
|
||||
disabled={viewOnly}
|
||||
className="min-w-0 border-0 p-0 m-0"
|
||||
>
|
||||
<Card className="rounded-2xl border shadow-sm">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
|
|
@ -2102,9 +2181,14 @@ export default function ContentWebsite({
|
|||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</fieldset>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="popup" className="mt-4">
|
||||
<fieldset
|
||||
disabled={viewOnly}
|
||||
className="min-w-0 border-0 p-0 m-0"
|
||||
>
|
||||
<Card className="rounded-2xl border shadow-sm">
|
||||
<CardContent className="space-y-6 p-6">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
|
|
@ -2295,6 +2379,7 @@ export default function ContentWebsite({
|
|||
</Dialog>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</fieldset>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ interface PostCount {
|
|||
export default function DashboardContainer() {
|
||||
const [levelName, setLevelName] = useState<string | undefined>();
|
||||
useEffect(() => {
|
||||
const levelId = Cookies.get("ulne");
|
||||
setLevelName(levelId);
|
||||
const roleId = Cookies.get("urie");
|
||||
setLevelName(roleId);
|
||||
}, []);
|
||||
|
||||
const username = Cookies.get("username");
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ import { listCmsContentSubmissions } from "@/service/cms-content-submissions";
|
|||
import { getArticlesForMyContent } from "@/service/article";
|
||||
import { apiPayload } from "@/service/cms-landing";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import {
|
||||
isApproverOrAdmin,
|
||||
isContributorRole,
|
||||
} from "@/constants/user-roles";
|
||||
|
||||
const PLACEHOLDER_IMG =
|
||||
"https://placehold.co/400x240/f1f5f9/64748b?text=Content";
|
||||
|
|
@ -149,11 +153,11 @@ export default function MyContent() {
|
|||
const [articleRows, setArticleRows] = useState<ArticleRow[]>([]);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const isApprover = levelId === "3";
|
||||
const isContributor = levelId === "2";
|
||||
const isContributor = isContributorRole(levelId);
|
||||
const isApprover = isApproverOrAdmin(levelId);
|
||||
|
||||
useEffect(() => {
|
||||
setLevelId(Cookies.get("ulne"));
|
||||
setLevelId(Cookies.get("urie"));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { getArticlePagination, deleteArticle } from "@/service/article";
|
|||
import { formatDate } from "@/utils/global";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { isContributorRole } from "@/constants/user-roles";
|
||||
import Swal from "sweetalert2";
|
||||
import type { ArticleContentKind } from "@/constants/article-content-types";
|
||||
import { ARTICLE_KIND_LABEL, articleListPath } from "@/constants/article-content-types";
|
||||
|
|
@ -37,8 +38,7 @@ export default function NewsArticleList({ kind, typeId }: Props) {
|
|||
const [levelId, setLevelId] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const ulne = Cookies.get("ulne");
|
||||
setLevelId(ulne);
|
||||
setLevelId(Cookies.get("urie"));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -128,7 +128,7 @@ export default function NewsArticleList({ kind, typeId }: Props) {
|
|||
Create and manage {label.toLowerCase()} articles. Organize with tags only.
|
||||
</p>
|
||||
</div>
|
||||
{levelId === "3" && (
|
||||
{isContributorRole(levelId) && (
|
||||
<Link href={`${basePath}/create`}>
|
||||
<Button className="bg-blue-600 hover:bg-blue-700 rounded-lg">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
|
|
@ -203,7 +203,7 @@ export default function NewsArticleList({ kind, typeId }: Props) {
|
|||
<Pencil className="w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
{levelId === "3" && (
|
||||
{isContributorRole(levelId) && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { getArticleByCategory, getArticlePagination } from "@/service/article";
|
|||
import { formatDate } from "@/utils/global";
|
||||
import { close, loading } from "@/config/swal";
|
||||
import Cookies from "js-cookie";
|
||||
import { isContributorRole } from "@/constants/user-roles";
|
||||
|
||||
export default function NewsImage() {
|
||||
const [articles, setArticles] = useState<any[]>([]);
|
||||
|
|
@ -30,8 +31,7 @@ export default function NewsImage() {
|
|||
const [selectedCategory, setSelectedCategory] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const ulne = Cookies.get("ulne");
|
||||
setLevelId(ulne);
|
||||
setLevelId(Cookies.get("urie"));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -127,7 +127,7 @@ export default function NewsImage() {
|
|||
Create and manage news articles and blog posts
|
||||
</p>
|
||||
</div>
|
||||
{levelId === "3" && (
|
||||
{isContributorRole(levelId) && (
|
||||
<Link href={"/admin/news-article/image/create"}>
|
||||
<Button className="bg-blue-600 hover:bg-blue-700 rounded-lg">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
/** Matches cookie `urie` / profile `userRoleId`. */
|
||||
export const USER_ROLE_ADMIN = "1";
|
||||
export const USER_ROLE_APPROVER = "2";
|
||||
export const USER_ROLE_CONTRIBUTOR = "3";
|
||||
|
||||
export function isApproverOrAdmin(
|
||||
roleId: string | undefined | null,
|
||||
): boolean {
|
||||
return roleId === USER_ROLE_ADMIN || roleId === USER_ROLE_APPROVER;
|
||||
}
|
||||
|
||||
export function isContributorRole(
|
||||
roleId: string | undefined | null,
|
||||
): boolean {
|
||||
return roleId === USER_ROLE_CONTRIBUTOR;
|
||||
}
|
||||
Loading…
Reference in New Issue