192 lines
6.5 KiB
TypeScript
192 lines
6.5 KiB
TypeScript
import { PlusIcon } from "lucide-react";
|
|
import { useState, type FormEvent } from "react";
|
|
import { cn } from "../../utils/classname";
|
|
import StateSection from "./sections/state";
|
|
import CommandSection from "./sections/command";
|
|
import { useCreateTopic } from "./hooks/mutations";
|
|
import { getMerchant } from "../../utils/storage";
|
|
import { ENV } from "../../constants/env";
|
|
|
|
export default function TopicsFeature() {
|
|
const [tab, setTab] = useState("state");
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [form, setForm] = useState<TopicPayload>({
|
|
topic: "",
|
|
type: "state-reply",
|
|
});
|
|
const [error, setError] = useState("");
|
|
const createTopic = useCreateTopic();
|
|
const merchant = getMerchant();
|
|
const expectedCredential = ENV.credential?.trim();
|
|
const hasCredential = Boolean(merchant?.credential?.trim());
|
|
const isCredentialMatch =
|
|
Boolean(expectedCredential) &&
|
|
merchant?.credential?.trim() === expectedCredential;
|
|
const isMerchantSetup =
|
|
Boolean(merchant?.merchantName?.trim()) &&
|
|
Boolean(merchant?.tower?.trim()) &&
|
|
Boolean(merchant?.floor?.trim()) &&
|
|
Boolean(merchant?.unit?.trim());
|
|
const isReady = isMerchantSetup && hasCredential && isCredentialMatch;
|
|
|
|
const handleOpen = () => {
|
|
setError("");
|
|
setForm({
|
|
topic: "",
|
|
type: tab === "state" ? "state-reply" : "commands",
|
|
});
|
|
setIsOpen(true);
|
|
};
|
|
|
|
const handleClose = () => {
|
|
if (createTopic.isPending) return;
|
|
setIsOpen(false);
|
|
};
|
|
|
|
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
const topic = form.topic.trim();
|
|
if (!topic) {
|
|
setError("Topic wajib diisi.");
|
|
return;
|
|
}
|
|
|
|
createTopic.mutate(
|
|
{ topic, type: form.type },
|
|
{
|
|
onSuccess: () => {
|
|
setIsOpen(false);
|
|
setForm({
|
|
topic: "",
|
|
type: tab === "state" ? "state-reply" : "commands",
|
|
});
|
|
},
|
|
onError: () => {
|
|
setError("Gagal menambah topic.");
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
{!isReady && (
|
|
<div className="mx-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700">
|
|
{!isMerchantSetup && (
|
|
<span>
|
|
Silakan setup merchant terlebih dahulu di halaman Settings sebelum
|
|
mengakses Topics.
|
|
</span>
|
|
)}
|
|
{isMerchantSetup && !hasCredential && (
|
|
<span>
|
|
Silakan isi credential terlebih dahulu di halaman Settings sebelum
|
|
mengakses Topics.
|
|
</span>
|
|
)}
|
|
{isMerchantSetup && hasCredential && !isCredentialMatch && (
|
|
<span>Credential tidak sesuai.</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="p-4 flex justify-end">
|
|
<button
|
|
onClick={handleOpen}
|
|
className="shrink-0 px-4 py-2 rounded-md flex items-center justify-center transition-all bg-orange-500 text-white disabled:cursor-not-allowed disabled:opacity-60"
|
|
disabled={!isReady}
|
|
>
|
|
<PlusIcon />
|
|
Tambah Topic
|
|
</button>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<button
|
|
onClick={() => setTab("state")}
|
|
className={cn(
|
|
"w-full text-center p-x2 py-4 border-b-2 border-transparent",
|
|
tab === "state" && "border-amber-500",
|
|
)}
|
|
>
|
|
state-reply
|
|
</button>
|
|
<button
|
|
onClick={() => setTab("command")}
|
|
className={cn(
|
|
"w-full text-center p-x2 py-4 border-b-2 border-transparent",
|
|
tab === "command" && "border-amber-500",
|
|
)}
|
|
>
|
|
commands
|
|
</button>
|
|
</div>
|
|
{tab === "state" && <StateSection />}
|
|
{tab === "command" && <CommandSection />}
|
|
{isOpen && (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
|
|
<div className="w-full max-w-md rounded-md bg-white p-4 shadow-lg">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-lg font-semibold">Tambah Topic</h2>
|
|
<button
|
|
onClick={handleClose}
|
|
className="text-sm text-neutral-500"
|
|
type="button"
|
|
>
|
|
Tutup
|
|
</button>
|
|
</div>
|
|
<form onSubmit={handleSubmit} className="mt-4 flex flex-col gap-3">
|
|
<label className="text-sm font-medium text-neutral-700">
|
|
Topic
|
|
<input
|
|
className="mt-1 w-full rounded-md border border-neutral-300 px-3 py-2 text-sm outline-none focus:border-amber-500"
|
|
placeholder="Masukkan topic"
|
|
value={form.topic}
|
|
onChange={(event) =>
|
|
setForm((prev) => ({ ...prev, topic: event.target.value }))
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="text-sm font-medium text-neutral-700">
|
|
Type
|
|
<select
|
|
className="mt-1 w-full rounded-md border border-neutral-300 px-3 py-2 text-sm outline-none focus:border-amber-500"
|
|
value={form.type}
|
|
onChange={(event) =>
|
|
setForm((prev) => ({
|
|
...prev,
|
|
type: event.target.value as TopicPayload["type"],
|
|
}))
|
|
}
|
|
>
|
|
<option value="state-reply">state-reply</option>
|
|
<option value="commands">commands</option>
|
|
</select>
|
|
</label>
|
|
{error && (
|
|
<p className="text-sm text-red-500">{error}</p>
|
|
)}
|
|
<div className="mt-2 flex justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
onClick={handleClose}
|
|
className="rounded-md border border-neutral-300 px-4 py-2 text-sm"
|
|
disabled={createTopic.isPending}
|
|
>
|
|
Batal
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="rounded-md bg-amber-500 px-4 py-2 text-sm text-white disabled:cursor-not-allowed disabled:opacity-70"
|
|
disabled={createTopic.isPending}
|
|
>
|
|
{createTopic.isPending ? "Menyimpan..." : "Simpan"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|