Files
saas_frontend/src/application/subscriptions/components/AddSubscriptions.tsx
T
2026-08-31 20:40:20 -04:00

252 lines
7.3 KiB
TypeScript

import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import {
CustomButton,
CustomCheckBox,
CustomDropdown,
CustomInput,
CustomBackButton,
} from "../../../components/custom";
import { subscriptionsApi } from "../SubscriptionsApi";
import type { SubscriptionPlanCreateRequest } from "../SubscriptionTypes";
import type { RoleAccess } from "../../roles/RolesTypes";
import { GroupedAccessSelector } from "../../roles/components/GroupedAccessSelector";
import { buildPlanCreatePayload } from "../buildPlanPayload";
const AddSubscriptions = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState<SubscriptionPlanCreateRequest>({
name: "",
description: "",
price: undefined,
duration_days: undefined,
max_users_allowed: undefined,
grace_period_days: 0,
is_public: true,
status: "active",
access_ids: [],
module_access_ids: [],
});
const [allAccesses, setAllAccesses] = useState<RoleAccess[]>([]);
const [selectedAccessIds, setSelectedAccessIds] = useState<string[]>([]);
const [isAccessLoading, setIsAccessLoading] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
useEffect(() => {
let isMounted = true;
const loadAccesses = async () => {
setIsAccessLoading(true);
try {
const data = await subscriptionsApi.getAccesses();
if (isMounted) {
setAllAccesses(data);
}
} catch (error) {
if (isMounted) {
const message =
error instanceof Error
? error.message
: "Unable to load access options.";
setErrorMessage(message);
}
} finally {
if (isMounted) {
setIsAccessLoading(false);
}
}
};
loadAccesses();
return () => {
isMounted = false;
};
}, []);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setErrorMessage("");
setIsLoading(true);
try {
const payload = buildPlanCreatePayload(
formData,
selectedAccessIds,
allAccesses
);
await subscriptionsApi.create(payload);
navigate("/subscriptions");
} catch (error) {
const message =
error instanceof Error
? error.message
: "Unable to create subscription plan.";
setErrorMessage(message);
} finally {
setIsLoading(false);
}
};
return (
<div className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex items-center gap-4">
<CustomBackButton to="/subscriptions" tooltip="Back to Subscriptions" />
<div>
<div className="flex flex-col gap-1">
<h1 className="text-2xl font-bold text-[var(--text-primary)]">
Add Subscription Plan
</h1>
<p className="text-sm text-[var(--text-secondary)]">
Create a new plan with pricing and access permissions.
</p>
</div>
</div>
</div>
</div>
<form
onSubmit={handleSubmit}
className="space-y-6 rounded-lg border border-gray-200 bg-white p-6"
>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
<CustomInput
label="Plan Name"
name="name"
placeholder="Enter plan name"
value={formData.name}
onChange={handleChange}
required
/>
<CustomInput
label="Price"
name="price"
type="number"
placeholder="0.00"
value={formData.price ?? ""}
onChange={(e) =>
setFormData((prev) => ({
...prev,
price: e.target.value ? Number(e.target.value) : undefined,
}))
}
/>
<CustomInput
label="Duration (Days)"
name="duration_days"
type="number"
placeholder="30"
value={formData.duration_days ?? ""}
onChange={(e) =>
setFormData((prev) => ({
...prev,
duration_days: e.target.value ? Number(e.target.value) : undefined,
}))
}
/>
<CustomInput
label="Max Allowed Users"
name="max_users_allowed"
type="number"
placeholder="Unlimited"
value={formData.max_users_allowed ?? ""}
onChange={(e) =>
setFormData((prev) => ({
...prev,
max_users_allowed: e.target.value ? Number(e.target.value) : undefined,
}))
}
/>
<CustomInput
label="Grace Period (Days)"
name="grace_period_days"
type="number"
placeholder="0"
value={formData.grace_period_days ?? ""}
onChange={(e) =>
setFormData((prev) => ({
...prev,
grace_period_days: e.target.value ? Number(e.target.value) : 0,
}))
}
/>
</div>
<CustomInput
label="Description"
name="description"
placeholder="Enter plan description"
value={formData.description ?? ""}
onChange={handleChange}
/>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
<CustomDropdown
label="Status"
name="status"
value={formData.status ?? "active"}
onChange={(e) =>
setFormData((prev) => ({ ...prev, status: e.target.value }))
}
options={[
{ label: "Active", value: "active" },
{ label: "Inactive", value: "inactive" },
]}
/>
<div className="flex items-end pb-1">
<CustomCheckBox
label="Publicly Visible"
name="is_public"
checked={formData.is_public ?? true}
onChange={(e) =>
setFormData((prev) => ({
...prev,
is_public: e.target.checked,
}))
}
/>
</div>
</div>
<div className="border-t border-gray-200 pt-6">
<GroupedAccessSelector
allAccesses={allAccesses}
selectedIds={selectedAccessIds}
onChange={setSelectedAccessIds}
isLoading={isAccessLoading}
title="Plan Permissions"
/>
</div>
{errorMessage && (
<div className="rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-600">
{errorMessage}
</div>
)}
<div className="flex justify-end pt-4">
<CustomButton
type="submit"
variant="primary"
disabled={isLoading}
loading={isLoading}
>
Create Plan
</CustomButton>
</div>
</form>
</div>
);
};
export default AddSubscriptions;