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

View file

@ -5,7 +5,9 @@
import { formatDate } from '$lib/utils/date' import { formatDate } from '$lib/utils/date'
import { renderEdraContent } from '$lib/utils/content' 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) : '') const renderedContent = $derived(post.content ? renderEdraContent(post.content) : '')
</script> </script>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -140,7 +140,7 @@
} }
// Update content when editor changes // 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 // Dismiss link menus on typing
linkManagerRef?.dismissOnTyping(transaction) linkManagerRef?.dismissOnTyping(transaction)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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