diff --git a/apps/web/content/docs/publishing.mdx b/apps/web/content/docs/publishing.mdx index 6908e43..6e2a1c6 100644 --- a/apps/web/content/docs/publishing.mdx +++ b/apps/web/content/docs/publishing.mdx @@ -82,6 +82,13 @@ the external site; it does not enable Modulora checkout or buyer entitlements. Tool listings also appear under the top-level **Tools** catalog category while retaining their more specific subcategory. +To edit a tool, open **Dashboard → Listings** and choose **Edit listing**. The +slug remains fixed, while the verified URL/domain, title, description, +subcategory, external pricing label, and site-thumbnail carousel can change. +Every saved edit returns to usefulness review. The last approved version stays +public until a curator approves the complete edit; requested changes and +rejections remain visible to the creator for revision and resubmission. + ## Sell externally (optional) Direct checkout through Modulora isn't live during alpha. You can link a diff --git a/apps/web/src/components/tool-listing-editor.stories.tsx b/apps/web/src/components/tool-listing-editor.stories.tsx index 4e06fa8..1b3ecec 100644 --- a/apps/web/src/components/tool-listing-editor.stories.tsx +++ b/apps/web/src/components/tool-listing-editor.stories.tsx @@ -32,3 +32,21 @@ export default meta; type Story = StoryObj; export const Default: Story = {}; + +export const Editing: Story = { + args: { + mode: "edit", + initial: { + name: "shieldcn-beautiful-readme-badges", + title: "Shieldcn — Beautiful README Badges", + description: "Beautiful GitHub README badges and charts styled as shadcn/ui, plus a visual README builder.", + category: "utilities", + siteUrl: "https://shieldcn.dev/", + showcaseImageUrls: [previewImage], + pricing: "free", + preview: { canonicalUrl: "https://shieldcn.dev/", title: "Shieldcn", description: "Beautiful README badges.", imageUrl: previewImage }, + editStatus: "changes-requested", + reviewReason: "Update the first screenshot so it matches the current landing page.", + }, + }, +}; diff --git a/apps/web/src/components/tool-listing-editor.tsx b/apps/web/src/components/tool-listing-editor.tsx index 078a068..10e0ea9 100644 --- a/apps/web/src/components/tool-listing-editor.tsx +++ b/apps/web/src/components/tool-listing-editor.tsx @@ -11,7 +11,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { CATEGORIES } from "@/lib/taxonomy"; -import type { ToolListingInput } from "@/lib/tool-listings"; +import type { EditableToolListing, ToolListingInput } from "@/lib/tool-listings"; export interface ToolListingPreview { canonicalUrl: string; @@ -31,6 +31,8 @@ function normalizeHttpsUrl(value: string): string | null { } export interface ToolListingEditorProps { + mode?: "create" | "edit"; + initial?: EditableToolListing; onInspect: (siteUrl: string) => Promise<{ ok: boolean; error?: string; metadata?: ToolListingPreview; verificationDomain?: string }>; onSubmit: (input: ToolListingInput) => Promise<{ ok: boolean; error?: string }>; onSubmitted: () => Promise | void; @@ -39,15 +41,16 @@ export interface ToolListingEditorProps { onDiscoverDomainConnect: (domain: string) => Promise<{ supported: boolean; provider?: string; applyUrl?: string }>; } -export function ToolListingEditor({ onInspect, onSubmit, onSubmitted, onCreateDomain, onVerifyDomain, onDiscoverDomainConnect }: ToolListingEditorProps) { - const [siteUrl, setSiteUrl] = useState(""); - const [name, setName] = useState(""); - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - const [category, setCategory] = useState(CATEGORIES[0].id); - const [pricing, setPricing] = useState("free"); - const [showcaseImageUrls, setShowcaseImageUrls] = useState([]); - const [preview, setPreview] = useState(null); +export function ToolListingEditor({ mode = "create", initial, onInspect, onSubmit, onSubmitted, onCreateDomain, onVerifyDomain, onDiscoverDomainConnect }: ToolListingEditorProps) { + const editing = mode === "edit"; + const [siteUrl, setSiteUrl] = useState(initial?.siteUrl ?? ""); + const [name, setName] = useState(initial?.name ?? ""); + const [title, setTitle] = useState(initial?.title ?? ""); + const [description, setDescription] = useState(initial?.description ?? ""); + const [category, setCategory] = useState(initial?.category ?? CATEGORIES[0].id); + const [pricing, setPricing] = useState(initial?.pricing ?? "free"); + const [showcaseImageUrls, setShowcaseImageUrls] = useState(initial?.showcaseImageUrls ?? []); + const [preview, setPreview] = useState(initial?.preview ?? null); const [inspecting, setInspecting] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); @@ -158,9 +161,10 @@ export function ToolListingEditor({ onInspect, onSubmit, onSubmitted, onCreateDo
-

Tool or site details

-

Only owner-authorized sites on a domain verified in Settings may be submitted.

+

{editing ? "Edit tool or site" : "Tool or site details"}

+

Only owner-authorized sites on a domain verified in Settings may be submitted. {editing ? "Every saved edit returns to curator review while the approved version stays public." : null}

+ {initial?.editStatus ?
Edit {initial.editStatus === "pending" ? "in review" : initial.editStatus.replace("-", " ")}.{initial.reviewReason ? ` ${initial.reviewReason}` : " You can revise and resubmit this draft."}
: null}
@@ -169,7 +173,7 @@ export function ToolListingEditor({ onInspect, onSubmit, onSubmitted, onCreateDo
setTitle(event.target.value)} maxLength={120} required />
-
setName(event.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""))} maxLength={64} required />

Used in the Modulora detail URL.

+
setName(event.target.value.toLowerCase().replace(/[^a-z0-9-]/g, ""))} maxLength={64} required />

{editing ? "The public listing URL stays fixed." : "Used in the Modulora detail URL."}