summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authortrisusilo48 <tri.susilo@altama.co.id>2024-10-21 14:54:11 +0700
committertrisusilo48 <tri.susilo@altama.co.id>2024-10-21 14:54:11 +0700
commit83d1a1c558293e1b14c9a5847628e7661f749c66 (patch)
tree5f083f90192df0fc2aff41e3dd1c3c84f2592352 /app
parent30c5eb5776fcc60f023ad6aa51153cb375c87930 (diff)
initial commit
Diffstat (limited to 'app')
-rw-r--r--app/lib/api/auth.ts42
-rw-r--r--app/lib/api/odooApi.ts56
-rw-r--r--app/lib/camera/component/camera.tsx79
-rw-r--r--app/lib/camera/component/cardFoto.tsx27
-rw-r--r--app/lib/camera/component/hedear.tsx21
-rw-r--r--app/lib/camera/component/pakageCamera.tsx44
-rw-r--r--app/lib/camera/component/scannerBarcode.tsx56
-rw-r--r--app/lib/camera/component/sjCamera.tsx48
-rw-r--r--app/lib/camera/hooks/useCamera.ts32
-rw-r--r--app/lib/camera/hooks/useCameraStore.ts22
-rw-r--r--app/login/page.tsx174
-rw-r--r--app/page.tsx248
12 files changed, 757 insertions, 92 deletions
diff --git a/app/lib/api/auth.ts b/app/lib/api/auth.ts
new file mode 100644
index 0000000..d954dc5
--- /dev/null
+++ b/app/lib/api/auth.ts
@@ -0,0 +1,42 @@
+import { deleteCookie, getCookie, setCookie } from "cookies-next"
+
+type AuthProps = {
+ id: number;
+ parentId: number;
+ parentName: string;
+ partnerId: number;
+ name: string;
+ email: string;
+ phone: string;
+ npwp: string;
+ mobile: string;
+ external: boolean;
+ company: boolean;
+ pricelist: string | null;
+ token: string;
+ feature : {
+ onlyReadyStock : boolean,
+ soApproval : boolean
+ }
+ };
+
+const getAuth = () : AuthProps | boolean => {
+ const auth = getCookie('auth')
+
+ if (auth) return JSON.parse(auth)
+ return false
+
+}
+
+const setAuth = (user : AuthProps) : boolean => {
+ setCookie('auth', JSON.stringify(user))
+ return true
+}
+
+
+const deleteAuth = () : boolean => {
+ deleteCookie('auth')
+ return true
+}
+
+export { getAuth , setAuth, deleteAuth} \ No newline at end of file
diff --git a/app/lib/api/odooApi.ts b/app/lib/api/odooApi.ts
new file mode 100644
index 0000000..c2c9d82
--- /dev/null
+++ b/app/lib/api/odooApi.ts
@@ -0,0 +1,56 @@
+import axios from "axios"
+import { getCookie, setCookie } from "cookies-next";
+import { getAuth } from "./auth";
+
+type axiosParameters = {
+ method : string,
+ url : string,
+ headers : {
+ Authorization : string,
+ 'Content-Type'? : string,
+ Token? : string
+ },
+ data ?: string
+}
+
+const renewToken = async () => {
+ const token = await axios.get(process.env.NEXT_PUBLIC_ODOO_API_HOST + '/api/token')
+ setCookie('token', token.data.result)
+ return token.data.result
+};
+
+const getToken = async () => {
+ let token = getCookie('token')
+ if (token == undefined) token = await renewToken()
+ return token
+};
+
+const odooApi = async (method : string, url : string, data = {}, headers = {}) => {
+ try {
+ const token = await getToken()
+ const auth = getAuth();
+ const axiosParameter : axiosParameters = {
+ method,
+ url: process.env.NEXT_PUBLIC_ODOO_API_HOST + url,
+ headers: { Authorization: token ? token : '', ...headers },
+ };
+ console.log('ini adalah tipe',axiosParameter)
+ if (auth && typeof auth === 'object' && 'token' in auth) {
+ axiosParameter.headers['Token'] = auth.token;
+ }
+ if (method.toUpperCase() == 'POST')
+ axiosParameter.headers['Content-Type'] =
+ 'application/x-www-form-urlencoded';
+ if (Object.keys(data).length > 0)
+ axiosParameter.data = new URLSearchParams(
+ Object.entries(data)
+ ).toString();
+ const response = await axios(axiosParameter);
+ return response.data;
+ } catch (error) {
+ console.log( JSON.stringify(error));
+ }
+}
+
+
+export default odooApi \ No newline at end of file
diff --git a/app/lib/camera/component/camera.tsx b/app/lib/camera/component/camera.tsx
new file mode 100644
index 0000000..b398616
--- /dev/null
+++ b/app/lib/camera/component/camera.tsx
@@ -0,0 +1,79 @@
+import { CameraSharp } from "@mui/icons-material";
+import { Button, IconButton } from "@mui/material";
+import Image from "next/image";
+import React, { useRef } from "react";
+import Webcam from "react-webcam";
+
+interface WebcamCaptureProps {
+ image: string | null;
+ setImage: (image: string | null) => void;
+ isWebcamVisible: boolean;
+ setIsWebcamVisible: (isVisible: boolean) => void;
+}
+
+const WebcamCapture: React.FC<WebcamCaptureProps> = ({
+ image,
+ setImage,
+ isWebcamVisible,
+ setIsWebcamVisible,
+}) => {
+ const webcamRef = useRef<Webcam>(null);
+
+ // Mengambil foto dari webcam
+ const capture = () => {
+ const image = webcamRef.current?.getScreenshot();
+ setIsWebcamVisible(false);
+ setImage(image || null);
+ };
+
+ const takePicture = () => {
+ setImage(null);
+ setIsWebcamVisible(true);
+ };
+
+ // Mengatur ukuran webcam
+ const videoConstraints = {
+ width: 500,
+ height: 480,
+ facingMode: {
+ exact: "environment" // untuk kamera belakang
+ },
+ };
+
+ return (
+ <div className="items-center">
+ {!isWebcamVisible && (
+ <Button variant="text" size="large" onClick={() => takePicture()}>
+ Ambil Foto Surat Jalan
+ </Button>
+ )}
+
+ {isWebcamVisible && (
+ <div>
+ <Webcam
+ audio={false}
+ ref={webcamRef}
+ screenshotFormat="image/jpeg"
+ videoConstraints={videoConstraints}
+ />
+ <IconButton aria-label="camera" size="large" onClick={() => capture()}>
+ <CameraSharp fontSize="inherit" />
+ </IconButton>
+ </div>
+ )}
+ {image && (
+ <div>
+ <Image
+ src={image}
+ alt="Captured"
+ width={500}
+ height={480}
+ unoptimized
+ />
+ </div>
+ )}
+ </div>
+ );
+};
+
+export default WebcamCapture;
diff --git a/app/lib/camera/component/cardFoto.tsx b/app/lib/camera/component/cardFoto.tsx
new file mode 100644
index 0000000..34da216
--- /dev/null
+++ b/app/lib/camera/component/cardFoto.tsx
@@ -0,0 +1,27 @@
+import { Card, CardContent, Typography } from "@mui/material";
+import Image from "next/image";
+import React from "react";
+import useCameraStore from "../hooks/useCameraStore";
+
+const CardFotos = () => {
+ const { imagePackage } = useCameraStore();
+ return (
+ <Card sx={{ maxWidth: 200 }}>
+ <Image
+ src={imagePackage ?? ''}
+ alt="Captured"
+ layout="fill"
+ objectFit="cover"
+ unoptimized
+ className="p-2"
+ />
+ <CardContent>
+ <Typography gutterBottom variant="h5" component="div">
+ Lizard
+ </Typography>
+ </CardContent>
+ </Card>
+ );
+}
+
+export default CardFotos
diff --git a/app/lib/camera/component/hedear.tsx b/app/lib/camera/component/hedear.tsx
new file mode 100644
index 0000000..5cf3f1d
--- /dev/null
+++ b/app/lib/camera/component/hedear.tsx
@@ -0,0 +1,21 @@
+// components/Header.tsx
+import Image from "next/image";
+
+export default function Header() {
+ return (
+ <nav className="fixed top-0 left-0 w-full bg-white border-b-2 border-red-500 py-4 px-4 sm:px-96 z-50 shadow-md">
+ <div className="flex justify-between items-center">
+ <div className="flex items-center">
+ <Image
+ src="/images/indoteknik-logo.png" // Ganti dengan path logo Anda
+ alt="Logo"
+ width={120}
+ height={60}
+ className="rounded-full"
+ />
+ </div>
+
+ </div>
+ </nav>
+ );
+}
diff --git a/app/lib/camera/component/pakageCamera.tsx b/app/lib/camera/component/pakageCamera.tsx
new file mode 100644
index 0000000..e0c7158
--- /dev/null
+++ b/app/lib/camera/component/pakageCamera.tsx
@@ -0,0 +1,44 @@
+import React from "react";
+import useCameraStore from "../hooks/useCameraStore";
+import { IconButton } from "@mui/material";
+import { PhotoCameraFrontOutlined } from "@mui/icons-material";
+
+const PackageCamera: React.FC = () => {
+ const { setImagePackage } = useCameraStore();
+ const handleCapture = (event: React.ChangeEvent<HTMLInputElement>) => {
+ const file = event.target.files?.[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setImagePackage(reader.result as string);
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ return (
+ <div className="px-4 py-8 items-center border-2 rounded-md shadow-sm w-[49%] text-center ">
+ <input
+ type="file"
+ accept="image/*"
+ capture="environment"
+ onChange={handleCapture}
+ className="hidden"
+ id="pakageCameraInput"
+ />
+ <label htmlFor="pakageCameraInput" className="text-gray-600">
+ <IconButton
+ color="primary"
+ aria-label="upload picture"
+ component="span"
+ >
+ <PhotoCameraFrontOutlined fontSize="large" />
+ </IconButton>
+ <br />
+ Foto Penerima
+ </label>
+ </div>
+ );
+};
+
+export default PackageCamera;
diff --git a/app/lib/camera/component/scannerBarcode.tsx b/app/lib/camera/component/scannerBarcode.tsx
new file mode 100644
index 0000000..3079e33
--- /dev/null
+++ b/app/lib/camera/component/scannerBarcode.tsx
@@ -0,0 +1,56 @@
+import { QrCode2 } from "@mui/icons-material";
+import { Button, TextField } from "@mui/material";
+import dynamic from "next/dynamic";
+import React, { useState } from "react";
+import useCameraStore from "../hooks/useCameraStore";
+
+const BarcodeScannerComponent = dynamic(
+ () => import("react-qr-barcode-scanner"),
+ { ssr: false }
+);
+const BarcodeScanner: React.FC = () => {
+ const { barcode, setBarcode } = useCameraStore();
+ const [isCameraActive, setIsCameraActive] = useState(false);
+
+ return (
+ <div>
+ <Button
+ variant="outlined"
+ onClick={() => setIsCameraActive(!isCameraActive)}
+ startIcon={<QrCode2 />}
+ color="error"
+ className="mb-2"
+ >
+ {isCameraActive ? "Cancel" : "Scane Code"}
+ </Button>
+
+ {isCameraActive && (
+ <BarcodeScannerComponent
+ width={500} // Tingkatkan ukuran untuk memperjelas gambar
+ height={300}
+ onUpdate={(err, result) => {
+ if (result) {
+ setBarcode(result.getText());
+ setIsCameraActive(false);
+ }
+ }}
+ />
+ )}
+
+ <div className="mt-4">
+ <TextField
+ fullWidth
+ label="Detected Picking Code"
+ id="outlined-basic"
+ value={barcode}
+ onChange={ (e) => setBarcode(e.target.value) }
+ InputLabelProps={{
+ shrink: true, // Label akan selalu berada di atas (outline)
+ }}
+ />
+ </div>
+ </div>
+ );
+};
+
+export default BarcodeScanner;
diff --git a/app/lib/camera/component/sjCamera.tsx b/app/lib/camera/component/sjCamera.tsx
new file mode 100644
index 0000000..5cc39ad
--- /dev/null
+++ b/app/lib/camera/component/sjCamera.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import useCameraStore from "../hooks/useCameraStore";
+import { IconButton } from "@mui/material";
+import { PendingActions } from "@mui/icons-material";
+
+const SjCamera: React.FC = () => {
+ const { setImageSj } = useCameraStore();
+ const handleSuratJalanCapture = (
+ event: React.ChangeEvent<HTMLInputElement>
+ ) => {
+ const file = event.target.files?.[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setImageSj(reader.result as string);
+ };
+ reader.readAsDataURL(file);
+ }
+ };
+
+ return (
+ <>
+ <div className="p-4 py-8 items-center border-2 rounded-md shadow-sm w-[49%] text-center">
+ <input
+ type="file"
+ accept="image/*"
+ capture="environment"
+ onChange={handleSuratJalanCapture}
+ className="hidden"
+ id="suratJalanInput"
+ />
+ <label htmlFor="suratJalanInput" className="text-gray-600">
+ <IconButton
+ color="primary"
+ aria-label="upload picture"
+ component="span"
+ >
+ <PendingActions fontSize="large" />
+ </IconButton>
+ <br />
+ Foto Surat Jalan
+ </label>
+ </div>
+ </>
+ );
+};
+
+export default SjCamera;
diff --git a/app/lib/camera/hooks/useCamera.ts b/app/lib/camera/hooks/useCamera.ts
new file mode 100644
index 0000000..d85d978
--- /dev/null
+++ b/app/lib/camera/hooks/useCamera.ts
@@ -0,0 +1,32 @@
+import { useState } from "react"
+
+const useCamera = () => {
+
+ const [imageSJ, setImageSj] = useState<string | null>(null)
+ const [imagePackage, setImagePackage] = useState<string | null>(null)
+ const [barcode, setBarcode] = useState<string | null>(null)
+
+ const [isWebcamVisibleSj, setIsWebcamVisibleSJ] = useState<boolean>(false)
+ const [isWebcamVisiblePackage, setIsWebcamVisiblePackage] = useState<boolean>(false)
+
+ const handleSubmit = () => {
+ setIsWebcamVisibleSJ(false)
+ }
+
+
+ return {
+ barcode,
+ setBarcode,
+ imageSJ,
+ imagePackage,
+ setImageSj,
+ setImagePackage,
+ isWebcamVisibleSj,
+ isWebcamVisiblePackage,
+ setIsWebcamVisibleSJ,
+ setIsWebcamVisiblePackage,
+ handleSubmit
+ }
+}
+
+export default useCamera \ No newline at end of file
diff --git a/app/lib/camera/hooks/useCameraStore.ts b/app/lib/camera/hooks/useCameraStore.ts
new file mode 100644
index 0000000..c922d1c
--- /dev/null
+++ b/app/lib/camera/hooks/useCameraStore.ts
@@ -0,0 +1,22 @@
+// store/useCameraStore.ts
+import { create } from 'zustand'
+
+interface CameraStore {
+ barcode: string | null
+ setBarcode: (barcode: string) => void
+ imageSj: string | null
+ setImageSj: (image: string) => void
+ imagePackage: string | null
+ setImagePackage: (image: string) => void
+}
+
+const useCameraStore = create<CameraStore>((set) => ({
+ barcode: null,
+ setBarcode: (barcode: string) => set({ barcode: barcode }),
+ imageSj: null,
+ setImageSj: (image: string) => set({ imageSj: image }),
+ imagePackage: null,
+ setImagePackage: (image: string) => set({ imagePackage: image }),
+}))
+
+export default useCameraStore \ No newline at end of file
diff --git a/app/login/page.tsx b/app/login/page.tsx
new file mode 100644
index 0000000..8df5b2c
--- /dev/null
+++ b/app/login/page.tsx
@@ -0,0 +1,174 @@
+"use client";
+import {
+ Box,
+ Button,
+ FormControl,
+ FormLabel,
+ TextField,
+ Typography,
+} from "@mui/material";
+import Header from "../lib/camera/component/hedear";
+import odooApi from "../lib/api/odooApi";
+import { getAuth, setAuth } from "../lib/api/auth";
+import { useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
+
+const Login = () => {
+ const router = useRouter();
+ const [emailError, setEmailError] = useState(false);
+ const [emailErrorMessage, setEmailErrorMessage] = useState("");
+ const [passwordError, setPasswordError] = useState(false);
+ const [passwordErrorMessage, setPasswordErrorMessage] = useState("");
+
+ useEffect(() => {
+ const token = getAuth();
+
+ if (token) {
+ router.push("/");
+ }
+ }, [router]);
+
+ const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ if (emailError || passwordError) {
+ return;
+ }
+ const data = new FormData(event.currentTarget);
+ const email = data.get("email");
+ const password = data.get("password");
+
+ console.log('ini user', email, password)
+ try {
+ odooApi("POST", "/api/v1/user/login", {
+ email ,
+ password
+ }).then((res) => {
+ const auth = res.result;
+ if (auth.is_auth) {
+ setAuth(auth.user);
+ router.push("/");
+ return;
+ }
+ switch (auth.reason) {
+ case "NOT_FOUND":
+ alert("Email tidak ditemukan");
+ break;
+ case "NOT_ACTIVE":
+ alert("Akun anda belum aktif");
+ break;
+ }
+ console.log('ini akhir',res);
+ });
+ } catch (error) {
+ console.log(error);
+ }
+ };
+
+ const validateInputs = () => {
+ const email = document.getElementById("email") as HTMLInputElement;
+ const password = document.getElementById("password") as HTMLInputElement;
+
+ let isValid = true;
+
+ if (!email.value || !/\S+@\S+\.\S+/.test(email.value)) {
+ setEmailError(true);
+ setEmailErrorMessage("Please enter a valid email address.");
+ isValid = false;
+ } else {
+ setEmailError(false);
+ setEmailErrorMessage("");
+ }
+
+ if (!password.value || password.value.length < 6) {
+ setPasswordError(true);
+ setPasswordErrorMessage("Password must be at least 6 characters long.");
+ isValid = false;
+ } else {
+ setPasswordError(false);
+ setPasswordErrorMessage("");
+ }
+
+ return isValid;
+ };
+ return (
+ <div className="bg-[#fafeff] h-screen overflow-auto">
+ <Header />
+ <div className="py-4 px-4 sm:px-96 pt-20">
+ <div className="bg-white py-6 px-4 sm:px-96 shadow-md rounded-sm">
+ <Typography
+ component="h1"
+ variant="h4"
+ sx={{
+ width: "100%",
+ fontSize: "clamp(2rem, 10vw, 2.15rem)",
+ mb: 4,
+ }}
+ >
+ Sign in
+ </Typography>
+ <Box
+ component="form"
+ onSubmit={handleSubmit}
+ noValidate
+ sx={{
+ display: "flex",
+ flexDirection: "column",
+ width: "100%",
+ gap: 2,
+ }}
+ >
+ <FormControl>
+ <FormLabel htmlFor="email">Email</FormLabel>
+ <TextField
+ error={emailError}
+ helperText={emailErrorMessage}
+ id="email"
+ type="email"
+ name="email"
+ placeholder="your@email.com"
+ autoComplete="email"
+ autoFocus
+ required
+ fullWidth
+ variant="outlined"
+ color={emailError ? "error" : "primary"}
+ sx={{ ariaLabel: "email" }}
+ size="small"
+ />
+ </FormControl>
+ <FormControl>
+ <Box sx={{ display: "flex", justifyContent: "space-between" }}>
+ <FormLabel htmlFor="password">Password</FormLabel>
+ </Box>
+ <TextField
+ error={passwordError}
+ helperText={passwordErrorMessage}
+ name="password"
+ placeholder="••••••"
+ type="password"
+ id="password"
+ autoComplete="current-password"
+ autoFocus
+ required
+ fullWidth
+ variant="outlined"
+ color={passwordError ? "error" : "primary"}
+ size="small"
+ />
+ </FormControl>
+ <Button
+ type="submit"
+ fullWidth
+ variant="contained"
+ onClick={validateInputs}
+ >
+ Sign in
+ </Button>
+ </Box>
+ </div>
+ </div>
+ </div>
+ );
+};
+
+export default Login;
diff --git a/app/page.tsx b/app/page.tsx
index 433c8aa..ccfb7f7 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,101 +1,165 @@
+"use client";
import Image from "next/image";
+import PackageCamera from "./lib/camera/component/pakageCamera";
+import BarcodeScanner from "./lib/camera/component/scannerBarcode";
+import SjCamera from "./lib/camera/component/sjCamera";
+import useCameraStore from "./lib/camera/hooks/useCameraStore";
+import Header from "./lib/camera/component/hedear";
+import { Button } from "@mui/material";
+import { SaveAsOutlined } from "@mui/icons-material";
+import axios from "axios";
+import odooApi from "./lib/api/odooApi";
+import { useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+import { getAuth } from "./lib/api/auth";
export default function Home() {
+ const [isLogin, setIsLogin] = useState<boolean>(true);
+ const {
+ barcode,
+ imageSj,
+ imagePackage,
+ setBarcode,
+ setImageSj,
+ setImagePackage,
+ } = useCameraStore();
+ const [isLoading, setIsLoading] = useState<boolean>(false);
+
+ const router = useRouter();
+
+ useEffect(() => {
+ const token = getAuth();
+
+ if (!token) {
+ router.push("/login");
+ } else {
+ setIsLogin(true);
+ }
+ }, [router]);
+
+ const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
+ event.preventDefault();
+ setIsLoading(true);
+
+ if (!barcode || !imageSj || !imagePackage) {
+ alert("Barcode dan gambar harus tersedia.");
+ setIsLoading(false);
+ return;
+ }
+
+ try {
+ const newSjImage = imageSj.replace(/^.*?,/, "");
+ const newPackageImage = imagePackage.replace(/^.*?,/, "");
+ // const method = 'PUT';
+
+ const data = {
+ sj_document: newSjImage, // Kirim base64 lengkap dengan prefix
+ paket_document: newPackageImage, // Kirim base64 lengkap dengan prefix
+ };
+
+ const response = await odooApi(
+ "PUT",
+ `/api/v1/stock-picking/${barcode}/documentation`,
+ data
+ );
+ console.log(response);
+ if (response.status.code == 200) {
+ alert("Berhasil Submit Data");
+ setBarcode("");
+ setImageSj("");
+ setImagePackage("");
+ setIsLoading(false);
+ }
+ return response.data;
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ console.error("Error mengirim data:", error.message);
+ } else if (axios.isAxiosError(error)) {
+ console.error("Error:", error.response?.data);
+ } else {
+ console.error("Unknown error:", error);
+ }
+ setIsLoading(false);
+ }
+ };
+
return (
- <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
- <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
- <Image
- className="dark:invert"
- src="https://nextjs.org/icons/next.svg"
- alt="Next.js logo"
- width={180}
- height={38}
- priority
- />
- <ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
- <li className="mb-2">
- Get started by editing{" "}
- <code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
- app/page.tsx
- </code>
- .
- </li>
- <li>Save and see your changes instantly.</li>
- </ol>
+ <div className="bg-white h-screen overflow-auto">
+ <Header />
+ {isLogin && (
+ <div className="py-4 px-4 sm:px-96 pt-20">
+ <form onSubmit={handleSubmit}>
+ <div>
+ <BarcodeScanner />
+ </div>
+ <div className="h-4"></div>
+
+ <div className="flex justify-between">
+ <SjCamera />
+ <PackageCamera />
+ </div>
+ <div className="h-2"></div>
- <div className="flex gap-4 items-center flex-col sm:flex-row">
- <a
- className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Image
- className="dark:invert"
- src="https://nextjs.org/icons/vercel.svg"
- alt="Vercel logomark"
- width={20}
- height={20}
- />
- Deploy now
- </a>
- <a
- className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
- target="_blank"
- rel="noopener noreferrer"
- >
- Read our docs
- </a>
+ {imageSj && (
+ <>
+ <label className="block mt-2 text-sm font-medium text-gray-700 text-center">
+ Gambar Foto Surat Jalan
+ </label>
+ <div className="relative w-full h-[300px] border-2 border-gray-200 p-2 rounded-sm">
+ <Image
+ src={imageSj}
+ alt="Captured"
+ layout="fill"
+ objectFit="cover"
+ unoptimized
+ className="p-2"
+ />
+ </div>
+ </>
+ )}
+
+ <div className="h-2"></div>
+
+ {imagePackage && (
+ <>
+ <label className="block mt-2 text-sm font-medium text-gray-700 text-center">
+ Gambar Foto Penerima
+ </label>
+ <div className="relative w-full h-[300px] border-2 border-gray-200 p-2 rounded-sm">
+ <Image
+ src={imagePackage}
+ alt="Captured"
+ layout="fill"
+ objectFit="cover"
+ unoptimized
+ className="p-2"
+ />
+ </div>
+ </>
+ )}
+ <div>
+ <div className="h-4"></div>
+ <Button
+ className="w-[50%] sm:w-[25%]"
+ variant="contained"
+ color="error"
+ startIcon={<SaveAsOutlined />}
+ type="submit"
+ disabled={isLoading}
+ >
+ Simpan
+ </Button>
+ </div>
+ </form>
+ </div>
+ )}
+ {!isLogin && (
+ <div className="py-4 px-4 sm:px-96 pt-20">
+ <div className="text-center">
+ <p className="text-2xl font-bold">Loading...</p>
+ </div>
</div>
- </main>
- <footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
- <a
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Image
- aria-hidden
- src="https://nextjs.org/icons/file.svg"
- alt="File icon"
- width={16}
- height={16}
- />
- Learn
- </a>
- <a
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Image
- aria-hidden
- src="https://nextjs.org/icons/window.svg"
- alt="Window icon"
- width={16}
- height={16}
- />
- Examples
- </a>
- <a
- className="flex items-center gap-2 hover:underline hover:underline-offset-4"
- href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
- target="_blank"
- rel="noopener noreferrer"
- >
- <Image
- aria-hidden
- src="https://nextjs.org/icons/globe.svg"
- alt="Globe icon"
- width={16}
- height={16}
- />
- Go to nextjs.org →
- </a>
- </footer>
+ )}
</div>
);
}