Merge pull request #15 from bipproduction/home/menu

Home/menu
This commit is contained in:
Bagasbanuna02
2023-10-03 17:51:22 +08:00
committed by GitHub
30 changed files with 633 additions and 50 deletions

View File

@@ -0,0 +1,41 @@
-- CreateTable
CREATE TABLE "Profile" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"alamat" TEXT NOT NULL,
"jenisKelamin" TEXT NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"userId" TEXT,
"imagesId" TEXT,
CONSTRAINT "Profile_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Images" (
"id" TEXT NOT NULL,
"url" TEXT NOT NULL,
"active" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Images_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Profile_email_key" ON "Profile"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Profile_imagesId_key" ON "Profile"("imagesId");
-- AddForeignKey
ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Profile" ADD CONSTRAINT "Profile_imagesId_fkey" FOREIGN KEY ("imagesId") REFERENCES "Images"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -20,6 +20,7 @@ model User {
MasterUserRole MasterUserRole @relation(fields: [masterUserRoleId], references: [id]) MasterUserRole MasterUserRole @relation(fields: [masterUserRoleId], references: [id])
masterUserRoleId String @default("1") masterUserRoleId String @default("1")
UserSession UserSession? UserSession UserSession?
Profile Profile?
} }
model MasterUserRole { model MasterUserRole {
@@ -41,3 +42,27 @@ model UserSession {
User User @relation(fields: [userId], references: [id]) User User @relation(fields: [userId], references: [id])
userId String @unique userId String @unique
} }
model Profile {
id String @id @default(cuid())
name String
email String @unique
alamat String
jenisKelamin String
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
User User? @relation(fields: [userId], references: [id])
userId String? @unique
ImageProfile Images? @relation(fields: [imagesId], references: [id])
imagesId String? @unique
}
model Images {
id String @id @default(cuid())
url String
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
Profile Profile?
}

BIN
public/aset/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,25 @@
import { myConsole } from "@/app/fun/my_console";
import prisma from "@/app/lib/prisma";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
if (req.method === "POST") {
const body = await req.json();
myConsole(body);
const data = await prisma.profile.create({
data: {
userId: body.userId,
name: body.name,
email: body.email,
alamat: body.alamat,
jenisKelamin: body.jenisKelamin,
},
});
if (data) return NextResponse.json({ status: 201 });
return NextResponse.json({ success: true });
}
return NextResponse.json({ success: false });
}

View File

@@ -0,0 +1,9 @@
import { HomeLayout } from "@/app_modules/home";
export default async function Layout({children}: {children: any}) {
return <>
<HomeLayout>{children}</HomeLayout>
</>
}

View File

