-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Opportunities for Component Reuse
This document identifies areas where the Sunlo codebase has duplicated patterns that could be consolidated into shared components.
High Priority
1. Search Interfaces
Problem: Five distinct search interfaces exist with significant overlap in functionality.
| Interface | Location | Search Type | Results UI |
|---|---|---|---|
| Smart Search | src/routes/_user/learn/$lang.search.tsx |
Server-side fuzzy (RPC) | Accordion |
| Browse Page | src/routes/_user/learn/browse.tsx |
Client-side includes | Mixed cards |
| Chat Send Phrase | src/routes/_user/friends/chats.$friendUid.recommend.tsx |
Client-side | Custom render |
| Select Phrases | src/components/comments/select-phrases-for-comment.tsx |
Client-side | Checkboxes |
| Friend Search | src/routes/_user/friends/search.tsx |
Client-side | Profile cards |
Shared Features:
- Search input with debouncing (100-300ms)
- Optional language/tag filtering
- Results display with loading/empty states
useLanguagePhrasesSearch()hook used by 3 interfaces
Recommendation: Create a <PhraseSearchWidget> component accepting:
searchType: 'smart' | 'local' | 'picker'renderResult: custom renderer functiononSelect: callback for selectionsfilters: optional{ tags, languages }sortable: booleanmaxSelections: number (for picker mode)
Files to consolidate:
src/routes/_user/friends/chats.$friendUid.recommend.tsx:40-127src/components/comments/select-phrases-for-comment.tsx:36-195- Common display logic from
src/routes/_user/learn/$lang.search.tsx
2. Deletion Dialogs
Problem: Three nearly identical deletion dialogs with 60+ lines of duplication each.
| Dialog | Location | Target |
|---|---|---|
| Delete Comment | src/components/comments/delete-comment-dialog.tsx |
Comments |
| Delete Playlist | src/components/playlists/delete-playlist-dialog.tsx |
Playlists |
| Delete Request | src/components/requests/delete-request-dialog.tsx |
Requests |
Identical Pattern:
- AlertDialog wrapper
- useState for open state
- useMutation for deletion
- Collection update on success
- Toast notification
- Optional navigation awayRecommendation: Create <DeleteConfirmDialog>:
interface DeleteDialogProps {
trigger: ReactNode
title: string
description: string
mutationKey: string[]
deleteFn: () => Promise<void>
onSuccess?: () => void
navigateAway?: string
}Files to consolidate:
src/components/comments/delete-comment-dialog.tsxsrc/components/playlists/delete-playlist-dialog.tsxsrc/components/requests/delete-request-dialog.tsx
3. Upvote Buttons
Problem: Two nearly identical upvote button implementations.
| Button | Location |
|---|---|
| Upvote Request | src/components/requests/upvote-request-button.tsx:17-97 |
| Upvote Playlist | src/components/playlists/upvote-playlist-button.tsx:17-97 |
Identical Logic:
- Check existing upvote via live query
- Toggle mutation (insert/delete)
- Heart icon with filled/outline state
- Count display with animation
Recommendation: Create generic <UpvoteButton>:
interface UpvoteButtonProps {
entityType: 'request' | 'playlist'
entityId: uuid
lang: string
count: number
}4. Edit/Update Dialogs
Problem: Two identical single-textarea update dialogs.
| Dialog | Location |
|---|---|
| Update Request | src/components/requests/update-request-dialog.tsx |
| Update Comment | src/components/comments/update-comment-dialog.tsx |
Identical Pattern:
- Dialog with textarea
- Cancel resets state
- Save mutation updates collection
- Toast notification
Recommendation: Create <EditTextDialog>:
interface EditTextDialogProps {
trigger: ReactNode
title: string
value: string
placeholder?: string
onSave: (value: string) => Promise<void>
}Medium Priority
5. Phrase Display Variants
Problem: Multiple components display phrase data with different layouts.
| Component | Location | Context |
|---|---|---|
| CardResultSimple | src/components/cards/card-result-simple.tsx |
Comments, contributions |
| PhraseTinyCard | src/components/cards/phrase-tiny-card.tsx |
Grids, recommendations |
| PhraseAccordionItem | src/components/phrase-accordion-item.tsx |
Accordion lists |
| PhraseSummaryLine | src/components/feed/feed-phrase-group-item.tsx:10-77 |
Feed items |
Shared Data:
- Phrase text
- Translations
- Language badge
- Heart/status indicator
- Learner count
Recommendation: Create <PhraseDisplay> with variant prop:
interface PhraseDisplayProps {
phrase: PhraseFullType
variant: 'card' | 'tiny' | 'accordion' | 'summary' | 'inline'
interactive?: boolean
onSelect?: () => void
}6. Send to Friend Dialogs
Problem: Two nearly identical "send to friend" dialogs.
| Dialog | Location |
|---|---|
| Send Playlist | src/components/playlists/send-playlist-to-friend.tsx |
| Send Request | src/components/card-pieces/send-request-to-friend.tsx |
Identical Logic:
- Dialog with
SelectMultipleFriendscomponent - Mutation sends chat message
- Auth requirement wrapper
Recommendation: Create <SendToFriendsDialog>:
interface SendToFriendsDialogProps {
trigger: ReactNode
entityType: 'playlist' | 'request' | 'phrase'
entityId: uuid
lang: string
}7. Empty State Components
Problem: Inconsistent empty state patterns across lists and feeds.
Current Patterns:
- Callout with CTA buttons (feed pages)
- Simple italic text (friend lists)
- Icon + heading + description (browse page)
Recommendation: Create <EmptyState>:
interface EmptyStateProps {
icon?: LucideIcon
title: string
description?: string
actions?: Array<{ label: string; to: string; variant?: ButtonVariant }>
}Files using inconsistent patterns:
src/routes/_user/learn/$lang.feed.tsx:198-222(Callout pattern)src/components/friend-profiles.tsx:25(Text pattern)src/routes/_user/learn/browse.tsx:465-476(Centered text pattern)
8. Infinite Scroll Trigger
Problem: Infinite scroll pattern duplicated with slight variations.
Current Implementations:
src/routes/_user/learn/$lang.search.tsx:245-249(IntersectionObserver)src/routes/_user/learn/$lang.feed.tsx:236-243(Load More button)
Recommendation: Create <InfiniteScrollTrigger>:
interface InfiniteScrollTriggerProps {
hasNextPage: boolean
isFetching: boolean
onLoadMore: () => void
mode?: 'intersection' | 'button'
}Lower Priority
9. Creator Header Pattern
Observation: Multiple components display creator info + timestamp + owner actions.
Appears in:
- Request headers
- Playlist headers
- Comment headers
- Feed item headers
Already somewhat abstracted: UidPermalink and UidPermalinkInline handle user display.
Potential improvement: Extract <ItemCreatorHeader> combining:
- User permalink
- Timestamp (via
ago()) - Conditional owner action buttons
10. Form Mutation Pattern
Observation: Most forms follow identical mutation success/error handling.
Pattern:
onSuccess: (data) => {
collection.utils.writeInsert/writeUpdate(Schema.parse(data))
toast.success('Message')
}
onError: (error) => {
console.log('Error', error)
toast.error('Error message')
}Potential improvement: Create useFormMutation hook:
function useFormMutation<TData, TInput>({
mutationFn,
collection,
schema,
successMessage,
errorMessage,
onSuccess,
}: FormMutationOptions<TData, TInput>)11. Unused Item Component
Observation: src/components/ui/item.tsx defines a comprehensive Item component system that is NOT currently used anywhere in the codebase.
Components available:
Item,ItemGroup,ItemSeparatorItemMedia,ItemContent,ItemTitle,ItemDescriptionItemActions,ItemHeader,ItemFooter
Opportunity: Consider using this for standardizing list item layouts instead of custom implementations.
Implementation Notes
Prioritization Criteria
- Code duplication - Highest priority for nearly identical implementations
- Maintenance burden - Components that need synchronized updates
- Consistency - User-facing patterns that should look/behave identically
Suggested Implementation Order
- Delete dialogs (quick win, high duplication)
- Upvote buttons (quick win, high duplication)
- Edit text dialogs (quick win)
- Search widget (larger effort, high impact)
- Phrase display variants (larger effort, medium impact)
- Send to friends dialogs
- Empty states
- Infinite scroll trigger
Testing Considerations
- All consolidated components should maintain existing behavior
- Consider creating shared test utilities for common patterns
- Playwright tests should not break if visual layout remains similar
Metadata
Metadata
Assignees
Labels
Projects
Status