summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorMiqdad <ahmadmiqdad27@gmail.com>2025-09-30 22:42:46 +0700
committerMiqdad <ahmadmiqdad27@gmail.com>2025-09-30 22:42:46 +0700
commit011c01a741f23734e4154342e9a560925687f152 (patch)
treed874ad11b52eb9749a53af5d90958e3a76f219c0 /app
parent352000c186a2765416cd2fab6693e68bb62e9e53 (diff)
<Miqdad> No auto loginrole
Diffstat (limited to 'app')
-rw-r--r--app/lib/api/clearOdooSession.ts25
-rw-r--r--app/login/page.tsx45
-rw-r--r--app/page.tsx128
3 files changed, 114 insertions, 84 deletions
diff --git a/app/lib/api/clearOdooSession.ts b/app/lib/api/clearOdooSession.ts
new file mode 100644
index 0000000..0ad9a5d
--- /dev/null
+++ b/app/lib/api/clearOdooSession.ts
@@ -0,0 +1,25 @@
+export async function clearOdooSession(baseUrl: string) {
+ try {
+ if (baseUrl) {
+ await fetch(`${baseUrl}/web/session/destroy`, {
+ method: "POST",
+ credentials: "include",
+ headers: { "Content-Type": "application/json" },
+ body: "{}",
+ });
+ }
+ } catch { }
+
+ // 2) hapus cookie session_id di browser
+ try {
+ const del = (name: string, domain?: string) => {
+ const d = domain ? `; domain=${domain}` : "";
+ document.cookie = `${name}=; Max-Age=0; Path=/${d}`;
+ document.cookie = `${name}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/${d}`;
+ };
+ const host = window.location.hostname.replace(/^www\./, "");
+ const parts = host.split(".");
+ const parent = parts.length >= 2 ? `.${parts.slice(-2).join(".")}` : undefined;
+ [undefined, host, parent].forEach(dom => del("session_id", dom));
+ } catch { }
+}
diff --git a/app/login/page.tsx b/app/login/page.tsx
index c0d7ab8..b4b0ed0 100644
--- a/app/login/page.tsx
+++ b/app/login/page.tsx
@@ -16,7 +16,7 @@ import { getAuth, setAuth } from "../lib/api/auth";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { setCookie } from "cookies-next";
-
+import { clearOdooSession } from "../lib/api/clearOdooSession";
// Ambil tipe parameter untuk setAuth agar sesuai tepat dengan definisinya
type AuthProps = Parameters<typeof setAuth>[0];
@@ -49,10 +49,16 @@ const Login = () => {
const [loading, setLoading] = useState(false);
useEffect(() => {
+ void clearOdooSession(process.env.NEXT_PUBLIC_ODOO_API_HOST ?? "");
const token = getAuth();
- if (token) router.push("/");
+ if (token) router.replace("/");
}, [router]);
+ // useEffect(() => {
+ // const token = getAuth();
+ // if (token) router.push("/");
+ // }, [router]);
+
const validateInputs = (e: string, p: string, r: "" | Role) => {
let ok = true;
@@ -105,7 +111,8 @@ const Login = () => {
const rawRole = fd.get("role");
const emailStr = typeof rawEmail === "string" ? rawEmail.trim() : "";
const passwordStr = typeof rawPassword === "string" ? rawPassword : "";
- const roleStr: "" | Role = rawRole === "driver" || rawRole === "dispatch" ? rawRole : "";
+ const roleStr: "" | Role =
+ rawRole === "driver" || rawRole === "dispatch" ? rawRole : "";
if (!validateInputs(emailStr, passwordStr, roleStr)) return;
@@ -125,9 +132,8 @@ const Login = () => {
if (auth.user && typeof auth.user === "object") {
setAuth(auth.user as AuthProps);
}
- // Simpan pilihan role agar bisa dipakai di halaman lain
- setCookie("web_role", roleStr, { path: "/" });
- router.push("/");
+ setCookie("web_role", roleStr, { path: "/", sameSite: "lax" });
+ router.replace("/");
return;
}
@@ -140,7 +146,9 @@ const Login = () => {
alert("Akun anda belum aktif");
break;
default:
- alert(res?.status?.description || "Login gagal. Periksa email/password.");
+ alert(
+ res?.status?.description || "Login gagal. Periksa email/password."
+ );
}
} catch (error) {
console.error(error);
@@ -158,7 +166,11 @@ const Login = () => {
<Typography
component="h1"
variant="h4"
- sx={{ width: "100%", fontSize: "clamp(2rem, 10vw, 2.15rem)", mb: 4 }}
+ sx={{
+ width: "100%",
+ fontSize: "clamp(2rem, 10vw, 2.15rem)",
+ mb: 4,
+ }}
>
Sign in
</Typography>
@@ -167,13 +179,19 @@ const Login = () => {
component="form"
onSubmit={handleSubmit}
noValidate
- sx={{ display: "flex", flexDirection: "column", width: "100%", gap: 2 }}
+ sx={{
+ display: "flex",
+ flexDirection: "column",
+ width: "100%",
+ gap: 2,
+ }}
>
<FormControl>
<FormLabel htmlFor="email">Email</FormLabel>
<TextField
error={emailError}
helperText={emailErrorMessage}
+ disabled={loading}
id="email"
type="email"
name="email"
@@ -198,6 +216,7 @@ const Login = () => {
<TextField
error={passwordError}
helperText={passwordErrorMessage}
+ disabled={loading}
name="password"
placeholder="••••••"
type="password"
@@ -220,6 +239,7 @@ const Login = () => {
id="role"
name="role"
value={role}
+ disabled={loading}
onChange={handleRoleChange}
displayEmpty
size="small"
@@ -237,7 +257,12 @@ const Login = () => {
)}
</FormControl>
- <Button type="submit" fullWidth variant="contained" disabled={loading}>
+ <Button
+ type="submit"
+ fullWidth
+ variant="contained"
+ disabled={loading}
+ >
{loading ? "Loading..." : "Sign in"}
</Button>
</Box>
diff --git a/app/page.tsx b/app/page.tsx
index add141f..af5ada3 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -6,7 +6,14 @@ import SjCamera from "./lib/camera/component/sjCamera";
import DispatchCamera from "./lib/camera/component/dispatchCamera";
import useCameraStore from "./lib/camera/hooks/useCameraStore";
import Header from "./lib/camera/component/hedear";
-import { Button, FormControl, InputLabel, MenuItem, Select, FormHelperText } from "@mui/material";
+import {
+ Button,
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ FormHelperText,
+} from "@mui/material";
import { SaveAsOutlined } from "@mui/icons-material";
import axios from "axios";
import odooApi from "./lib/api/odooApi";
@@ -14,18 +21,18 @@ import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { getAuth } from "./lib/api/auth";
import { getCookie } from "cookies-next";
+import { clearOdooSession } from "./lib/api/clearOdooSession";
type Role = "driver" | "dispatch";
type ShipMethod = "" | "self_pickup" | "indoteknik_delivery" | "ekspedisi";
export default function Home() {
- const [isLogin, setIsLogin] = useState<boolean>(true);
+ const [isLogin, setIsLogin] = useState<boolean>(false); // start false biar nggak nge-flash
const [isDriver, setIsDriver] = useState<boolean>(false);
const [isDispatch, setIsDispatch] = useState<boolean>(false);
const [shippingMethod, setShippingMethod] = useState<ShipMethod>("");
const [shipTouched, setShipTouched] = useState(false);
-
const {
barcode,
imageSj,
@@ -40,18 +47,20 @@ export default function Home() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const router = useRouter();
+ // Single effect: auth gate + set role
useEffect(() => {
const auth = getAuth();
if (!auth) {
- router.push("/login");
+ void clearOdooSession(process.env.NEXT_PUBLIC_ODOO_API_HOST ?? "");
+ router.replace("/login");
return;
}
- const roleCookie = (getCookie("web_role") as string | undefined)?.toLowerCase() as Role | undefined;
- const role: Role | null = roleCookie === "driver" || roleCookie === "dispatch" ? roleCookie : null;
-
+ const roleCookie = (
+ getCookie("web_role") as string | undefined
+ )?.toLowerCase() as Role | undefined;
+ setIsDriver(roleCookie === "driver");
+ setIsDispatch(roleCookie === "dispatch");
setIsLogin(true);
- setIsDriver(role === "driver");
- setIsDispatch(role === "dispatch");
}, [router]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
@@ -72,7 +81,6 @@ export default function Home() {
return;
}
-
// Validasi foto sesuai role & shipping method
if (isDispatch) {
if (!imageDispatch) {
@@ -80,7 +88,7 @@ export default function Home() {
setIsLoading(false);
return;
}
- // SJ opsional untuk self_pickup & ekspedisi → tidak divalidasi
+ // SJ opsional untuk self_pickup & ekspedisi
} else {
// Driver: SJ & Penerima wajib
if (!imageSj || !imagePackage) {
@@ -92,7 +100,9 @@ export default function Home() {
try {
const newSjImage = imageSj ? imageSj.replace(/^.*?,/, "") : undefined;
- const newPackageImage = imagePackage ? imagePackage.replace(/^.*?,/, "") : undefined;
+ const newPackageImage = imagePackage
+ ? imagePackage.replace(/^.*?,/, "")
+ : undefined;
const newDispatchImage =
imageDispatch && imageDispatch.startsWith("data:")
? imageDispatch.replace(/^.*?,/, "")
@@ -107,7 +117,6 @@ export default function Home() {
if (submittedSj) data.sj_document = newSjImage!;
if (submittedPackage) data.paket_document = newPackageImage!;
if (submittedDispatch) data.dispatch_document = newDispatchImage!;
- // (opsional) kirim shippingMethod jika backend mau simpan
if (isDispatch && shippingMethod) data.shipping_method = shippingMethod;
const response = (await odooApi(
@@ -141,23 +150,23 @@ export default function Home() {
}
};
+ // === UI helpers ===
// dispatch: SJ hanya utk self_pickup & ekspedisi
const showSjForDispatch =
- isDispatch && (shippingMethod === "self_pickup" || shippingMethod === "ekspedisi");
-
+ isDispatch &&
+ (shippingMethod === "self_pickup" || shippingMethod === "ekspedisi");
// dispatch: kamera dispatch tampil utk semua method (asal sudah dipilih)
const showDispatchForDispatch = isDispatch && shippingMethod !== "";
-
// preview SJ: sembunyikan kalau dispatch belum pilih method
const showSjPreview = !!imageSj && (!isDispatch || showSjForDispatch);
-
// preview Dispatch: sembunyikan kalau dispatch belum pilih method
- const showDispatchPreview = !!imageDispatch && (!isDispatch || showDispatchForDispatch);
+ const showDispatchPreview =
+ !!imageDispatch && (!isDispatch || showDispatchForDispatch);
return (
<div className="bg-white h-screen overflow-auto">
<Header />
- {isLogin && (
+ {isLogin ? (
<div className="py-4 px-4 sm:px-96 pt-20">
<form onSubmit={handleSubmit}>
<div>
@@ -167,19 +176,32 @@ export default function Home() {
{/* Shipping Method (khusus dispatch) */}
{isDispatch && (
<div className="mt-4">
- <FormControl fullWidth size="small" required error={shipTouched && !shippingMethod}>
- {/* shrink = kunci agar label tidak tumpuk dengan placeholder */}
- <InputLabel id="shipping-label" shrink>Shipping Method</InputLabel>
+ <FormControl
+ fullWidth
+ size="small"
+ required
+ error={shipTouched && !shippingMethod}
+ >
+ <InputLabel id="shipping-label" shrink>
+ Shipping Method
+ </InputLabel>
<Select
labelId="shipping-label"
id="shipping"
label="Shipping Method"
value={shippingMethod}
displayEmpty
- onChange={(e) => setShippingMethod(e.target.value as ShipMethod)}
+ onChange={(e) =>
+ setShippingMethod(e.target.value as ShipMethod)
+ }
onBlur={() => setShipTouched(true)}
renderValue={(selected) => {
- if (!selected) return <span style={{ opacity: 0.6 }}>Pilih Shipping Method</span>;
+ if (!selected)
+ return (
+ <span style={{ opacity: 0.6 }}>
+ Pilih Shipping Method
+ </span>
+ );
const map: Record<string, string> = {
self_pickup: "Self Pickup",
indoteknik_delivery: "Indoteknik Delivery",
@@ -192,17 +214,20 @@ export default function Home() {
<em>Pilih Shipping Method</em>
</MenuItem>
<MenuItem value="self_pickup">Self Pickup</MenuItem>
- <MenuItem value="indoteknik_delivery">Indoteknik Delivery</MenuItem>
+ <MenuItem value="indoteknik_delivery">
+ Indoteknik Delivery
+ </MenuItem>
<MenuItem value="ekspedisi">Ekspedisi</MenuItem>
</Select>
{shipTouched && !shippingMethod && (
- <FormHelperText>Wajib pilih shipping method.</FormHelperText>
+ <FormHelperText>
+ Wajib pilih shipping method.
+ </FormHelperText>
)}
</FormControl>
</div>
)}
-
<div className="h-4" />
<div className="flex justify-between">
@@ -210,19 +235,16 @@ export default function Home() {
<>
{showSjForDispatch && <SjCamera />}
{showDispatchForDispatch && <DispatchCamera />}
- {/* dispatch TIDAK perlu kamera penerima */}
</>
) : (
<>
{/* driver / non-dispatch */}
<SjCamera />
<PackageCamera />
- {/* driver tidak menampilkan kamera dispatch */}
</>
)}
</div>
-
<div className="h-2" />
{/* Preview SJ */}
@@ -271,48 +293,7 @@ export default function Home() {
</label>
<div className="relative w-full h-[300px] border-2 border-gray-200 p-2 rounded-sm">
<Image
- src={imageDispatch}
- alt="Captured"
- fill
- unoptimized
- className="p-2"
- style={{ objectFit: "cover" }}
- />
- </div>
- </>
- )}
-
-
- <div className="h-2" />
-
- {!isDispatch && 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"
- fill
- unoptimized
- className="p-2"
- style={{ objectFit: "cover" }}
- />
- </div>
- </>
- )}
-
- <div className="h-2" />
-
- {!isDriver && imageDispatch && (
- <>
- <label className="block mt-2 text-sm font-medium text-gray-700 text-center">
- Gambar Foto Dispatch
- </label>
- <div className="relative w-full h-[300px] border-2 border-gray-200 p-2 rounded-sm">
- <Image
- src={imageDispatch}
+ src={imageDispatch!}
alt="Captured"
fill
unoptimized
@@ -336,8 +317,7 @@ export default function Home() {
</Button>
</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>