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

@@ -9,7 +9,7 @@ datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
username String @unique
@@ -20,6 +20,7 @@ model User {
MasterUserRole MasterUserRole @relation(fields: [masterUserRoleId], references: [id])
masterUserRoleId String @default("1")
UserSession UserSession?
Profile Profile?
}
model MasterUserRole {
@@ -41,3 +42,27 @@ model UserSession {
User User @relation(fields: [userId], references: [id])
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 (
<>

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",
register: "/api/auth/register",
logout: "/api/auth/logout",
//Profile
create_profile: "/api/profile/create"
};

View File

@@ -64,34 +64,35 @@ export default function Login() {
align={"center"}
gap={"lg"}
>
<>
<IconCircleLetterH size={150} />
<Title>Login</Title>
<IconCircleLetterH size={150} />
<Title>Login</Title>
<TextInput
label="Phone Number"
w={250}
type="number"
placeholder="62 xx xxx xxx xxx"
// value={nomor}
onChange={(val) => {
setNomor(val.target.value);
}}
/>
<TextInput
label="Phone Number"
w={250}
type="number"
placeholder="62 xx xxx xxx xxx"
// value={nomor}
onChange={(val) => {
setNomor(val.target.value);
}}
/>
<Button
h={30}
radius={50}
compact
bg={Warna.hijau_muda}
color={"green"}
onClick={() => {
onLogin();
}}
>
Login
</Button>
</>
<Button
mt={"xs"}
h={30}
w={250}
radius={50}
compact
bg={Warna.hijau_muda}
color={"green"}
onClick={() => {
onLogin();
}}
>
Login
</Button>
</Flex>
</>
);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import HomeView from "./view";
export {HomeView}
import HomeLayout from "./layout";
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";
import { Text, Title } from "@mantine/core";
import {
ActionIcon,
Box,
Flex,
Image,
Loader,
Paper,
SimpleGrid,
Text,
Title,
} from "@mantine/core";
import { Logout } from "../auth";
import { useState } from "react";
import { ApiHipmi } from "@/app/lib/api";
import { useShallowEffect } from "@mantine/hooks";
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() {
const [token, setToken] = useState<any | null>(null);
// useShallowEffect(() => {
// userToken();
// }, []);
// async function userToken() {
// await fetch(ApiHipmi.get_token)
// .then((res) => res.json())
// .then((val) => setToken(val));
// }
const router = useRouter();
const [token, setToken] = useAtom(gs_token);
const [profile, setProfile] = useState<any | null>(null);
useShallowEffect(() => {
getUserId();
@@ -28,12 +91,64 @@ export default function HomeView() {
setToken(data);
}
useShallowEffect(() => {
getUserProfile();
}, []);
async function getUserProfile() {
const data = await getProfile();
setProfile(data);
}
return (
<>
{/* <pre>{JSON.stringify(token, null, 2)}</pre> */}
<Title>Home</Title>
<Text>Welcome, {token?.username}</Text>
<Logout />
{/* <pre>{JSON.stringify(profile, null, 2)}</pre> */}
<Box>
<Flex align={"center"} gap={"sm"}>
<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>
</>
}