Merge pull request #227 from bipproduction/fix/bug/middleware

fix ( middleware )
This commit is contained in:
Bagasbanuna02
2025-01-08 10:42:19 +08:00
committed by GitHub
36 changed files with 1206 additions and 450 deletions

View File

@@ -0,0 +1,15 @@
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server';
export const dynamic = "force-dynamic";
export async function GET(request: Request) {
try {
const cookiesKey = process.env.NEXT_PUBLIC_BASE_SESSION_KEY!
const cookieStore = cookies();
const hipmiKey = cookieStore.get(cookiesKey)?.value || '';
return NextResponse.json({ token: hipmiKey });
} catch (error) {
console.error(error);
return NextResponse.json({ error }, { status: 500 });
}
}

View File

@@ -4,6 +4,8 @@ import { NextResponse } from "next/server";
export async function DELETE(req: Request) { export async function DELETE(req: Request) {
const data = await req.json(); const data = await req.json();
console.log("data request =>", data);
const id = data.fileId; const id = data.fileId;
const dirId = data.dirId; const dirId = data.dirId;
@@ -25,9 +27,7 @@ export async function DELETE(req: Request) {
backendLogger.info("Server status code: " + res.status); backendLogger.info("Server status code: " + res.status);
const data = await res.json(); const data = await res.json();
if (res.ok) { if (res.ok) {
backendLogger.info( backendLogger.info(`Success delete ${keyOfDirectory}`);
`Success delete ${keyOfDirectory}`
);
return NextResponse.json({ success: true }); return NextResponse.json({ success: true });
} else { } else {
const errorText = await res.json(); const errorText = await res.json();

View File

@@ -1,173 +1,234 @@
import { prisma } from "@/app/lib"; import { DIRECTORY_ID, prisma } from "@/app/lib";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import fs from "fs"; import fs from "fs";
import { funGlobal_DeleteFileById } from "@/app_modules/_global/fun";
import { apiDeleteImageById } from "@/app_modules/_global/lib/api_image";
import backendLogger from "@/util/backendLogger";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
// GET ONE DATA PORTOFOLIO BY ID PORTOFOLIO // GET ONE DATA PORTOFOLIO BY ID PORTOFOLIO
export async function GET(request: Request, context: { params: { id: string } }) { export async function GET(
try { request: Request,
let dataFix context: { params: { id: string } }
const { id } = context.params; ) {
const { searchParams } = new URL(request.url); try {
const kategori = searchParams.get('cat'); let dataFix;
const { id } = context.params;
const { searchParams } = new URL(request.url);
const kategori = searchParams.get("cat");
if (kategori == "bisnis") { if (kategori == "bisnis") {
const data = await prisma.portofolio.findUnique({ const data = await prisma.portofolio.findUnique({
where: { where: {
id: id, id: id,
}, },
select: {
id_Portofolio: true,
namaBisnis: true,
alamatKantor: true,
tlpn: true,
deskripsi: true,
logoId: true,
MasterBidangBisnis: {
select: { select: {
id_Portofolio: true, name: true,
namaBisnis: true,
alamatKantor: true,
tlpn: true,
deskripsi: true,
logoId: true,
MasterBidangBisnis: {
select: {
name: true
}
},
Profile: {
select: {
userId: true
}
}
}
});
dataFix = {
id_Portofolio: data?.id_Portofolio,
namaBisnis: data?.namaBisnis,
alamatKantor: data?.alamatKantor,
tlpn: data?.tlpn,
deskripsi: data?.deskripsi,
logoId: data?.logoId,
bidangBisnis: data?.MasterBidangBisnis?.name,
authorId: data?.Profile?.userId
}
} else if (kategori == "lokasi") {
const data = await prisma.portofolio.findUnique({
where: {
id: id,
}, },
},
Profile: {
select: { select: {
logoId: true, userId: true,
BusinessMaps: {
select: {
id: true,
namePin: true,
latitude: true,
longitude: true,
imageId: true,
pinId: true
}
}
}
});
dataFix = {
mapId: data?.BusinessMaps?.id,
logoId: data?.logoId,
namePin: data?.BusinessMaps?.namePin,
latitude: data?.BusinessMaps?.latitude,
longitude: data?.BusinessMaps?.longitude,
imageId: data?.BusinessMaps?.imageId,
pinId: data?.BusinessMaps?.pinId
}
} else if (kategori == "sosmed") {
const data = await prisma.portofolio.findUnique({
where: {
id: id,
}, },
},
},
});
dataFix = {
id_Portofolio: data?.id_Portofolio,
namaBisnis: data?.namaBisnis,
alamatKantor: data?.alamatKantor,
tlpn: data?.tlpn,
deskripsi: data?.deskripsi,
logoId: data?.logoId,
bidangBisnis: data?.MasterBidangBisnis?.name,
authorId: data?.Profile?.userId,
};
} else if (kategori == "lokasi") {
const data = await prisma.portofolio.findUnique({
where: {
id: id,
},
select: {
logoId: true,
BusinessMaps: {
select: { select: {
Portofolio_MediaSosial: { id: true,
select: { namePin: true,
facebook: true, latitude: true,
twitter: true, longitude: true,
instagram: true, imageId: true,
tiktok: true, pinId: true,
youtube: true },
} },
} },
} });
});
dataFix = { dataFix = {
facebook: data?.Portofolio_MediaSosial?.facebook, mapId: data?.BusinessMaps?.id,
twitter: data?.Portofolio_MediaSosial?.twitter, logoId: data?.logoId,
instagram: data?.Portofolio_MediaSosial?.instagram, namePin: data?.BusinessMaps?.namePin,
tiktok: data?.Portofolio_MediaSosial?.tiktok, latitude: data?.BusinessMaps?.latitude,
youtube: data?.Portofolio_MediaSosial?.youtube longitude: data?.BusinessMaps?.longitude,
} imageId: data?.BusinessMaps?.imageId,
} pinId: data?.BusinessMaps?.pinId,
};
} else if (kategori == "sosmed") {
const data = await prisma.portofolio.findUnique({
where: {
id: id,
},
select: {
Portofolio_MediaSosial: {
select: {
facebook: true,
twitter: true,
instagram: true,
tiktok: true,
youtube: true,
},
},
},
});
return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: dataFix }, { status: 200 }); dataFix = {
facebook: data?.Portofolio_MediaSosial?.facebook,
twitter: data?.Portofolio_MediaSosial?.twitter,
instagram: data?.Portofolio_MediaSosial?.instagram,
tiktok: data?.Portofolio_MediaSosial?.tiktok,
youtube: data?.Portofolio_MediaSosial?.youtube,
};
}
} catch (error) { return NextResponse.json(
console.error(error); { success: true, message: "Berhasil mendapatkan data", data: dataFix },
return NextResponse.json({ success: false, message: "Gagal mendapatkan data, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); { status: 200 }
} );
} catch (error) {
console.error(error);
return NextResponse.json(
{
success: false,
message: "Gagal mendapatkan data, coba lagi nanti (error: 500)",
reason: (error as Error).message,
},
{ status: 500 }
);
}
} }
// DELETE ONE DATA PORTOFOLIO // DELETE ONE DATA PORTOFOLIO
export async function DELETE(request: Request, context: { params: { id: string } }) { export async function DELETE(
try { request: Request,
const { id } = context.params context: { params: { id: string } }
) {
try {
const { id } = context.params;
const data = await prisma.portofolio.findUnique({ const data = await prisma.portofolio.findUnique({
where: { where: {
id: id id: id,
} },
}) include: {
BusinessMaps: {
select: {
pinId: true,
imageId: true,
},
},
},
});
const findLogo = await prisma.images.findFirst({ try {
where: { const id = data?.logoId;
id: String(data?.logoId), const deleteLogo = await fetch(
}, `https://wibu-storage.wibudev.com/api/files/${id}/delete`,
select: { {
id: true, method: "DELETE",
url: true, headers: {
}, Authorization: `Bearer ${process.env.WS_APIKEY}`,
}); },
}
);
if (findLogo) { if (deleteLogo.ok) {
fs.unlinkSync(`./public/portofolio/logo/${findLogo.url}`) backendLogger.info(`Success delete logo`);
const deleteLogo = await prisma.images.delete({
where: {
id: String(findLogo?.id),
},
});
} }
if (data?.BusinessMaps?.pinId != null) {
const pinId = data?.BusinessMaps?.pinId;
const deletePin = await fetch(
`https://wibu-storage.wibudev.com/api/files/${pinId}/delete`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
}
);
if (deletePin.ok) {
backendLogger.info(`Success delete pin`);
}
const deletePortoMedsos = await prisma.portofolio_MediaSosial.delete({ const imageId = data?.BusinessMaps?.imageId;
where: { const deleteImage = await fetch(
portofolioId: id, `https://wibu-storage.wibudev.com/api/files/${imageId}/delete`,
}, {
}); method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
}
);
const deleteMap = await prisma.businessMaps.delete({ if (deleteImage.ok) {
where: { backendLogger.info(`Success delete image`);
portofolioId: id }
} }
}) } catch (error) {
backendLogger.error("Error delete logo", error);
}
const deletePortofolio = await prisma.portofolio.delete({ const deletePortoMedsos = await prisma.portofolio_MediaSosial.delete({
where: { where: {
id: id, portofolioId: id,
}, },
}); });
return NextResponse.json({ success: true, message: "Berhasil menghapus data" }, { status: 200 }); const deleteMap = await prisma.businessMaps.delete({
where: {
portofolioId: id,
},
});
} catch (error) { const deletePortofolio = await prisma.portofolio.delete({
console.error(error); where: {
return NextResponse.json({ success: false, message: "Gagal menghapus data, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); id: id,
} },
} });
return NextResponse.json(
{ success: true, message: "Berhasil menghapus data" },
{ status: 200 }
);
} catch (error) {
console.error(error);
return NextResponse.json(
{
success: false,
message: "Gagal menghapus data, coba lagi nanti (error: 500)",
reason: (error as Error).message,
},
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,33 @@
import { decrypt } from "@/app/auth/_lib/decrypt";
import { prisma } from "@/app/lib";
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET(req: NextRequest) {
const token = req.headers.get('Authorization')?.split(' ')[1];
const decripted = await decrypt({
token: token!,
encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!
})
if (!decripted) {
return NextResponse.json({
success: false,
message: "Unauthorized"
}, { status: 401 })
}
const user = await prisma.user.findUnique({
where: {
id: decripted.id
}
})
return NextResponse.json({
success: true,
message: "Berhasil mendapatkan data",
data: user
})
}

View File

@@ -3,6 +3,8 @@ import _ from "lodash";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET() { export async function GET() {
// const data = await req.text(); // const data = await req.text();
// console.log(data); // console.log(data);

25
src/app/api/user/route.ts Normal file
View File

@@ -0,0 +1,25 @@
import { decrypt } from "@/app/auth/_lib/decrypt";
import _ from "lodash";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function GET() {
const c = cookies().get(process.env.NEXT_PUBLIC_BASE_SESSION_KEY!);
if (!c || !c?.value || _.isEmpty(c?.value) || _.isUndefined(c?.value)) {
return NextResponse.json({ status: 401, message: "Unauthorized" });
}
const token = c.value;
const dataUser = await decrypt({
token: token,
encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!,
});
const id = dataUser?.id
return NextResponse.json({ status: 200, message: "OK", data: id });
}

View File

@@ -5,12 +5,7 @@ import { NextResponse } from "next/server";
export async function GET(req: Request) { export async function GET(req: Request) {
const auth = req.headers.get("Authorization"); const auth = req.headers.get("Authorization");
const token = auth?.split(" ")[1]; const token = auth?.split(" ")[1];
console.log("validasi atas", token);
if (!token) return NextResponse.json({ success: false }, { status: 401 }); if (!token) return NextResponse.json({ success: false }, { status: 401 });
console.log("validasi bawah", token);
return NextResponse.json({ success: true }); return NextResponse.json({ success: true });
} }

View File

@@ -1,20 +1,8 @@
import { ViewKatalogNew } from "@/app_modules/katalog"; import { ViewKatalogNew } from "@/app_modules/katalog";
export default async function Page({ params }: { params: { id: string } }) { export default async function Page() {
// let profileId = params.id;
// const userLoginId = await funGetUserIdByToken();
// const listPorto = await funGetListPortofolio(profileId);
// const dataProfile = await Profile_getOneProfileAndUserById(profileId);
return ( return (
<> <>
{/* <Katalog_MainView
profile={dataProfile as any}
listPorto={listPorto as any}
userLoginId={userLoginId as any}
/> */}
<ViewKatalogNew /> <ViewKatalogNew />
</> </>
); );

View File

@@ -1,16 +1,8 @@
import { ListDetailPortofolioNew } from "@/app_modules/katalog/portofolio"; import { ListDetailPortofolioNew } from "@/app_modules/katalog/portofolio";
export default async function Page({ params }: { params: { id: string } }) { export default async function Page() {
// const profileId = params.id;
// const dataPortofolio = await portofolio_funGetAllDaftarByid({
// profileId,
// page: 1,
// });
return ( return (
<> <>
{/* <Portofolio_ViewListDetail dataPortofolio={dataPortofolio as any} profileId={profileId} /> */}
<ListDetailPortofolioNew /> <ListDetailPortofolioNew />
</> </>
); );

View File

@@ -1,19 +1,16 @@
import { funGetUserIdByToken } from "@/app_modules/_global/fun/get";
import { Portofolio_UiDetailNew } from "@/app_modules/katalog/portofolio"; import { Portofolio_UiDetailNew } from "@/app_modules/katalog/portofolio";
const mapboxToken = process.env.MAPBOX_TOKEN!; const mapboxToken = process.env.MAPBOX_TOKEN!;
export default async function Page({ params }: { params: { id: string } }) { export default async function Page() {
// const portofolioId = params.id; const userLoginId = await funGetUserIdByToken()
// const dataPortofolio = await portofolio_getOneById(portofolioId);
// const userLoginId = await funGetUserIdByToken();
return ( return (
<> <>
{/* <ViewPortofolio <Portofolio_UiDetailNew
dataPorto={dataPortofolio as any}
userLoginId={userLoginId as any}
mapboxToken={mapboxToken} mapboxToken={mapboxToken}
/> */} userLoginId={userLoginId}
<Portofolio_UiDetailNew mapboxToken={mapboxToken} /> />
</> </>
); );
} }

View File

@@ -1,6 +1,5 @@
"use server"; "use server";
import { jwtVerify } from "jose";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { decrypt } from "../../../../app/auth/_lib/decrypt"; import { decrypt } from "../../../../app/auth/_lib/decrypt";
@@ -16,21 +15,3 @@ export async function funGetUserIdByToken() {
return cekUser?.id; return cekUser?.id;
} }
// async function decrypt({
// token,
// encodedKey,
// }: {
// token: string;
// encodedKey: string;
// }): Promise<Record<string, any> | null> {
// try {
// const enc = new TextEncoder().encode(encodedKey);
// const { payload } = await jwtVerify(token, enc, {
// algorithms: ["HS256"],
// });
// return (payload.user as Record<string, any>) || null;
// } catch (error) {
// console.error("Gagal verifikasi session", error);
// return null;
// }
// }

View File

@@ -0,0 +1,16 @@
export const apiDeleteImageById = async ({
fileId,
dirId,
}: {
fileId: string;
dirId?: string;
}) => {
const response = await fetch(`/api/image/delete`, {
method: "DELETE",
body: JSON.stringify({ fileId, dirId }),
});
console.log("delete api =>", await response.json());
return await response.json().catch(() => null);
};

View File

@@ -1,9 +1,39 @@
export const apiGetUserId = async () => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const response = await fetch(`/api/user`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
console.log("Ini di pemanggilan API",await response.json());
if (!response.ok) return null;
const data: Record<string, any> = await response.json();
return data;
};
export const apiGetCookiesUser = async () => { export const apiGetCookiesUser = async () => {
const response = await fetch(`/api/user/get`); const response = await fetch(`/api/user/get`);
return await response.json().catch(() => null); return await response.json().catch(() => null);
}; };
export const apiGetACtivationUser = async () => { export const apiGetACtivationUser = async () => {
const response = await fetch(`/api/user/activation`); const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const response = await fetch(`/api/user/activation`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
return await response.json().catch(() => null); return await response.json().catch(() => null);
}; };

View File

@@ -13,7 +13,7 @@ import {
Paper, Paper,
SimpleGrid, SimpleGrid,
Stack, Stack,
Text Text,
} from "@mantine/core"; } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks"; import { useShallowEffect } from "@mantine/hooks";
import { IconUserSearch } from "@tabler/icons-react"; import { IconUserSearch } from "@tabler/icons-react";
@@ -22,6 +22,7 @@ import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { apiGetDataHome } from "../fun/get/api_home"; import { apiGetDataHome } from "../fun/get/api_home";
import { listMenuHomeBody, menuHomeJob } from "./list_menu_home"; import { listMenuHomeBody, menuHomeJob } from "./list_menu_home";
import { clientLogger } from "@/util/clientLogger";
export default function BodyHome() { export default function BodyHome() {
const router = useRouter(); const router = useRouter();
@@ -37,24 +38,31 @@ export default function BodyHome() {
async function cekUserLogin() { async function cekUserLogin() {
try { try {
const response = await apiGetDataHome("?cat=cek_profile"); const response = await apiGetDataHome({
if (response.success) { path: "?cat=cek_profile",
});
if (response) {
setDataUser(response.data); setDataUser(response.data);
} }
} catch (error) { } catch (error) {
console.error(error); clientLogger.error("Error get data user", error);
} }
} }
async function getHomeJob() { async function getHomeJob() {
try { try {
setLoadingJob(true); setLoadingJob(true);
const response = await apiGetDataHome("?cat=job");
if (response.success) { const response = await apiGetDataHome({
path: "?cat=job",
});
if (response) {
setDataJob(response.data); setDataJob(response.data);
} }
} catch (error) { } catch (error) {
console.error(error); clientLogger.error("Error get data job", error);
} finally { } finally {
setLoadingJob(false); setLoadingJob(false);
} }
@@ -197,12 +205,22 @@ export default function BodyHome() {
<Box key={i} mb={"md"}> <Box key={i} mb={"md"}>
<Grid> <Grid>
<Grid.Col span={6}> <Grid.Col span={6}>
<CustomSkeleton height={10} mt={0} radius="xl" width={"75%"} /> <CustomSkeleton
<CustomSkeleton height={10} mt={10} radius="xl" /> height={10}
</Grid.Col > mt={0}
radius="xl"
width={"75%"}
/>
<CustomSkeleton height={10} mt={10} radius="xl" />
</Grid.Col>
<Grid.Col span={6}> <Grid.Col span={6}>
<CustomSkeleton height={10} mt={0} radius="xl" width={"75%"} /> <CustomSkeleton
<CustomSkeleton height={10} mt={10} radius="xl" /> height={10}
mt={0}
radius="xl"
width={"75%"}
/>
<CustomSkeleton height={10} mt={10} radius="xl" />
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Box> </Box>

View File

@@ -17,6 +17,7 @@ import { apiGetDataHome } from "../fun/get/api_home";
import { Home_ComponentAvatarProfile } from "./comp_avatar_profile"; import { Home_ComponentAvatarProfile } from "./comp_avatar_profile";
import { listMenuHomeFooter } from "./list_menu_home"; import { listMenuHomeFooter } from "./list_menu_home";
import { MainColor } from "@/app_modules/_global/color"; import { MainColor } from "@/app_modules/_global/color";
import { clientLogger } from "@/util/clientLogger";
export default function FooterHome() { export default function FooterHome() {
const router = useRouter(); const router = useRouter();
@@ -28,12 +29,14 @@ export default function FooterHome() {
async function cekUserLogin() { async function cekUserLogin() {
try { try {
const response = await apiGetDataHome("?cat=cek_profile"); const response = await apiGetDataHome({
if (response.success) { path: "?cat=cek_profile",
});
if (response) {
setDataUser(response.data); setDataUser(response.data);
} }
} catch (error) { } catch (error) {
console.error(error); clientLogger.error("Error get data profile",error);
} }
} }
@@ -104,7 +107,10 @@ export default function FooterHome() {
} }
}} }}
> >
<ActionIcon variant={"transparent"}> <ActionIcon
variant={"transparent"}>
{dataUser.profile === undefined || dataUser?.profile === null ? ( {dataUser.profile === undefined || dataUser?.profile === null ? (
<IconUserCircle color={MainColor.white} /> <IconUserCircle color={MainColor.white} />
) : ( ) : (

View File

@@ -1,4 +1,17 @@
export const apiGetDataHome = async (path?: string) => { export const apiGetDataHome = async ({ path }: { path?: string }) => {
const response = await fetch(`/api/new/home${(path) ? path : ''}`) const { token } = await fetch("/api/get-cookie").then((res) => res.json());
return await response.json().catch(() => null) if (!token) return await token.json().catch(() => null);
}
const response = await fetch(`/api/new/home${path ? path : ""}`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) return null;
const data: Record<string, any> = await response.json();
return data;
};

View File

@@ -17,6 +17,7 @@ import { gs_notifikasi_kategori_app } from "../notifikasi/lib";
import BodyHome from "./component/body_home"; import BodyHome from "./component/body_home";
import FooterHome from "./component/footer_home"; import FooterHome from "./component/footer_home";
import { apiGetDataHome } from "./fun/get/api_home"; import { apiGetDataHome } from "./fun/get/api_home";
import { clientLogger } from "@/util/clientLogger";
export default function HomeViewNew() { export default function HomeViewNew() {
const [countNtf, setCountNtf] = useAtom(gs_count_ntf); const [countNtf, setCountNtf] = useAtom(gs_count_ntf);
@@ -48,12 +49,14 @@ export default function HomeViewNew() {
async function cekUserLogin() { async function cekUserLogin() {
try { try {
const response = await apiGetDataHome("?cat=cek_profile"); const response = await apiGetDataHome({
if (response.success) { path: "?cat=cek_profile",
});
if (response) {
setDataUser(response.data); setDataUser(response.data);
} }
} catch (error) { } catch (error) {
console.error(error); clientLogger.error("Error get data home", error);
} }
} }

View File

@@ -22,7 +22,7 @@ export default function LayoutKatalogNew({ children }: { children: any }) {
setLoading(true) setLoading(true)
const response = await apiGetUserProfile(`?profile=${param.id}`) const response = await apiGetUserProfile(`?profile=${param.id}`)
const response2 = await funGetUserIdByToken() const response2 = await funGetUserIdByToken()
if (response.success) { if (response) {
setAuthorId(response.data.id) setAuthorId(response.data.id)
setUserRoleId(response.data.masterUserRoleId) setUserRoleId(response.data.masterUserRoleId)
setUserLoginId(response2) setUserLoginId(response2)

View File

@@ -1,98 +1,103 @@
import { funGetUserIdByToken } from "@/app_modules/_global/fun/get"; import { funGetUserIdByToken } from "@/app_modules/_global/fun/get";
import { ComponentGlobal_NotifikasiBerhasil, ComponentGlobal_NotifikasiGagal } from "@/app_modules/_global/notif_global"; import {
ComponentGlobal_NotifikasiBerhasil,
ComponentGlobal_NotifikasiGagal,
} from "@/app_modules/_global/notif_global";
import { UIGlobal_Modal } from "@/app_modules/_global/ui"; import { UIGlobal_Modal } from "@/app_modules/_global/ui";
import { Button } from "@mantine/core"; import { Button } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks"; import { useShallowEffect } from "@mantine/hooks";
import { IconTrash } from "@tabler/icons-react"; import { IconTrash } from "@tabler/icons-react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { apiDeletePortofolio, apiGetOnePortofolioById } from "../lib/api_portofolio"; import {
apiDeletePortofolio,
apiGetOnePortofolioById,
} from "../lib/api_portofolio";
import { IDetailPortofolioBisnis } from "../lib/type_portofolio"; import { IDetailPortofolioBisnis } from "../lib/type_portofolio";
import { MainColor } from "@/app_modules/_global/color"; import { MainColor } from "@/app_modules/_global/color";
import { apiGetUserId } from "@/app_modules/_global/lib/api_user";
import { clientLogger } from "@/util/clientLogger";
export default function ComponentPortofolio_ButtonDeleteNew() { export default function ComponentPortofolio_ButtonDeleteNew({
const param = useParams<{ id: string }>() userLoginId,
const [openModal, setModal] = useState(false) }: {
const [loadingDel, setLoadingDel] = useState(false) userLoginId: string;
const [userLoginId, setUserLoginId] = useState("") }) {
const [dataPorto, setDataPorto] = useState<IDetailPortofolioBisnis>() const param = useParams<{ id: string }>();
const router = useRouter() const [openModal, setModal] = useState(false);
const [loadingDel, setLoadingDel] = useState(false);
const [dataPorto, setDataPorto] = useState<IDetailPortofolioBisnis>();
const router = useRouter();
async function onDelete() {
async function onDelete() { try {
try { setLoadingDel(true);
setLoadingDel(true) const response = await apiDeletePortofolio(param.id);
const response = await apiDeletePortofolio(param.id) if (response) {
if (response.success) { ComponentGlobal_NotifikasiBerhasil(response.message);
ComponentGlobal_NotifikasiBerhasil(response.message) router.back();
router.back() } else {
} else { ComponentGlobal_NotifikasiGagal(response?.message);
ComponentGlobal_NotifikasiGagal(response.message);
}
} catch (error) {
console.error(error)
ComponentGlobal_NotifikasiGagal("Gagal menghapus portofolio");
} finally {
setLoadingDel(false)
} }
} } catch (error) {
clientLogger.error("Error delete portofolio", error);
ComponentGlobal_NotifikasiGagal("Gagal menghapus portofolio");
} finally {
setLoadingDel(false);
}
}
async function funGetPortofolio() { async function funGetPortofolio() {
try { try {
const response = await apiGetOnePortofolioById(param.id, "bisnis") const response = await apiGetOnePortofolioById(param.id, "bisnis");
const response2 = await funGetUserIdByToken() if (response) {
if (response.success) { setDataPorto(response.data);
setDataPorto(response.data)
setUserLoginId(response2)
}
} catch (error) {
console.error(error);
} }
} } catch (error) {
console.error("Error get data button delete:", error);
}
}
useShallowEffect(() => { useShallowEffect(() => {
funGetPortofolio() funGetPortofolio();
}, []); }, []);
return (
<>
{userLoginId === dataPorto?.authorId && (
<Button
radius={"xl"}
bg={MainColor.red}
color="red"
onClick={() => {
setModal(true);
}}
>
<IconTrash />
</Button>
)}
return ( <UIGlobal_Modal
<> title={"Anda yakin menghapus portofolio ini ?"}
{userLoginId === dataPorto?.authorId ? ( opened={openModal}
<Button close={() => setModal(false)}
radius={"xl"} buttonKiri={
bg={MainColor.red} <Button radius={"xl"} onClick={() => setModal(false)}>
color="red" Batal
onClick={() => { </Button>
setModal(true) }
}} buttonKanan={
> <Button
<IconTrash /> radius={"xl"}
</Button> color="red"
) : ( loaderPosition="center"
"" loading={loadingDel}
)} onClick={() => onDelete()}
>
<UIGlobal_Modal Hapus
title={"Anda yakin menghapus portofolio ini ?"} </Button>
opened={openModal} }
close={() => setModal(false)} />
buttonKiri={ </>
<Button radius={"xl"} onClick={() => setModal(false)}> );
Batal }
</Button>
}
buttonKanan={
<Button
radius={"xl"}
color="red"
loaderPosition="center"
loading={loadingDel}
onClick={() => onDelete()}
>
Hapus
</Button>
}
/>
</>
)
}

View File

@@ -89,9 +89,9 @@ export default function ComponentPortofolio_ButtonMoreNew() {
const response = await apiGetOnePortofolioById(param.id, "bisnis") const response = await apiGetOnePortofolioById(param.id, "bisnis")
const response3 = await apiGetOnePortofolioById(param.id, "lokasi") const response3 = await apiGetOnePortofolioById(param.id, "lokasi")
const response2 = await funGetUserIdByToken() const response2 = await funGetUserIdByToken()
if (response.success) { if (response) {
setAuthorId(response.data.authorId) setAuthorId(response.data.authorId)
setMapId((response3.data?.mapId !== null && response3.data?.mapId !== undefined) ? true : false) setMapId((response3 !== null && response3.data?.mapId !== undefined) ? true : false)
setUserLoginId(response2) setUserLoginId(response2)
} }
} catch (error) { } catch (error) {

View File

@@ -0,0 +1,8 @@
export {
}
function Portofolio_SkeletonListPorto() {
}

View File

@@ -170,30 +170,6 @@ export default function CreatePortofolio({
/> />
</Stack> </Stack>
{/* <TextInput
styles={{
label: {
color: MainColor.white,
},
// input: {
// backgroundColor: MainColor.white,
// },
// required: {
// color: MainColor.red,
// },
}}
withAsterisk
label="Nomor Telepon "
placeholder="Nomor telepon "
type="number"
onChange={(val) => {
setDataPortofolio({
...dataPortofolio,
tlpn: val.target.value,
});
}}
/> */}
<Stack spacing={5}> <Stack spacing={5}>
<Textarea <Textarea
styles={{ styles={{

View File

@@ -1,19 +1,51 @@
export const apiGetPortofolioByProfile = async (path?: string) => { export const apiGetPortofolioByProfile = async (path?: string) => {
const response = await fetch(`/api/new/portofolio${(path) ? path : ''}`) const { token } = await fetch("/api/get-cookie").then((res) => res.json());
return await response.json().catch(() => null) if (!token) return null;
}
export const apiGetOnePortofolioById = async (path: string, cat:string) => { const response = await fetch(`/api/new/portofolio${path ? path : ""}`, {
const response = await fetch(`/api/new/portofolio/${path}?cat=${cat}`); headers: {
return await response.json().catch(() => null); "Content-Type": "application/json",
} Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) return null;
const data: Record<string, any> = await response.json();
return data;
};
export const apiGetOnePortofolioById = async (path: string, cat: string) => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return null;
const response = await fetch(`/api/new/portofolio/${path}?cat=${cat}`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) return null;
const data: Record<string, any> = await response.json();
return data;
};
export const apiDeletePortofolio = async (path: string) => { export const apiDeletePortofolio = async (path: string) => {
const response = await fetch(`/api/new/portofolio/${path}`, { const { token } = await fetch("/api/get-cookie").then((res) => res.json());
method: "DELETE", if (!token) return null;
headers: {
"Content-Type": "application/json", const response = await fetch(`/api/new/portofolio/${path}`, {
}, method: "DELETE",
}); headers: {
return await response.json().catch(() => null); "Content-Type": "application/json",
} Authorization: `Bearer ${token}`,
},
});
return await response.json().catch(() => null);
};

View File

@@ -38,7 +38,7 @@ export default function Portofolio_UiDetailDataNew() {
try { try {
setLoading(true); setLoading(true);
const response = await apiGetOnePortofolioById(param.id, "bisnis"); const response = await apiGetOnePortofolioById(param.id, "bisnis");
if (response.success) { if (response) {
setDataPorto(response.data); setDataPorto(response.data);
} }
} catch (error) { } catch (error) {

View File

@@ -23,7 +23,7 @@ export default function Portofolio_UiMapNew({ mapboxToken }: { mapboxToken: stri
try { try {
setLoading(true) setLoading(true)
const response = await apiGetOnePortofolioById(param.id, "lokasi"); const response = await apiGetOnePortofolioById(param.id, "lokasi");
if (response.success) { if (response) {
setDataPorto(response.data); setDataPorto(response.data);
} }
} catch (error) { } catch (error) {

View File

@@ -17,7 +17,7 @@ export default function Portofolio_UiSosialMediaNew() {
try { try {
setLoading(true) setLoading(true)
const response = await apiGetOnePortofolioById(param.id, "sosmed"); const response = await apiGetOnePortofolioById(param.id, "sosmed");
if (response.success) { if (response) {
setDataPorto(response.data); setDataPorto(response.data);
} }
} catch (error) { } catch (error) {

View File

@@ -5,15 +5,21 @@ import Portofolio_UiMapNew from "./ui_detail_map_new";
import Portofolio_UiSosialMediaNew from "./ui_detail_media_new"; import Portofolio_UiSosialMediaNew from "./ui_detail_media_new";
import ComponentPortofolio_ButtonDeleteNew from "../component/button_delete_new"; import ComponentPortofolio_ButtonDeleteNew from "../component/button_delete_new";
export default function Portofolio_UiDetailNew({ mapboxToken }: { mapboxToken: string }) { export default function Portofolio_UiDetailNew({
return ( mapboxToken,
<> userLoginId,
<Stack mb={"lg"}> }: {
<Portofolio_UiDetailDataNew /> mapboxToken: string;
<Portofolio_UiMapNew mapboxToken={mapboxToken} /> userLoginId: string
<Portofolio_UiSosialMediaNew /> }) {
<ComponentPortofolio_ButtonDeleteNew/> return (
</Stack> <>
</> <Stack mb={"lg"}>
) <Portofolio_UiDetailDataNew />
<Portofolio_UiMapNew mapboxToken={mapboxToken} />
<Portofolio_UiSosialMediaNew />
<ComponentPortofolio_ButtonDeleteNew userLoginId={userLoginId} />
</Stack>
</>
);
} }

View File

@@ -1,59 +1,84 @@
import ComponentGlobal_Loader from "@/app_modules/_global/component/loader"; import ComponentGlobal_Loader from "@/app_modules/_global/component/loader";
import { Box, Center } from "@mantine/core"; import { Box, Center, Group, Skeleton, Stack } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { ScrollOnly } from "next-scroll-loader"; import { ScrollOnly } from "next-scroll-loader";
import { useParams } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { ComponentPortofolio_DaftarBoxView } from "../component/card_view_daftar"; import { ComponentPortofolio_DaftarBoxView } from "../component/card_view_daftar";
import { portofolio_funGetAllDaftarByid } from "../fun/get/get_all_portofolio";
import { MODEL_PORTOFOLIO } from "../model/interface";
import { useParams } from "next/navigation";
import { useShallowEffect } from "@mantine/hooks";
import { apiGetPortofolioByProfile } from "../lib/api_portofolio"; import { apiGetPortofolioByProfile } from "../lib/api_portofolio";
import { MODEL_PORTOFOLIO } from "../model/interface";
import _ from "lodash";
import { ComponentGlobal_CardStyles } from "@/app_modules/_global/component";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
export default function Portofolio_UiListDetailNew() { export default function Portofolio_UiListDetailNew() {
const param = useParams<{ id: string }>() const param = useParams<{ id: string }>();
const profileId = param.id const profileId = param.id;
const [data, setData] = useState<MODEL_PORTOFOLIO[]>([]) const [data, setData] = useState<MODEL_PORTOFOLIO[] | null>(null);
const [activePage, setActivePage] = useState(1) const [activePage, setActivePage] = useState(1);
async function getPortofolio() { async function getPortofolio() {
try { try {
const response = await apiGetPortofolioByProfile(`?profile=${param.id}&cat=portofolio&page=1`) const response = await apiGetPortofolioByProfile(
if (response.success) { `?profile=${param.id}&cat=portofolio&page=1`
setData(response.data); );
} if (response) {
} catch (error) { setData(response.data);
console.error(error);
} }
} } catch (error) {
console.error(error);
}
}
useShallowEffect(() => {
getPortofolio();
}, []);
useShallowEffect(() => { if (_.isNull(data))
getPortofolio() return (
}, []); <>
<Stack>
<CustomSkeleton height={80} radius={"md"} width={"100%"} />
<CustomSkeleton height={80} radius={"md"} width={"100%"} />
</Stack>
</>
);
return <> return (
<>
<Box py={5}> <Box py={5}>
<ScrollOnly <ScrollOnly
height="90vh" height="90vh"
renderLoading={() => ( renderLoading={() => (
<Center mt={"lg"}> <Center mt={"lg"}>
<ComponentGlobal_Loader /> <ComponentGlobal_Loader />
</Center> </Center>
)} )}
data={data} data={data}
setData={setData} setData={setData as any}
moreData={async () => { moreData={async () => {
const loadData = await portofolio_funGetAllDaftarByid({ // const loadData = await portofolio_funGetAllDaftarByid({
profileId, // profileId,
page: activePage + 1, // page: activePage + 1,
}); // });
setActivePage((val) => val + 1);
return loadData; try {
}} const response = await apiGetPortofolioByProfile(
> `?profile=${param.id}&cat=portofolio&page=${activePage + 1}`
{(item) => <ComponentPortofolio_DaftarBoxView data={item} />} );
</ScrollOnly> if (response) {
setActivePage((val) => val + 1);
return response.data;
}
} catch (error) {
console.error(error);
}
}}
>
{(item) => <ComponentPortofolio_DaftarBoxView data={item} />}
</ScrollOnly>
</Box> </Box>
</>; </>
} );
}

View File

@@ -19,7 +19,7 @@ export default function ListPortofolioProfileNew() {
try { try {
setLoading(true) setLoading(true)
const response = await apiGetPortofolioByProfile(`?profile=${param.id}&cat=profile`) const response = await apiGetPortofolioByProfile(`?profile=${param.id}&cat=profile`)
if (response.success) { if (response) {
setDataPortofolio(response.data); setDataPortofolio(response.data);
} }
} catch (error) { } catch (error) {

View File

@@ -65,7 +65,7 @@ export default function ProfileDetail() {
try { try {
setLoading(true); setLoading(true);
const response = await apiGetUserProfile(`?profile=${param.id}`); const response = await apiGetUserProfile(`?profile=${param.id}`);
if (response.success) { if (response) {
setDataProfile(response.data); setDataProfile(response.data);
} }
} catch (error) { } catch (error) {

View File

@@ -1,9 +1,20 @@
export const apiGetAllMap = async (path?: string) => { export const apiGetAllMap = async (path?: string) => {
const response = await fetch(`/api/new/map${(path) ? path : ''}`) const { token } = await fetch("/api/get-cookie").then((res) => res.json());
return await response.json().catch(() => null) if (!token) return await token.json().catch(() => null);
}
const response = await fetch(`/api/new/map${path ? path : ""}`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
return await response.json().catch(() => null);
};
export const apiGetOneMapById = async (path: string) => { export const apiGetOneMapById = async (path: string) => {
const response = await fetch(`/api/new/map/${path}`);
return await response.json().catch(() => null); const response = await fetch(`/api/new/map/${path}`);
} return await response.json().catch(() => null);
};

View File

@@ -26,7 +26,7 @@ export function UiMap_MapBoxViewNew({ mapboxToken, }: { mapboxToken: string }) {
try { try {
setLoading(true) setLoading(true)
const response = await apiGetAllMap() const response = await apiGetAllMap()
if (response.success) { if (response) {
setData(response.data) setData(response.data)
} }
} catch (error) { } catch (error) {

View File

@@ -1,4 +1,19 @@
export const apiGetUserProfile = async (path?: string) => { export const apiGetUserProfile = async (path?: string) => {
const response = await fetch(`/api/new/user${(path) ? path : ''}`) const { token } = await fetch("/api/get-cookie").then((res) => res.json());
return await response.json().catch(() => null) if(!token) return null
const response = await fetch(`/api/new/user${(path) ? path : ''}`, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Origin': '*',
'Authorization': `Bearer ${token}`,
}
})
// console.log(await response.json())
if (!response.ok) return null
const data: Record<string, any> = await response.json()
return data
} }

View File

@@ -2,22 +2,14 @@
import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global"; import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global";
import { UIGlobal_LayoutDefault } from "@/app_modules/_global/ui"; import { UIGlobal_LayoutDefault } from "@/app_modules/_global/ui";
import { import { clientLogger } from "@/util/clientLogger";
Button, import { Skeleton, Stack, Text } from "@mantine/core";
Center,
Group,
Skeleton,
Stack,
Text,
Title,
} from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks"; import { useShallowEffect } from "@mantine/hooks";
import { redirect, useRouter } from "next/navigation"; import _ from "lodash";
import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { ComponentGlobal_CardStyles } from "../_global/component"; import { ComponentGlobal_CardStyles } from "../_global/component";
import { apiGetACtivationUser } from "../_global/lib/api_user"; import { apiGetACtivationUser } from "../_global/lib/api_user";
import { clientLogger } from "@/util/clientLogger";
import _ from "lodash";
export default function WaitingRoom_View({ export default function WaitingRoom_View({
userLoginId, userLoginId,
@@ -49,7 +41,6 @@ export default function WaitingRoom_View({
try { try {
const respone = await apiGetACtivationUser(); const respone = await apiGetACtivationUser();
if (respone) { if (respone) {
console.log(respone.data);
setData(respone.data); setData(respone.data);
} }
} catch (error) { } catch (error) {

View File

@@ -18,6 +18,7 @@ const middlewareConfig: MiddlewareConfig = {
loginPath: "/login", loginPath: "/login",
userPath: "/dev/home", userPath: "/dev/home",
publicRoutes: [ publicRoutes: [
// API
"/", "/",
"/api/voting/*", "/api/voting/*",
"/api/collaboration/*", "/api/collaboration/*",
@@ -25,26 +26,36 @@ const middlewareConfig: MiddlewareConfig = {
"/api/logs/*", "/api/logs/*",
"/api/image/*", "/api/image/*",
"/api/job/*", "/api/job/*",
"/api/validation",
"/api/auth/*", "/api/auth/*",
"/api/origin-url", "/api/origin-url",
"/api/user", // "/api/user",
"/api/event/*", "/api/event/*",
// Akses awal
"/api/get-cookie",
"/api/user/activation",
"/api/user-validate",
// PAGE
"/login", "/login",
"/register", "/register",
"/validasi", "/validasi",
"/splash", "/splash",
"/job-vacancy",
"/preview-image",
"/auth/login", "/auth/login",
"/auth/api/login", "/auth/api/login",
"/waiting-room",
// ASSETS
"/aset/global/main_background.png", "/aset/global/main_background.png",
"/aset/logo/logo-hipmi.png", "/aset/logo/logo-hipmi.png",
"/api/new/*",
], ],
encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!, encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!,
sessionKey: process.env.NEXT_PUBLIC_BASE_SESSION_KEY!, sessionKey: process.env.NEXT_PUBLIC_BASE_SESSION_KEY!,
validationApiRoute: "/api/validation", validationApiRoute: "/api/validation",
log: false, log: false,
}; };
export const middleware = async (req: NextRequest) => { export const middleware = async (req: NextRequest) => {
const { const {
apiPath, apiPath,
@@ -64,14 +75,20 @@ export const middleware = async (req: NextRequest) => {
} }
// Skip authentication for public routes // Skip authentication for public routes
const isPublicRoute = [...publicRoutes, loginPath, validationApiRoute].some( const isPublicRoute = [...publicRoutes, loginPath].some((route) => {
(route) => { const pattern = route.replace(/\*/g, ".*");
const pattern = route.replace(/\*/g, ".*"); return new RegExp(`^${pattern}$`).test(pathname);
return new RegExp(`^${pattern}$`).test(pathname); });
}
);
if (isPublicRoute) { // Always protect validation endpoint
if (pathname === validationApiRoute) {
const reqToken = req.headers.get("Authorization")?.split(" ")[1];
if (!reqToken) {
return setCorsHeaders(unauthorizedResponse());
}
}
if (isPublicRoute && pathname !== loginPath) {
return setCorsHeaders(NextResponse.next()); return setCorsHeaders(NextResponse.next());
} }
@@ -82,21 +99,43 @@ export const middleware = async (req: NextRequest) => {
// Token verification // Token verification
const user = await verifyToken({ token, encodedKey }); const user = await verifyToken({ token, encodedKey });
if (!user) { // Handle login page access
if (pathname.startsWith(apiPath)) { if (pathname === loginPath) {
return setCorsHeaders(unauthorizedResponse()); if (user) {
return setCorsHeaders(NextResponse.redirect(new URL(userPath, req.url)));
} }
return setCorsHeaders(NextResponse.next());
}
// Handle protected routes
if (!user) {
return setCorsHeaders(NextResponse.redirect(new URL(loginPath, req.url))); return setCorsHeaders(NextResponse.redirect(new URL(loginPath, req.url)));
} }
// Redirect authenticated user away from login page if (pathname.startsWith("/dev")) {
if (user && pathname === loginPath) { const userValidate = await fetch(new URL("/api/user-validate", req.url), {
return setCorsHeaders(NextResponse.redirect(new URL(userPath, req.url))); headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const userValidateJson = await userValidate.json();
if (!userValidateJson.data.active) {
return setCorsHeaders(
NextResponse.redirect(new URL("/waiting-room", req.url))
);
}
} }
if (req.nextUrl.pathname.startsWith(apiPath)) { // Handle authenticated API requests
if (pathname.startsWith(apiPath)) {
const reqToken = req.headers.get("Authorization")?.split(" ")[1]; const reqToken = req.headers.get("Authorization")?.split(" ")[1];
if (!reqToken) {
return setCorsHeaders(unauthorizedResponse());
}
// Validate user access with external API // Validate user access with external API
const validationResponse = await fetch( const validationResponse = await fetch(
new URL(validationApiRoute, req.url), new URL(validationApiRoute, req.url),
@@ -111,6 +150,8 @@ export const middleware = async (req: NextRequest) => {
if (!validationResponse.ok) { if (!validationResponse.ok) {
return setCorsHeaders(unauthorizedResponse()); return setCorsHeaders(unauthorizedResponse());
} }
const dataJson = await validationResponse.json();
} }
// Proceed with the request // Proceed with the request

471
xserver/index.html Normal file
View File

@@ -0,0 +1,471 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HIPMI Feature Checklist</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f4f4f4;
}
h1 {
color: #333;
text-align: center;
border-bottom: 2px solid #333;
padding-bottom: 10px;
}
h3 {
color: #444;
margin-top: 20px;
background-color: #e0e0e0;
padding: 10px;
border-radius: 5px;
}
ul {
list-style-type: none;
padding-left: 0;
}
li {
background-color: #fff;
margin: 5px 0;
padding: 10px;
border-radius: 5px;
display: flex;
align-items: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s ease;
}
input[type="checkbox"] {
margin-right: 10px;
transform: scale(1.2);
}
li:hover {
background-color: #f0f0f0;
}
li.checked {
text-decoration: line-through;
color: #888;
background-color: #f0f0f0;
}
#progress-bar {
width: 100%;
background-color: #e0e0e0;
padding: 10px;
margin-top: 20px;
border-radius: 5px;
}
#progress {
width: 0%;
height: 20px;
background-color: #4caf50;
border-radius: 5px;
transition: width 0.5s ease;
}
#reset-btn {
display: block;
width: 100%;
padding: 10px;
background-color: #f44336;
color: white;
border: none;
border-radius: 5px;
margin-top: 20px;
cursor: pointer;
}
#reset-btn:hover {
background-color: #d32f2f;
}
</style>
</head>
<body>
<h1>HIPMI Feature Checklist</h1>
<div id="progress-bar">
<div id="progress"></div>
</div>
<div id="checklist">
<h3>Authentication</h3>
<ul>
<li>
<input type="checkbox" id="login" /> <label for="login">Login</label>
</li>
<li>
<input type="checkbox" id="validasi" />
<label for="validasi">Validasi</label>
</li>
<li>
<input type="checkbox" id="register" />
<label for="register">Register</label>
</li>
</ul>
<h3>Profile</h3>
<ul>
<li>
<input type="checkbox" id="buat-profile" />
<label for="buat-profile">Buat profile baru</label>
</li>
<li>
<input type="checkbox" id="edit-data" />
<label for="edit-data">Edit Data</label>
</li>
<li>
<input type="checkbox" id="update-foto-profile" />
<label for="update-foto-profile">Update foto profile</label>
</li>
<li>
<input type="checkbox" id="update-foto-background" />
<label for="update-foto-background">Update foto background</label>
</li>
</ul>
<h3>Portofolio</h3>
<ul>
<li>
<input type="checkbox" id="buat-portofolio" />
<label for="buat-portofolio">Buat portofolio baru</label>
</li>
<li>
<input type="checkbox" id="edit-detail-portofolio" />
<label for="edit-detail-portofolio">Edit detail portofolio</label>
</li>
<li>
<input type="checkbox" id="edit-logo-map" />
<label for="edit-logo-map">Edit logo map</label>
</li>
<li>
<input type="checkbox" id="edit-sosial-media" />
<label for="edit-sosial-media">Edit data sosial media</label>
</li>
<li>
<input type="checkbox" id="edit-data-map" />
<label for="edit-data-map">Edit data map</label>
</li>
<li>
<input type="checkbox" id="custom-pin-map" />
<label for="custom-pin-map">Custom pin map</label>
</li>
</ul>
<h3>User Search</h3>
<ul>
<li>
<input type="checkbox" id="cari-pengguna" />
<label for="cari-pengguna"
>Cari pengguna lain & masuk ke katalog nya</label
>
</li>
</ul>
<h3>Business Map</h3>
<ul>
<li>
<input type="checkbox" id="buka-pin-map" />
<label for="buka-pin-map">Buka pin map user lain</label>
</li>
<li>
<input type="checkbox" id="lihat-detail-map" />
<label for="lihat-detail-map">Lihat detailnya</label>
</li>
<li>
<input type="checkbox" id="buka-google-maps" />
<label for="buka-google-maps">Buka map (Google Maps)</label>
</li>
</ul>
<h3>Event</h3>
<ul>
<li>
<input type="checkbox" id="buat-event-bebas" />
<label for="buat-event-bebas">Buat event baru (tanggal bebas)</label>
</li>
<li>
<input type="checkbox" id="batalkan-review" />
<label for="batalkan-review">Batalkan review</label>
</li>
<li>
<input type="checkbox" id="edit-event" />
<label for="edit-event">Edit event</label>
</li>
<li>
<input type="checkbox" id="ajukan-event" />
<label for="ajukan-event">Ajukan event</label>
</li>
<li>
<input type="checkbox" id="tunggu-acc-admin-event" />
<label for="tunggu-acc-admin-event">Tunggu acc admin</label>
</li>
<li>
<input type="checkbox" id="reload-beranda" />
<label for="reload-beranda"
>Reload data beranda (dengan button reload)</label
>
</li>
<li>
<input type="checkbox" id="join-detail-event" />
<label for="join-detail-event">Join melalui detail event</label>
</li>
<li>
<input type="checkbox" id="scan-qr-join" />
<label for="scan-qr-join"
>Scan QrCode dan join melalui halaman konfirmasi</label
>
</li>
<li>
<input type="checkbox" id="buat-event-hari-ini" />
<label for="buat-event-hari-ini"
>Buat event baru (tanggal dan jam hari ini)</label
>
</li>
<li>
<input type="checkbox" id="tunggu-acc-admin-event-2" />
<label for="tunggu-acc-admin-event-2">Tunggu acc admin</label>
</li>
<li>
<input type="checkbox" id="scan-qr-konfirmasi" />
<label for="scan-qr-konfirmasi"
>Scan QrCode dan join melalui halaman konfirmasi</label
>
</li>
<li>
<input type="checkbox" id="cek-kontribusi-user" />
<label for="cek-kontribusi-user">Cek kontribusi user</label>
</li>
<li>
<input type="checkbox" id="cek-riwayat-semua" />
<label for="cek-riwayat-semua">Cek Riwayat Semua</label>
</li>
<li>
<input type="checkbox" id="cek-riwayat-saya" />
<label for="cek-riwayat-saya">Cek Riwayat Saya</label>
</li>
</ul>
<h3>Collaboration</h3>
<ul>
<li>
<input type="checkbox" id="buat-kolaborasi" />
<label for="buat-kolaborasi">Buat kolaborasi baru</label>
</li>
<li>
<input type="checkbox" id="user-join-kolaborasi" />
<label for="user-join-kolaborasi">User lain join</label>
</li>
<li>
<input type="checkbox" id="pilih-user-kolaborasi" />
<label for="pilih-user-kolaborasi"
>User pemilik proyek kolaborasi memilih user yang ada di list</label
>
</li>
<li>
<input type="checkbox" id="grup-proyek-kolaborasi" />
<label for="grup-proyek-kolaborasi"
>Grup proyek kolabari terbentuk dan memulai chat</label
>
</li>
</ul>
<h3>Voting</h3>
<ul>
<li>
<input type="checkbox" id="buat-voting" />
<label for="buat-voting">Buat voting baru</label>
</li>
<li>
<input type="checkbox" id="tunggu-acc-voting" />
<label for="tunggu-acc-voting">Tunggu acc admin</label>
</li>
<li>
<input type="checkbox" id="user-voting" />
<label for="user-voting">User lain melakukan voting</label>
</li>
<li>
<input type="checkbox" id="cek-kontribusi-voting" />
<label for="cek-kontribusi-voting"
>Setelah melakukan voting cek kontribusi nya di halaman
kontribusi</label
>
</li>
</ul>
<h3>Investasi</h3>
<ul>
<li>
<input type="checkbox" id="buat-investasi" />
<label for="buat-investasi">Buat invetasi baru</label>
</li>
<li>
<input type="checkbox" id="tunggu-acc-investasi" />
<label for="tunggu-acc-investasi">Tunggu acc admin</label>
</li>
<li>
<input type="checkbox" id="user-investasi-baru" />
<label for="user-investasi-baru"
>User lain melakukan investasi baru</label
>
</li>
<li>
<input type="checkbox" id="lampirkan-bukti-transfer" />
<label for="lampirkan-bukti-transfer"
>User lain melampirkan bukti transfer</label
>
</li>
<li>
<input type="checkbox" id="validasi-admin-investasi" />
<label for="validasi-admin-investasi">Admin melakukan validasi</label>
</li>
<li>
<input type="checkbox" id="investor-lihat-transaksi" />
<label for="investor-lihat-transaksi"
>User investor melihat hasilnya pada halaman transaksi</label
>
</li>
</ul>
<h3>Donasi</h3>
<ul>
<li>
<input type="checkbox" id="buat-donasi" />
<label for="buat-donasi">Buat donasi baru</label>
</li>
<li>
<input type="checkbox" id="tunggu-acc-donasi" />
<label for="tunggu-acc-donasi">Tunggu acc admin</label>
</li>
<li>
<input type="checkbox" id="user-donasi-baru" />
<label for="user-donasi-baru">User lain melakukan donasi baru</label>
</li>
<li>
<input type="checkbox" id="lampirkan-bukti-donasi" />
<label for="lampirkan-bukti-donasi"
>User lain melampirkan bukti transfer</label
>
</li>
<li>
<input type="checkbox" id="validasi-admin-donasi" />
<label for="validasi-admin-donasi">Admin melakukan validasi</label>
</li>
<li>
<input type="checkbox" id="donatur-lihat-donasi" />
<label for="donatur-lihat-donasi"
>User donatur melihat hasilnya pada halaman donasi saya</label
>
</li>
</ul>
<h3>Job</h3>
<ul>
<li>
<input type="checkbox" id="buat-job-baru" />
<label for="buat-job-baru">Buat job baru</label>
</li>
<li>
<input type="checkbox" id="tunggu-acc-job" />
<label for="tunggu-acc-job">Tunggu acc admin</label>
</li>
<li>
<input type="checkbox" id="update-beranda-job" />
<label for="update-beranda-job"
>User lain melihat update diberanda</label
>
</li>
<li>
<input type="checkbox" id="share-whatsapp" />
<label for="share-whatsapp">Share melalui whatsapp</label>
</li>
</ul>
<h3>Forum</h3>
<ul>
<li>
<input type="checkbox" id="buat-forum-baru" />
<label for="buat-forum-baru">Buat forum baru</label>
</li>
<li>
<input type="checkbox" id="user-komentar" />
<label for="user-komentar">User lain berkomentar</label>
</li>
<li>
<input type="checkbox" id="kontrol-komentar" />
<label for="kontrol-komentar"
>User pemilik forum menutup atau membuka kolom komentar</label
>
</li>
<li>
<input type="checkbox" id="user-report" />
<label for="user-report">User lain melakukan report</label>
</li>
</ul>
</div>
<button id="reset-btn">Reset Semua Checklist</button>
<script>
document.addEventListener("DOMContentLoaded", () => {
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
const progressBar = document.getElementById("progress");
const resetBtn = document.getElementById("reset-btn");
// Load saved state from localStorage
checkboxes.forEach((checkbox) => {
const savedState = localStorage.getItem(checkbox.id);
if (savedState === "checked") {
checkbox.checked = true;
checkbox.parentElement.classList.add("checked");
}
});
// Update progress
function updateProgress() {
const totalCheckboxes = checkboxes.length;
const checkedCheckboxes = document.querySelectorAll(
'input[type="checkbox"]:checked'
).length;
const progressPercentage =
(checkedCheckboxes / totalCheckboxes) * 100;
progressBar.style.width = `${progressPercentage}%`;
}
// Initial progress update
updateProgress();
// Add event listeners to checkboxes
checkboxes.forEach((checkbox) => {
checkbox.addEventListener("change", () => {
// Toggle 'checked' class on parent li
checkbox.parentElement.classList.toggle(
"checked",
checkbox.checked
);
// Save state to localStorage
localStorage.setItem(
checkbox.id,
checkbox.checked ? "checked" : "unchecked"
);
// Update progress bar
updateProgress();
});
});
// Reset button functionality
resetBtn.addEventListener("click", () => {
checkboxes.forEach((checkbox) => {
checkbox.checked = false;
checkbox.parentElement.classList.remove("checked");
localStorage.removeItem(checkbox.id);
});
updateProgress();
});
});
</script>
</body>
</html>