feat: tiered costs feature + admin tier management + tag selection
Build & Push Docker Image / build (push) Successful in 6m52s
Build & Push Docker Image / build (push) Successful in 6m52s
- costs: nullable notes (fix create without notes), drop renewalDate (schema + API + UI), gate POST/PATCH/DELETE to admin + costs feature - feature middleware: admin-aware hasFeature + getEntitlements union; /auth/me and login return resolved entitlements - users: tier enum (free/premium/enterprise) in create/update/list, admin UI tier select + tier badge - tags: GET /tags/all, TagInput autocomplete in new/edit tool forms, feature suggestions on focus, query invalidation on create/update - openapi: nullable ToolUpdate urls, ToolUpdate tier fields, listAllTags - Dockerfile: push-force to drop renewal_date column
This commit is contained in:
@@ -91,7 +91,6 @@ export default function ToolDetail() {
|
||||
const [costBillingPeriod, setCostBillingPeriod] = useState("monthly");
|
||||
const [costAmount, setCostAmount] = useState("");
|
||||
const [costCurrency, setCostCurrency] = useState("EUR");
|
||||
const [costRenewal, setCostRenewal] = useState("");
|
||||
const [costNotes, setCostNotes] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -150,7 +149,6 @@ export default function ToolDetail() {
|
||||
notes: costNotes || null,
|
||||
};
|
||||
if (costAmount) body.cost = costAmount;
|
||||
if (costRenewal) body.renewalDate = costRenewal;
|
||||
try {
|
||||
await customFetch(url, { method, body: JSON.stringify(body) });
|
||||
toast({ title: editCost ? "Cost updated" : "Cost added" });
|
||||
@@ -168,7 +166,6 @@ export default function ToolDetail() {
|
||||
setCostBillingPeriod("monthly");
|
||||
setCostAmount("");
|
||||
setCostCurrency("EUR");
|
||||
setCostRenewal("");
|
||||
setCostNotes("");
|
||||
}
|
||||
|
||||
@@ -178,7 +175,6 @@ export default function ToolDetail() {
|
||||
setCostBillingPeriod(c.billingPeriod ?? "monthly");
|
||||
setCostAmount(c.cost ?? "");
|
||||
setCostCurrency(c.currency ?? "EUR");
|
||||
setCostRenewal(c.renewalDate ?? "");
|
||||
setCostNotes(c.notes ?? "");
|
||||
setCostDialogOpen(true);
|
||||
}
|
||||
@@ -562,11 +558,6 @@ export default function ToolDetail() {
|
||||
<div className="text-lg font-bold">
|
||||
{c.cost != null ? `${c.cost} ${c.currency ?? ""}` : "Free"}
|
||||
</div>
|
||||
{c.renewalDate && (
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Renews: {new Date(c.renewalDate).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
{c.notes && <p className="text-xs text-muted-foreground mt-1 italic">{c.notes}</p>}
|
||||
</div>
|
||||
{isAdmin && (
|
||||
@@ -641,10 +632,6 @@ export default function ToolDetail() {
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Renewal Date</label>
|
||||
<Input type="date" value={costRenewal} onChange={(e) => setCostRenewal(e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Notes</label>
|
||||
<Textarea placeholder="Billing details, contract info..." value={costNotes} onChange={(e) => setCostNotes(e.target.value)} />
|
||||
|
||||
Reference in New Issue
Block a user