mirror of
https://github.com/homarr-labs/dashboard-icons.git
synced 2026-01-12 16:25:38 +08:00
Allow editing existing icons
Co-authored-by: ajnart <49837342+ajnart@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,14 @@ import { BASE_URL, WEB_URL } from "@/constants"
|
||||
import { getAllIcons, getAuthorData } from "@/lib/api"
|
||||
import { getCommunityGalleryRecord, getCommunitySubmissionByName, getCommunitySubmissions } from "@/lib/community"
|
||||
|
||||
function isIconAddedToCollection(
|
||||
record: Awaited<ReturnType<typeof getCommunityGalleryRecord>>,
|
||||
collectionIcons: Record<string, unknown>,
|
||||
icon: string,
|
||||
) {
|
||||
return record?.status === "added_to_collection" && Object.hasOwn(collectionIcons, icon)
|
||||
}
|
||||
|
||||
export const dynamicParams = true
|
||||
export const revalidate = 21600 // 6 hours
|
||||
export const dynamic = "force-static"
|
||||
@@ -30,7 +38,10 @@ export async function generateMetadata({ params }: Props, _parent: ResolvingMeta
|
||||
}
|
||||
|
||||
const record = await getCommunityGalleryRecord(icon)
|
||||
if (record?.status === "added_to_collection") {
|
||||
const collectionIcons = await getAllIcons()
|
||||
const isInCollection = isIconAddedToCollection(record, collectionIcons, icon)
|
||||
|
||||
if (isInCollection) {
|
||||
permanentRedirect(`/icons/${icon}`)
|
||||
}
|
||||
|
||||
@@ -118,12 +129,12 @@ export default async function CommunityIconPage({ params }: { params: Promise<{
|
||||
}
|
||||
|
||||
const record = await getCommunityGalleryRecord(icon)
|
||||
if (record?.status === "added_to_collection") {
|
||||
const allIcons = await getAllIcons()
|
||||
const isInCollection = isIconAddedToCollection(record, allIcons, icon)
|
||||
if (isInCollection) {
|
||||
permanentRedirect(`/icons/${icon}`)
|
||||
}
|
||||
|
||||
const allIcons = await getAllIcons()
|
||||
|
||||
const author = iconData.data.update.author as any
|
||||
const githubId = author?.github_id
|
||||
const authorMetaLogin = author?.login || author?.name
|
||||
|
||||
@@ -27,7 +27,6 @@ import { MultiSelect, type MultiSelectOption } from "@/components/ui/multi-selec
|
||||
import { Dropzone, DropzoneContent, DropzoneEmptyState } from "@/components/ui/shadcn-io/dropzone"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { REPO_PATH } from "@/constants"
|
||||
import { useExistingIconNames } from "@/hooks/use-submissions"
|
||||
import { pb } from "@/lib/pb"
|
||||
|
||||
interface VariantConfig {
|
||||
@@ -112,7 +111,6 @@ interface FormData {
|
||||
export function AdvancedIconSubmissionFormTanStack() {
|
||||
const [filePreviews, setFilePreviews] = useState<Record<string, string>>({})
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false)
|
||||
const { data: existingIcons = [] } = useExistingIconNames()
|
||||
const router = useRouter()
|
||||
|
||||
const form = useForm({
|
||||
@@ -383,11 +381,6 @@ export function AdvancedIconSubmissionFormTanStack() {
|
||||
if (!/^[a-z0-9-]+$/.test(value)) {
|
||||
return "Icon name must contain only lowercase letters, numbers, and hyphens"
|
||||
}
|
||||
// Check if icon already exists
|
||||
const iconExists = existingIcons.some((icon) => icon.value === value)
|
||||
if (iconExists) {
|
||||
return "This icon already exists. Icon updates are not yet supported. Please choose a different name."
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
}}
|
||||
@@ -676,7 +669,7 @@ export function AdvancedIconSubmissionFormTanStack() {
|
||||
>
|
||||
{(state) => (
|
||||
<Button type="submit" disabled={!state.canSubmit || state.isSubmitting} size="lg">
|
||||
{state.isSubmitting ? "Submitting..." : "Submit New Icon"}
|
||||
{state.isSubmitting ? "Submitting..." : "Submit Icon"}
|
||||
</Button>
|
||||
)}
|
||||
</form.Subscribe>
|
||||
|
||||
@@ -65,7 +65,7 @@ export function IconNameCombobox({ value, onValueChange, error, isInvalid }: Ico
|
||||
// Delay to allow clicking on suggestions
|
||||
setTimeout(() => setIsFocused(false), 200)
|
||||
}}
|
||||
placeholder="Type new icon ID (e.g., my-app)..."
|
||||
placeholder="Type icon ID (new or existing, e.g., my-app)..."
|
||||
className={cn("font-mono", isInvalid && "border-destructive focus-visible:ring-destructive/50")}
|
||||
aria-invalid={isInvalid}
|
||||
aria-describedby={error ? "icon-name-error" : undefined}
|
||||
@@ -77,7 +77,7 @@ export function IconNameCombobox({ value, onValueChange, error, isInvalid }: Ico
|
||||
<Command className="rounded-md">
|
||||
<CommandList className="max-h-[300px] overflow-y-auto">
|
||||
<CommandEmpty>No existing icons found</CommandEmpty>
|
||||
<CommandGroup heading={`⚠️ Existing Icons (${filteredIcons.length} matches - Not Allowed)`}>
|
||||
<CommandGroup heading={`Existing Icons (${filteredIcons.length} matches)`}>
|
||||
{filteredIcons.slice(0, 50).map((icon) => (
|
||||
<CommandItem
|
||||
key={icon.value}
|
||||
@@ -87,9 +87,8 @@ export function IconNameCombobox({ value, onValueChange, error, isInvalid }: Ico
|
||||
onValueChange(selectedValue)
|
||||
setIsFocused(false)
|
||||
}}
|
||||
className="cursor-pointer opacity-60"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<AlertCircle className="h-3.5 w-3.5 text-destructive mr-2 flex-shrink-0" />
|
||||
<span className="font-mono text-sm">{icon.label}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
@@ -109,7 +108,9 @@ export function IconNameCombobox({ value, onValueChange, error, isInvalid }: Ico
|
||||
|
||||
{/* Helper text when no error */}
|
||||
{!error && value && (
|
||||
<p className="text-sm text-muted-foreground mt-1.5">{loading ? "Checking availability..." : "✓ Available icon ID"}</p>
|
||||
<p className="text-sm text-muted-foreground mt-1.5">
|
||||
{loading ? "Loading icon names..." : "Select an existing icon to update or enter a new ID"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user