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:
@@ -10,6 +10,7 @@ import {
|
||||
getListToolsQueryKey,
|
||||
getListCategoriesQueryKey,
|
||||
getListAllFeaturesQueryKey,
|
||||
getListAllTagsQueryKey,
|
||||
getGetTopToolsQueryKey,
|
||||
getGetAnalyticsSummaryQueryKey,
|
||||
} from "@workspace/api-client-react";
|
||||
@@ -27,6 +28,7 @@ import { Pencil, Plus, X, ArrowLeft } from "lucide-react";
|
||||
import { Link } from "wouter";
|
||||
import { CategoryCombobox } from "@/components/category-combobox";
|
||||
import { FeatureInput } from "@/components/feature-input";
|
||||
import { TagInput } from "@/components/tag-input";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
|
||||
const toolSchema = z.object({
|
||||
@@ -109,6 +111,7 @@ export default function ToolEdit() {
|
||||
queryClient.invalidateQueries({ queryKey: getListToolsQueryKey() });
|
||||
queryClient.invalidateQueries({ queryKey: getListCategoriesQueryKey() });
|
||||
queryClient.invalidateQueries({ queryKey: getListAllFeaturesQueryKey() });
|
||||
queryClient.invalidateQueries({ queryKey: getListAllTagsQueryKey() });
|
||||
queryClient.invalidateQueries({ queryKey: getGetTopToolsQueryKey() });
|
||||
queryClient.invalidateQueries({ queryKey: getGetAnalyticsSummaryQueryKey() });
|
||||
setLocation(`/tools/${id}`);
|
||||
@@ -260,7 +263,7 @@ export default function ToolEdit() {
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Features</h3>
|
||||
<p className="text-sm text-muted-foreground">Key capabilities of this tool.</p>
|
||||
<p className="text-sm text-muted-foreground">Key capabilities of this tool. Existing features from other tools are selectable.</p>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => appendFeature({ value: "" })}>
|
||||
<Plus className="w-4 h-4 mr-2" /> Add Feature
|
||||
@@ -304,7 +307,7 @@ export default function ToolEdit() {
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Tags</h3>
|
||||
<p className="text-sm text-muted-foreground">Keywords for this tool.</p>
|
||||
<p className="text-sm text-muted-foreground">Keywords for this tool. Existing tags from other tools are selectable.</p>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => appendTag({ value: "" })}>
|
||||
<Plus className="w-4 h-4 mr-2" /> Add Tag
|
||||
@@ -317,9 +320,14 @@ export default function ToolEdit() {
|
||||
control={form.control}
|
||||
name={`tags.${index}.value`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-y-0 relative w-[150px]">
|
||||
<FormItem className="flex items-center space-y-0 relative w-[200px]">
|
||||
<FormControl>
|
||||
<Input placeholder="Tag" className="pr-8 h-9 text-sm" {...field} />
|
||||
<TagInput
|
||||
placeholder="Tag"
|
||||
className="pr-8 h-9 text-sm"
|
||||
onChange={field.onChange}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user