@@ -14,7 +14,7 @@ export default async function Page() {
// }) // })
// ); // );
if (!c?.value) return redirect("/dev/auth/login"); // if (!c?.value) return redirect("/dev/auth/login");
return ( return (
<> <>

View File

@@ -0,0 +1,9 @@
import { ProfileLayout } from "@/app_modules/katalog/profile";
export default function Layout({ children }: { children: any }) {
return (
<>
<ProfileLayout>{children}</ProfileLayout>
</>
);
}

View File

@@ -0,0 +1,7 @@
import { CreateProfile } from "@/app_modules/katalog/profile";
export default async function Page() {
return <>
<CreateProfile/>
</>
}

View File

@@ -0,0 +1,9 @@
import { KatalogLayout } from "@/app_modules/katalog/view";
export default async function Layout({ children }: { children: any }) {
return (
<>
<KatalogLayout>{children}</KatalogLayout>
</>
);
}

View File

@@ -0,0 +1,7 @@
import { KatalogView } from "@/app_modules/katalog/view";
export default async function Page() {
return <>
<KatalogView/>
</>
}

View File

@@ -6,4 +6,7 @@ export const ApiHipmi = {
validasi: "/api/auth/validasi", validasi: "/api/auth/validasi",
register: "/api/auth/register", register: "/api/auth/register",
logout: "/api/auth/logout", logout: "/api/auth/logout",
//Profile
create_profile: "/api/profile/create"
}; };

View File

@@ -64,7 +64,6 @@ export default function Login() {
align={"center"} align={"center"}
gap={"lg"} gap={"lg"}
> >
<>
<IconCircleLetterH size={150} /> <IconCircleLetterH size={150} />
<Title>Login</Title> <Title>Login</Title>
@@ -80,7 +79,10 @@ export default function Login() {
/> />
<Button <Button
mt={"xs"}
h={30} h={30}
w={250}
radius={50} radius={50}
compact compact
bg={Warna.hijau_muda} bg={Warna.hijau_muda}
@@ -91,7 +93,6 @@ export default function Login() {
> >
Login Login
</Button> </Button>
</>
</Flex> </Flex>
</> </>
); );

View File

@@ -1,15 +1,20 @@
"use client"; "use client";
import { myConsole } from "@/app/fun/my_console"; import { myConsole } from "@/app/fun/my_console";
import { ApiHipmi } from "@/app/lib/api"; import { ApiHipmi } from "@/app/lib/api";
import { Button } from "@mantine/core"; import { ActionIcon, Button } from "@mantine/core";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { gs_nomor, gs_otp } from "../state/state"; import { gs_nomor, gs_otp } from "../state/state";
import { IconLogout } from "@tabler/icons-react";
import { Warna } from "@/app/lib/warna";
import { gs_token } from "@/app_modules/home/state/global_state";
export default function Logout() { export default function Logout() {
const router = useRouter(); const router = useRouter();
const [nomor, setnomor] = useAtom(gs_nomor); const [nomor, setnomor] = useAtom(gs_nomor);
const [code, setCode] = useAtom(gs_otp); const [code, setCode] = useAtom(gs_otp);
const [token, setToken] = useAtom(gs_token);
const onLogout = async () => { const onLogout = async () => {
// MyConsole("keluar"); // MyConsole("keluar");
@@ -20,6 +25,7 @@ export default function Logout() {
if (val.status == 200) { if (val.status == 200) {
setnomor(null); setnomor(null);
setCode(null); setCode(null);
setToken(null)
return router.push("/dev/auth/login"); return router.push("/dev/auth/login");
} }
@@ -28,9 +34,9 @@ export default function Logout() {
return ( return (
<> <>
<Button compact onClick={() => onLogout()}> <ActionIcon variant="transparent">
Logout <IconLogout color={Warna.merah} onClick={() => onLogout()}/>
</Button> </ActionIcon>
</> </>
); );
} }

View File

@@ -60,6 +60,7 @@ export default function Register() {
<Flex direction={"column"} gap={"xl"} align={"center"}> <Flex direction={"column"} gap={"xl"} align={"center"}>
<Flex direction={"column"}> <Flex direction={"column"}>
<TextInput <TextInput
w={250}
label="Username" label="Username"
placeholder="Username" placeholder="Username"
onChange={(val) => { onChange={(val) => {
@@ -70,6 +71,8 @@ export default function Register() {
<Text>Nomor : {nomor}</Text> <Text>Nomor : {nomor}</Text>
</Flex> </Flex>
<Button <Button
w={250}
mt={"md"}
radius={50} radius={50}
bg={Warna.biru} bg={Warna.biru}
color="cyan" color="cyan"

View File

@@ -81,11 +81,15 @@ export default function Validasi() {
<Text>to {nomor}</Text> <Text>to {nomor}</Text>
</Flex> </Flex>
<PinInput <PinInput
spacing={"md"}
mt={"md"}
onChange={(val) => { onChange={(val) => {
setInputOtp(val); setInputOtp(val);
}} }}
/> />
<Button <Button
w={100}
mt={"md"}
compact compact
radius={50} radius={50}
bg={Warna.hijau_tua} bg={Warna.hijau_tua}

View File

@@ -9,6 +9,9 @@ import fs from "fs";
import yaml from "yaml"; import yaml from "yaml";
const config = yaml.parse(fs.readFileSync("config.yaml").toString()); const config = yaml.parse(fs.readFileSync("config.yaml").toString());
/**
* @returns token(id and username)
*/
export async function getToken() { export async function getToken() {
const c = cookies().get("ssn"); const c = cookies().get("ssn");

View File

@@ -1,3 +1,4 @@
import HomeView from "./view"; import HomeView from "./view";
import HomeLayout from "./layout";
export {HomeView} import { getToken } from "./fun/get-token";
export {HomeView, HomeLayout, getToken}

View File

@@ -0,0 +1,39 @@
"use client";
import { ActionIcon, AppShell, Flex, Group, Header, Text } from "@mantine/core";
import { HomeView } from ".";
import { IconUserSearch, IconAward, IconQrcode } from "@tabler/icons-react";
import { Logout } from "../auth";
export default function HomeLayout({ children }: { children: any }) {
return (
<>
<AppShell
header={
<Header height={50} bg={"dark"}>
<Group position="apart" align="center" h={50} p={"sm"}>
<Group spacing={"sm"}>
<ActionIcon>
<IconUserSearch />
</ActionIcon>
<ActionIcon>
<IconAward />
</ActionIcon>
</Group>
<Text color="white" fw={"bold"}>
HIPMI
</Text>
<Group spacing={"sm"}>
<ActionIcon>
<IconQrcode />
</ActionIcon>
<Logout />
</Group>
</Group>
</Header>
}
>
{children}
</AppShell>
</>
);
}

View File

@@ -0,0 +1,3 @@
import { atomWithStorage } from "jotai/utils";
export const gs_token = atomWithStorage<any | null>("gs_token", null);

View File

@@ -1,24 +1,87 @@
"use client"; "use client";
import { Text, Title } from "@mantine/core"; import {
ActionIcon,
Box,
Flex,
Image,
Loader,
Paper,
SimpleGrid,
Text,
Title,
} from "@mantine/core";
import { Logout } from "../auth"; import { Logout } from "../auth";
import { useState } from "react"; import { useState } from "react";
import { ApiHipmi } from "@/app/lib/api"; import { ApiHipmi } from "@/app/lib/api";
import { useShallowEffect } from "@mantine/hooks"; import { useShallowEffect } from "@mantine/hooks";
import { getToken } from "./fun/get-token"; import { getToken } from "./fun/get-token";
import {
IconAffiliate,
IconBriefcase,
IconHeartHandshake,
IconMap2,
IconMessages,
IconPackageImport,
IconPresentation,
IconShoppingBag,
IconUserCircle,
} from "@tabler/icons-react";
import toast from "react-simple-toasts";
import { getProfile } from "../katalog/profile";
import { useRouter } from "next/navigation";
import { useAtom } from "jotai";
import { gs_token } from "./state/global_state";
const listHalaman = [
{
id: 1,
name: "Forums",
icon: <IconMessages size={50} />,
},
{
id: 2,
name: "Project Collaboration",
icon: <IconAffiliate size={50} />,
},
{
id: 3,
name: "Voting",
icon: <IconPackageImport size={50} />,
},
{
id: 4,
name: "Event",
icon: <IconPresentation size={50} />,
},
{
id: 5,
name: "Crowd Funding",
icon: <IconHeartHandshake size={50} />,
},
{
id: 6,
name: "Marketplace",
icon: <IconShoppingBag size={50} />,
},
{
id: 7,
name: "Job Vacancy",
icon: <IconBriefcase size={50} />,
},
{
id: 8,
name: "Business Maps",
icon: <IconMap2 size={50} />,
},
];
export default function HomeView() { export default function HomeView() {
const [token, setToken] = useState<any | null>(null); const router = useRouter();
const [token, setToken] = useAtom(gs_token);
// useShallowEffect(() => { const [profile, setProfile] = useState<any | null>(null);
// userToken();
// }, []);
// async function userToken() {
// await fetch(ApiHipmi.get_token)
// .then((res) => res.json())
// .then((val) => setToken(val));
// }
useShallowEffect(() => { useShallowEffect(() => {
getUserId(); getUserId();
@@ -28,12 +91,64 @@ export default function HomeView() {
setToken(data); setToken(data);
} }
useShallowEffect(() => {
getUserProfile();
}, []);
async function getUserProfile() {
const data = await getProfile();
setProfile(data);
}
return ( return (
<> <>
{/* <pre>{JSON.stringify(token, null, 2)}</pre> */} {/* <pre>{JSON.stringify(profile, null, 2)}</pre> */}
<Title>Home</Title> <Box>
<Text>Welcome, {token?.username}</Text> <Flex align={"center"} gap={"sm"}>
<Logout /> <ActionIcon
size={30}
variant="transparent"
onClick={() => {
if (profile === null) {
return router.push("/dev/katalog/profile/create");
} else {
return router.push("/dev/katalog/view");
}
}}
>
<IconUserCircle size={50} color="black" />
</ActionIcon>
<Text>
Welcome to,{" "}
{token?.username ? token?.username : <Loader size={"xs"} />}
</Text>
</Flex>
<Paper bg={"dark"} radius={5} my={"xs"}>
<Image alt="logo" src={"/aset/logo.png"} />
</Paper>
<Box my={"sm"}>
<SimpleGrid cols={2}>
{listHalaman.map((e, i) => (
<Paper
key={e.id}
h={100}
withBorder
onClick={() => toast(e.name)}
>
<Flex
justify={"center"}
align={"center"}
direction={"column"}
h={100}
>
{e.icon}
{e.name}
</Flex>
</Paper>
))}
</SimpleGrid>
</Box>
</Box>
</> </>
); );
} }

View File

@@ -0,0 +1,27 @@
"use client";
import { Header, Group, ActionIcon, Text } from "@mantine/core";
import { IconArrowLeft } from "@tabler/icons-react";
import React from "react";
export default function headerTransparent({
icon1,
icon2,
title,
}: {
icon1: React.ReactNode;
icon2: React.ReactNode;
title: string;
}) {
return (
<>
<Header height={50} px={"sm"}>
<Group position="apart" h={50}>
{icon1}
<Text>{title}</Text>
{icon2}
</Group>
</Header>
</>
);
}

View File

@@ -0,0 +1,3 @@
import headerTransparent from "./component/header_transparent";
export {headerTransparent}

View File

@@ -0,0 +1,36 @@
"use client";
import {
ActionIcon,
AppShell,
Group,
Header,
Text,
Title,
} from "@mantine/core";
import { IconArrowLeft } from "@tabler/icons-react";
import { useRouter } from "next/navigation";
export default function ProfileLayout({ children }: { children: any }) {
const router = useRouter()
return (
<>
<AppShell
header={
<Header height={50} px={"sm"}>
<Group position="apart" h={50}>
<ActionIcon variant="transparent" onClick={() => router.push("/dev/home")}>
<IconArrowLeft />
</ActionIcon>
<Text>Create Profile</Text>
<ActionIcon variant="transparent"></ActionIcon>
</Group>
</Header>
}
>
{children}
</AppShell>
</>
);
}

View File

@@ -0,0 +1,112 @@
"use client";
import { myConsole } from "@/app/fun/my_console";
import { ApiHipmi } from "@/app/lib/api";
import { Warna } from "@/app/lib/warna";
import { gs_token } from "@/app_modules/home/state/global_state";
import { Button, Select, Stack, TextInput } from "@mantine/core";
import { useAtom } from "jotai";
import _ from "lodash";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-simple-toasts";
export default function CreateProfile() {
const router = useRouter();
const [token, setToken] = useAtom(gs_token);
const [value, setValue] = useState({
name: "",
email: "",
alamat: "",
jenisKelamin: "",
});
async function onSubmit() {
const body = {
userId: token?.id,
name: value.name,
email: value.email,
alamat: value.alamat,
jenisKelamin: value.jenisKelamin,
};
myConsole(body);
if (_.values(value).includes("")) return toast("Lengkapi data");
// if(_.values(value.email).includes(`${/^\S+@\S+$/.test(value.email) ? null : "Invalid email"}`)){}
await fetch(ApiHipmi.create_profile, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
.then((res) => res.json())
.then((val) => {
if (val.status == 201) {
toast("Data tersimpan")
return router.push("/dev/katalog/view");
} else {
return toast("Server Error!!!")
}
});
}
return (
<>
<Stack px={"sm"}>
<TextInput
label="Nama"
onChange={(val) => {
setValue({
...value,
name: val.target.value,
});
}}
/>
<TextInput
label="Email"
onChange={(val) => {
setValue({
...value,
email: val.target.value,
});
}}
/>
<TextInput
label="Alamat"
onChange={(val) => {
setValue({
...value,
alamat: val.target.value,
});
}}
/>
<Select
label="Jenis Kelamin"
data={[
{ value: "Laki-laki", label: "Laki-laki" },
{ value: "Perempuan", label: "Perempuan" },
]}
onChange={(val) => {
setValue({
...value,
jenisKelamin: val as string,
});
}}
/>
<Button
mt={"md"}
radius={50}
bg={Warna.hijau_muda}
color="green"
onClick={() => onSubmit()}
>
Simpan
</Button>
</Stack>
</>
);
}

View File

@@ -0,0 +1,33 @@
"use server";
import { myConsole } from "@/app/fun/my_console";
import prisma from "@/app/lib/prisma";
import { getToken } from "@/app_modules/home";
import { NextResponse } from "next/server";
export async function getProfile() {
const token = await getToken();
const dataProfile = await prisma.profile.findUnique({
where: {
userId: token.id,
},
select: {
id: true,
name: true,
email: true,
alamat: true,
jenisKelamin: true,
active: true,
ImageProfile: {
select: {
id: true,
url: true,
active: true,
},
},
},
});
return dataProfile;
}

View File

@@ -0,0 +1,4 @@
import ProfileLayout from "./create/layout";
import CreateProfile from "./create/view";
import {getProfile} from "./fun/get-profile";
export {ProfileLayout, CreateProfile, getProfile}

View File

@@ -0,0 +1,4 @@
import KatalogView from "./view";
import KatalogLayout from "./layout";
export {KatalogView, KatalogLayout}

View File

@@ -0,0 +1,41 @@
"use client";
import { Logout } from "@/app_modules/auth";
import { ActionIcon, AppShell, Group, Header, Text } from "@mantine/core";
import { IconUserSearch, IconAward, IconQrcode, IconArrowLeft, IconPencilPlus } from "@tabler/icons-react";
import { useRouter } from "next/navigation";
export default function KatalogLayout({ children }: { children: any }) {
const router = useRouter()
return (
<>
<AppShell
header={
<Header height={50} bg={"dark"}>
<Group position="apart" align="center" h={50} p={"sm"}>
<Group spacing={"sm"}>
<ActionIcon variant="transparent" onClick={() => router.push("/dev/home")}>
<IconArrowLeft/>
</ActionIcon>
{/* <ActionIcon>
<IconAward />
</ActionIcon> */}
</Group>
<Text color="white" fw={"bold"}>
Katalog
</Text>
<Group spacing={"sm"}>
<ActionIcon>
<IconPencilPlus />
</ActionIcon>
{/* <Logout /> */}
</Group>
</Group>
</Header>
}
>
{children}
</AppShell>
</>
);
}

View File

@@ -0,0 +1,13 @@
"use client"
import { BackgroundImage, Box, Center, Text } from "@mantine/core"
export default function KatalogView(){
return <>
<Center>
Katalog User
</Center>
</>
}