fix: complete frontend component any type cleanup

- replace any with Prisma types (Post, Project, Album, Media)
- use Component type for Svelte component parameters
- use Snippet type for Svelte 5 render slots
- use Record<string, unknown> for dynamic objects
- add proper type guards for error handling
- fix editor extension types with proper generics
- all frontend components now have zero any type errors
This commit is contained in:
Justin Edmund 2025-11-23 05:50:22 -08:00
parent 3d77922a99
commit 93795577cd
24 changed files with 44 additions and 29 deletions

View file

@ -7,7 +7,7 @@
let searchQuery = $state('')
let storefront = $state('us')
let isSearching = $state(false)
let searchResults = $state<any>(null)
let searchResults = $state<unknown>(null)
let searchError = $state<string | null>(null)
let responseTime = $state<number>(0)

View file

@ -5,7 +5,9 @@
import { formatDate } from '$lib/utils/date'
import { renderEdraContent } from '$lib/utils/content'
let { post }: { post: any } = $props()
import type { Post } from '@prisma/client'
let { post }: { post: Post } = $props()
const renderedContent = $derived(post.content ? renderEdraContent(post.content) : '')
</script>

View file

@ -5,7 +5,7 @@
title?: string
caption?: string
description?: string
exifData?: any
exifData?: Record<string, unknown>
createdAt?: string
backHref?: string
backLabel?: string

View file

@ -1,4 +1,6 @@
import type { Project } from '@prisma/client'
<script lang="ts">
import type { Snippet } from 'svelte'
import Button from '$lib/components/admin/Button.svelte'
import BackButton from './BackButton.svelte'
import { onMount } from 'svelte'
@ -7,7 +9,7 @@
projectSlug: string
correctPassword: string
projectType?: 'work' | 'labs'
children?: any
children?: Snippet
}
let { projectSlug, correctPassword, projectType = 'work', children }: Props = $props()

View file

