2026-04-09 12:01:08 +05:30
|
|
|
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";
|
2026-08-31 20:05:33 -04:00
|
|
|
import { buildPlanCreatePayload } from "../buildPlanPayload";
|
2026-04-09 12:01:08 +05:30
|
|
|
|
|
|
|
|
const AddSubscriptions = () => {
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
|
|
|
|
const [formData, setFormData] = useState<SubscriptionPlanCreateRequest>({
|
|
|
|
|
name: "",
|
|
|
|
|
description: "",
|
|
|
|
|
price: undefined,
|
2026-04-18 16:31:52 +05:30
|
|
|
duration_days: undefined,
|
2026-04-29 11:45:50 +03:00
|
|
|
max_users_allowed: undefined,
|
2026-08-31 20:05:33 -04:00
|
|
|
grace_period_days: 0,
|
2026-04-09 12:01:08 +05:30
|
|
|
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 {
|
2026-08-31 20:05:33 -04:00
|
|
|
const payload = buildPlanCreatePayload(
|
|
|
|
|
formData,
|
|
|
|
|
selectedAccessIds,
|
|
|
|
|
allAccesses
|
|
|
|
|
);
|
2026-04-09 12:01:08 +05:30
|
|
|
|
|
|
|
|
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>
|
2026-04-25 08:23:16 +03:00
|
|
|
<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>
|
2026-04-09 12:01:08 +05:30
|
|
|
</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,
|
2026-04-18 16:31:52 +05:30
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<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,
|
2026-04-29 11:45:50 +03:00
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<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,
|
2026-04-09 12:01:08 +05:30
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
/>
|
2026-08-31 20:05:33 -04:00
|
|
|
<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,
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
/>
|
2026-04-09 12:01:08 +05:30
|
|
|
</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;
|