@ -8,7 +8,7 @@
interface UniverseItem {
slug: string
publishedAt: string
[key: string]: any
[key: string]: unknown
}
let {

View file

@ -8,10 +8,12 @@
const currentPath = $derived($page.url.pathname)
import type { Component } from 'svelte'
interface NavItem {
text: string
href: string
icon: any
icon: Component
}
const navItems: NavItem[] = [

View file

@ -1,3 +1,4 @@
import type { Album } from '@prisma/client'
<script lang="ts">
import { goto } from '$app/navigation'
import AdminByline from './AdminByline.svelte'
@ -23,7 +24,7 @@
createdAt: string
updatedAt: string
photos: Photo[]
content?: any
content?: unknown
_count: {
media: number
}

View file

@ -183,7 +183,7 @@
if (!hasContent() && postType !== 'essay') return
if (postType === 'essay' && !essayTitle) return
let postData: any = {
let postData: Record<string, unknown> = {
content,
status: 'published',
attachedPhotos: attachedPhotos.map((photo) => photo.id)

View file

@ -1,9 +1,10 @@
<script lang="ts">
import { onMount } from 'svelte'
import Input from './Input.svelte'
import type { Post } from '@prisma/client'
type Props = {
post: any
post: Post
postType: 'post' | 'essay'
slug: string
excerpt: string

View file

@ -162,7 +162,7 @@ $effect(() => {
usedIn: [],
createdAt: new Date(),
updatedAt: new Date()
} as any
} as unknown
}
showDraftPrompt = false
clearDraft(draftKey)

View file

@ -1,3 +1,4 @@
import type { Post } from '@prisma/client'
<script lang="ts">
import { goto } from '$app/navigation'
import { onMount } from 'svelte'
@ -72,14 +73,14 @@
if (typeof post.content === 'object' && post.content.content) {
// BlockNote/TipTap format
function extractText(node: any): string {
if (node.text) return node.text
if (node.content && Array.isArray(node.content)) {
return node.content.map(extractText).join(' ')
function extractText(node: Record<string, unknown>): string {
if (typeof node.text === 'string') return node.text
if (Array.isArray(node.content)) {
return node.content.map((n) => extractText(n as Record<string, unknown>)).join(' ')
}
return ''
}
textContent = extractText(post.content)
textContent = extractText(post.content as Record<string, unknown>)
} else if (typeof post.content === 'string') {
textContent = post.content
}

View file

@ -259,7 +259,7 @@ $effect(() => {
try {
isSaving = true
const payload: any = {
const payload: Record<string, unknown> = {
type: 'post', // Use simplified post type
status: publishStatus,
content: content

View file

@ -6,7 +6,7 @@
editor: Editor
isOpen: boolean
onClose: () => void
features: any
features: { textStyles?: boolean; colors?: boolean; [key: string]: unknown }
}
const { editor, isOpen, onClose, features }: Props = $props()

View file

@ -140,7 +140,7 @@
}
// Update content when editor changes
function handleUpdate({ editor: updatedEditor, transaction }: any) {
function handleUpdate({ editor: updatedEditor, transaction }: { editor: Editor; transaction: unknown }) {
// Dismiss link menus on typing
linkManagerRef?.dismissOnTyping(transaction)

View file

@ -33,10 +33,12 @@ export interface DropdownPosition {
left: number
}
import type { Media } from '@prisma/client'
export interface MediaSelectionOptions {
mode: 'single' | 'multiple'
fileType?: 'image' | 'video' | 'audio' | 'all'
albumId?: number
onSelect: (media: any) => void
onSelect: (media: Media | Media[]) => void
onClose: () => void
}

View file

@ -43,11 +43,13 @@ import SlashCommandList from './headless/components/SlashCommandList.svelte'
// Create lowlight instance
const lowlight = createLowlight(all)
import type { Component } from 'svelte'
export interface EditorExtensionOptions {
showSlashCommands?: boolean
onShowUrlConvertDropdown?: (pos: number, url: string) => void
onShowLinkContextMenu?: (pos: number, url: string, coords: { x: number; y: number }) => void
imagePlaceholderComponent?: any // Allow custom image placeholder component
imagePlaceholderComponent?: Component // Allow custom image placeholder component
}
export function getEditorExtensions(options: EditorExtensionOptions = {}): Extensions {

View file

@ -4,7 +4,7 @@ import type { Component } from 'svelte'
import type { NodeViewProps } from '@tiptap/core'
export interface GalleryOptions {
HTMLAttributes: Record<string, any>
HTMLAttributes: Record<string, unknown>
}
declare module '@tiptap/core' {

View file

@ -3,8 +3,8 @@ import type { Component } from 'svelte'
import { SvelteNodeViewRenderer } from 'svelte-tiptap'
export interface GalleryPlaceholderOptions {
HTMLAttributes: Record<string, object>
onSelectImages: (images: any[], editor: Editor) => void
HTMLAttributes: Record<string, unknown>
onSelectImages: (images: Array<Record<string, unknown>>, editor: Editor) => void
}
declare module '@tiptap/core' {

View file

@ -3,7 +3,7 @@ import type { Component } from 'svelte'
import { SvelteNodeViewRenderer } from 'svelte-tiptap'
export interface GeolocationExtendedOptions {
HTMLAttributes: Record<string, any>
HTMLAttributes: Record<string, unknown>
}
export const GeolocationExtended = (

View file

@ -4,7 +4,7 @@ import GeolocationPlaceholder from './geolocation-placeholder.svelte'
import GeolocationExtended from './geolocation-extended.svelte'
export interface GeolocationOptions {
HTMLAttributes: Record<string, any>
HTMLAttributes: Record<string, unknown>
}
declare module '@tiptap/core' {

View file

@ -3,7 +3,7 @@ import { Plugin, PluginKey } from '@tiptap/pm/state'
import { Decoration, DecorationSet } from '@tiptap/pm/view'
export interface UrlEmbedOptions {
HTMLAttributes: Record<string, any>
HTMLAttributes: Record<string, unknown>
onShowDropdown?: (pos: number, url: string) => void
}

View file

@ -1,7 +1,8 @@
import { mergeAttributes, Node } from '@tiptap/core'
import { SvelteNodeViewRenderer } from 'svelte-tiptap'
import type { Component } from 'svelte'
export const UrlEmbedExtended = (component: any) =>
export const UrlEmbedExtended = (component: Component) =>
Node.create({
name: 'urlEmbed',

View file

@ -1,7 +1,8 @@
import { mergeAttributes, Node } from '@tiptap/core'
import { SvelteNodeViewRenderer } from 'svelte-tiptap'
import type { Component } from 'svelte'
export const UrlEmbedPlaceholder = (component: any) =>
export const UrlEmbedPlaceholder = (component: Component) =>
Node.create({
name: 'urlEmbedPlaceholder',

View file

@ -116,7 +116,7 @@
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
handleBrowseLibrary(e as any)
handleBrowseLibrary(e as unknown as MouseEvent)
} else if (e.key === 'Escape') {
deleteNode()
}