I've tried to keep the app as clean as possible so it's easy to find everything. The main elements of the app are split into:
- The chunks of code that are responsible for a specific task.
- I've separated components into Basic and Complex components
- Basic Components are ones that get reused lots throughout the app like buttons and textboxes
- The Complex components do a more complex and specific job. A complex component might use a Basic component to complete it's job
- css Stylings for the components are stored with the tsx files of the components
- The code that represents the different pages of the website
- Where possible these are kept as simple as possible and just made up from different components
- There are no stylings for pages - these are managed either by the component stylings or by the app.css styling
- Assets (folder -> src/assets)
- Where the fonts are stored
- public
- public/Stickers contains the files for all the stickers
- public/wiki contains the files associated with the wiki page
- this folder is excluded by the gitignore currently for security and will need to be added manually to the public folder
- .env
- This stores all the secure information about the system and is not included in the github files. It will need to be manually added to the root folder of the album.
- I've made efforts to ensure that all references to the server are in here so it can be easily swapped out for another project
- I've created a file called example.env that can be used to see the structure of the current env
Styling in the app is managed predominantly by the App.css file. I've tried to make it so that any of the main logic of styling is here so any app-wide changes can be made here.
Components have their own css styling associated with them. Pages do not have their own styling and just use the app.css and component styling.
I've attempted to ensure that there is a styling hierarcy in all components to make changes simple. For example the styling for all buttons is controlled by the button.css file. Components like Messageboard.tsx and the Carousel.tsx use buttons. If the button.css styling is changed it will change across all components that use buttons.
Colours are set by the list of colours in the App.css file. If the colours are updated there then they should update across the whole app.
The colours are:
- --colour1: #4CAF50;
- --colour2: #0000FF;
- --colour3: #FF9F65;
- --colour4: #FFFFFF;
- --colour5: #333333;
Colours will be referred to throughout the app like this
var(--colour1)
Fonts are set in the App.css file. Like colours, if these are updated in App.css then they should change across the whole app
- font1: 'WorkSans', Arial, sans-serif;
- font2: 'NectoMono', monospace;
Fonts will be referred to in the code like this
var(--font1)
All buttons used across the app use this component. The component has several versions:
- Basic: A standard button with a label.
- Close: A button with a close (X) icon.
- Arrow-Left: A button with a left arrow icon.
- Arrow-Right: A button with a right arrow icon.
- Submit: A button used for form submissions.
-
Basic Button:
<Button label="Click Me" onClick={() => alert('Button clicked!')} type="basic" />
-
Close Button:
<Button type="close" onClick={() => alert('Close button clicked!')} size="2em" />
The Carousel component is used to display a customizable and interactive carousel/slider. It supports features like looping, autoplay, and navigation buttons. The carousel is used by the CarouselAlbums and CaourselStickers components.
- slides: An array of
ReactNodeelements to be displayed as slides. - loop: A boolean to enable or disable looping of slides (default:
false). - autoplay: A boolean to enable or disable autoplay (default:
false). - autoplayDelay: The delay (in milliseconds) between autoplay transitions (default:
4000).
- Basic Carousel:
<Carousel slides={[ <img src="slide1.jpg" alt="Slide 1" />, <img src="slide2.jpg" alt="Slide 2" />, <img src="slide3.jpg" alt="Slide 3" />, ]} loop={true} autoplay={true} autoplayDelay={3000} />
The Header component is used to display the navigation (eg home, listen etc) and title of the app. It can turn into a burger navigation button when the page width is small enough (eg on mobile).
- title: The main title text to display in the header.
- subtitle: The subtitle text to display below the title.
- Basic Header:
<Header title="YabbyVille" subtitle="Your music, your way" />
The MessageTextBox component is a versatile text input box designed for sending messages. It includes features like word and character limits, auto-resizing, and an optional send button.
- placeholder: Placeholder text for the input box (default:
"Type here"). - value: The current value of the text box (for controlled input).
- onSend: A callback function triggered when the send button is clicked or
Ctrl+Enter/Cmd+Enteris pressed. - onChange: A callback function triggered when the text changes.
- disabled: Disables the text box and send button (default:
false). - maxWords: Maximum number of words allowed (default:
250). - className: Additional CSS classes for custom styling.
- showSendButton: Controls the visibility of the send button (default:
true). - showCounter: Controls the visibility of the word/character counter (default:
true). - children: Custom child elements, such as a custom send button.
- Auto-Resizing: The text box automatically adjusts its height based on the content.
- Word and Character Limits: Displays a counter and prevents input beyond the specified limits.
- Keyboard Shortcuts: Supports
Ctrl+Enter/Cmd+Enterfor sending messages. - Customizable Send Button: Allows passing a custom button as a child.
-
Basic MessageTextBox:
<MessageTextBox placeholder="Type your message..." onSend={(text) => console.log("Message sent:", text)} />
-
MessageTextBox with Custom Limits:
<MessageTextBox maxWords={100} onSend={(text) => console.log("Message sent:", text)} showCounter={true} />
-
MessageTextBox with Custom Send Button:
<MessageTextBox onSend={(text) => console.log("Message sent:", text)} showSendButton={false} > <Button type="submit" label="Custom Send" /> </MessageTextBox>
The Star component is an animated SVG star with a dynamic distortion effect. It uses an feTurbulence filter to create a "stop-motion" style animation, making the star appear to distort over time. This component is only used once in the App.tsx file.
- Basic Star:
<Star />
The TextAnimations.css file provides reusable CSS classes for gentle and dynamic text animations. These include floating, breathing, drifting, and hover effects. These are only currently used for the floating effect on the header but it could be reused anywhere.
- Floating Animation:
<h1 class="animated-text float-gentle">Floating Text</h1>
The Tips component displays helpful tips to users. Visibility can be configured for mobile and/or desktop devices.
- text: The tip text to display (can include emojis).
- showOnMobile: Boolean to show tip on mobile devices (default:
true). - showOnDesktop: Boolean to show tip on desktop (default:
false).
<Tips
text="đź’ˇ Long press the heart to see who reacted"
showOnMobile={true}
showOnDesktop={false}
/>The ForumMessageBox component is a rich text editor designed for posting messages with artist/album tagging functionality. It uses a WYSIWYG editor and integrates with the Navidrome API to search and tag artists and albums by typing @ followed by a search query. In future updates, this could be refactored to also power sticker messages.
- placeholder: Placeholder text for the input box (default:
"Type your message..."). - value: The current value of the text box (for controlled input).
- onSend: A callback function triggered when the send button is clicked.
- disabled: Disables the text box and send button (default:
false). - maxWords: Maximum number of words allowed (default:
250). - maxChars: Maximum number of characters allowed (default:
1000). - className: Additional CSS classes for custom styling.
- showSendButton: Controls the visibility of the send button (default:
true).
- Rich Text Editing: Uses a WYSIWYG editor for HTML content creation.
- Artist/Album Tagging: Type
@followed by a search term to search for artists and albums from Navidrome. Results appear as you type (minimum 3 characters). - Hyperlink Creation: Selected artists/albums are automatically converted into hyperlinks pointing to the Navidrome interface.
- Word and Character Limits: Displays a counter and prevents input beyond specified limits.
- Search Results: Shows top 3 matching artists and albums as clickable buttons.
- Hyperlink Protection: Prevents accidental modification of inserted hyperlinks.
-
Basic ForumMessageBox:
<ForumMessageBox placeholder="Share your thoughts..." onSend={(html) => console.log("Message sent:", html)} />
-
ForumMessageBox with Custom Limits:
<ForumMessageBox maxWords={150} maxChars={750} onSend={(html) => console.log("Message sent:", html)} />
-
ForumMessageBox without Send Button:
<ForumMessageBox onSend={(html) => console.log("Message sent:", html)} showSendButton={false} />
- Type
@followed by an artist or album name (e.g.,@Beatles) - Search results appear after 3 characters
- Click a result to insert it as a hyperlink
- The link format:
https://music.yabbyville.xyz/app/#/artist/{id}/show
The AlbumSearchBox component is a dual-purpose input field that allows users to either search for albums by typing their name OR paste a Navidrome album URL. It automatically detects which input method is being used and handles them appropriately.
- placeholder: Placeholder text for the input box (default:
"Search for an album or paste URL..."). - onAlbumSelect: A callback function triggered when a user selects an album from search results. Receives the album ID as a parameter.
- onUrlSubmit: A callback function triggered when a user submits a URL. Receives the full URL as a parameter.
- Auto-Search: Automatically searches for albums as the user types (minimum 3 characters).
- URL Detection: Intelligently detects when user is pasting a URL (starts with "https:") and disables search.
- Album Search: Searches only albums (not artists) from the Navidrome API.
- Debounced Search: 300ms delay prevents excessive API calls while typing.
- Results Dropdown: Shows up to 5 matching albums with album name and artist.
- Clean UI: Search results appear below the input with clear album/artist information.
- Automatic Cleanup: Clears input and results after selection or submission.
-
Basic AlbumSearchBox:
<AlbumSearchBox onAlbumSelect={(albumId) => fetchAlbumDetails(albumId)} onUrlSubmit={(url) => handleUrlPaste(url)} />
-
AlbumSearchBox with Custom Placeholder:
<AlbumSearchBox placeholder="Find an album to add a sticker..." onAlbumSelect={(albumId) => console.log("Selected album:", albumId)} onUrlSubmit={(url) => console.log("URL pasted:", url)} />
Searching by Album Name:
- User starts typing an album name (e.g., "Abbey Road")
- After 3 characters, search results appear automatically
- Up to 5 albums shown with name and artist
- User clicks a result
onAlbumSelectcallback fires with the album ID
Pasting URL:
- User pastes a Navidrome album URL (e.g., "https://music.yabbyville.xyz/app/#/album/123/show")
- Search is automatically disabled (URL detected)
- Send button appears
- User clicks send or presses Enter
onUrlSubmitcallback fires with the URL
This component is used by PlaceSticker.tsx in both url-input and inline-url modes to provide a flexible album selection experience.
The UserMessages component is used to display messages from users. It's used for displaying the stickers and also for the messageboard. It includes their username, message content, timestamp, and an optional sticker (emoji or image). It also supports a close button for dismissing the message.
- username: The name of the user sending the message.
- message: The content of the message. This renders HTML links so long as the link points to yabbyville.xyz.
- timestamp: The time the message was sent.
- userSticker: An optional sticker (emoji or image URL) to represent the user.
- onClose: A callback function triggered when the close button is clicked.
- hideCloseButton: A boolean to hide the close button (default:
false). - reactions: An optional array of reaction objects (each containing
userId,username,timestamp) for displaying who reacted to the message. - reactionCount: An optional number indicating the total number of reactions.
- currentUserReacted: An optional boolean indicating if the current user has reacted to the message.
- onToggleReaction: An optional callback function triggered when the user clicks the heart reaction button.
-
Basic UserMessage:
<UserMessage username="JaneDoe" message="Check out my new avatar!" timestamp="2025-09-21 11:00 AM" userSticker="/Stickers/avatar.webp" onClose={() => console.log("Message closed")} />
-
UserMessage with Reactions:
<UserMessage username="JaneDoe" message="Check out my new album!" timestamp="2025-09-21 11:00 AM" userSticker="/Stickers/avatar.webp" onClose={() => console.log("Message closed")} reactions={[{ userId: '123', username: 'John', timestamp: {} }]} reactionCount={1} currentUserReacted={false} onToggleReaction={() => console.log("Reaction toggled")} />
Used to add the dropdown menu that lets users change their profile image on the profile page. Uses a flexible data structure (avatarOptions.ts) that maps each avatar shape to its available colors, automatically filtering the color dropdown to show only valid options for the selected shape. Avatar selections are stored in Firestore and displayed throughout the application in user messages and profiles. Avatar images are in public/Stickers and are 1000x1000 webp files.
Fetches the 10 most recent albums from Navidrome and displays them in the basic Carousel.tsx component.
Fetches the 10 most recent stickers and displays them in the Carousel.tsx component. Once the 10 most recent stickers are fetched it will also fetch any other stickers that have been put on those 10 albums.
Sticker information displayed using the UserMessages.tsx component
Handles all the code for the messageboard. Uses the UserMessages.tsx and the ForumMessageBox.tsx component.
- enableReactions: A boolean to enable heart reactions on messages (default:
false). When enabled, users can react to messages with a heart, and see who else has reacted.
- Heart Reactions: Users can react to messages once by clicking/tapping the heart icon. Clicking again removes their reaction.
- Reaction Count: Displays the total number of reactions on each message.
- Reaction Tooltip:
- Desktop: Hover over the reaction area to see usernames
- Mobile: Long press the reaction area (hold for 500ms) to see usernames
- Real-time Updates: Reactions are stored in Firestore subcollections and update in real-time.
// Without reactions (default)
<MessageBoard />
// With reactions enabled
<MessageBoard enableReactions={true} />The PlaceSticker component is a unified system for adding stickers to albums across the entire application. It has been refactored to eliminate code duplication and provide three different modes of operation.
-
PlaceStickerCore.tsx: The core component containing all shared sticker placement logic (drag & drop, coordinate conversion, Firestore submission, user sticker fetching). This is the single source of truth for sticker placement functionality.
-
PlaceSticker.tsx: A wrapper component that provides three different modes by utilizing PlaceStickerCore:
-
url-input (Home Page)
- Shows
AlbumSearchBoxin a styled container - User can either search for albums by name OR paste a Navidrome album URL
- Opens popup with PlaceStickerCore for sticker placement
<PlaceSticker /> // defaults to url-input mode
- Shows
-
popup (CarouselStickers & StickerGrid)
- Receives pre-loaded album information via props
- Opens directly in popup mode
- Includes back button for dual-state popup navigation
<PlaceSticker mode="popup" albumInfo={selectedAlbum} isVisible={true} onClose={handleClose} onBack={handleBack} showBackButton={true} />
-
inline-url (Stickers Page)
- Shows
AlbumSearchBoxwithout container styling - User can search for albums by name OR paste URL
- Opens popup with PlaceStickerCore when album is selected
<PlaceSticker mode="inline-url" />
- Shows
- Album Search: Type album names to search and select from Navidrome library (powered by
AlbumSearchBox) - URL Support: Paste Navidrome album URLs for quick album selection
- Drag and Drop: Interactive sticker positioning on album covers
- Normalized Coordinates: Consistent positioning across all screen sizes
- Real-time Updates: Visual feedback during sticker placement
- Firebase Integration: Sticker storage and retrieval via Firestore
- User Stickers: Automatic fetching of user's selected avatar/sticker
- Auto-refresh: Page reloads on successful submission to show new sticker
- Home Page: Uses
url-inputmode for standalone sticker placement - CarouselStickers: Uses
popupmode when clicking "Place Sticker on Album" - StickerGrid: Uses
popupmode when clicking "Place Sticker on Album" - Stickers Page: Uses
inline-urlmode for quick sticker placement at top of page
The StickerGrid component displays all albums that have stickers on them in a grid layout. It fetches stickers from Firestore, groups them by album, and shows the album covers with stickers overlaid at their saved positions. The component supports two sorting modes (chronological and shuffle) and includes pagination for better performance.
- sortMode:
'chronological' | 'shuffle'- Controls how albums are sorted in the grid - shuffleKey: A number that triggers a re-shuffle when changed
The component uses the UserMessages.tsx component to display sticker messages in the popup and the MessageTextBox.tsx component for adding new stickers.
Allows the website to be kept secure. Prevents someone not logged into the site from accessing anything other than the login page.
Fetches statistics about the Navidrome server like the number of albums. Also contains the dancing ascii man.
Allows the information from the wiki.html file to be passed into the wiki page. Doesn't currently work very well tbh as it doesn't scale with the page width for unknown reasons.
YabbyVille is a Progressive Web App, which means users can install it on their devices and use it like a native app
Android/Chrome:
- Visit the deployed site
- Tap the "Add to Home Screen" prompt or browser menu
- Tap "Install"
iOS/Safari:
- Visit the deployed site
- Tap the Share button
- Scroll down and tap "Add to Home Screen"
- Tap "Add"
Desktop (Chrome/Edge):
- Visit the deployed site
- Look for install icon in address bar or browser menu
- Click "Install YabbyVille"
The PWA functionality is implemented through the following key files:
-
vite.config.tsConfigures thevite-plugin-pwaplugin with manifest settings, caching strategies, and service worker options. Defines which assets to cache and how API requests should be handled. -
src/main.tsxRegisters the service worker when the app loads and handles update prompts and offline notifications. -
src/vite-env.d.tsTypeScript type declarations for the PWA virtual modules. -
index.htmlContains PWA meta tags for theme colors, manifest link, and iOS-specific meta tags for home screen installation. -
public/manifest.jsonWeb app manifest defining app name, icons, theme colors, and display mode for installation. -
public/icons/Contains app icons in various sizes:icon-192x192.png- Standard PWA iconicon-512x512.png- Large PWA iconapple-touch-icon.png- iOS home screen icon (180x180)
- Service worker automatically caches stickers, fonts, and app assets
- API requests to music.yabbyville.xyz are cached with NetworkFirst strategy
- Manifest configured for standalone display with custom theme colors
- Icons optimized for all platforms (192x192, 512x512, Apple touch icon)
- Auto-generated service worker handles precaching and runtime caching via Workbox
-
react-router-dom This package is used to handle routing within the application. It allows for navigation between different pages or components without requiring a full page reload, enabling a seamless single-page application (SPA) experience. Features like
useNavigateandRoutemake it easy to manage navigation and define routes in the app. -
react_icons This package is used for adding basic icons like the close button, arrows, and other visual elements to enhance the user interface.
-
firebase Firebase is used for authentication and backend services in the app. It simplifies user authentication, database management, and other backend functionalities, allowing for a seamless integration of these features into the application.
-
react-firebase-hooks This package provides a set of reusable React hooks for Firebase. It simplifies the integration of Firebase services like authentication, Firestore, and Realtime Database into React applications. For example, the
useAuthStatehook is used to track the authentication state of the user, making it easy to determine if a user is logged in or not. This reduces boilerplate code and improves the readability of the app. -
embla-carousel-react This package is used to create highly customizable and performant carousels in the application. It provides a lightweight and flexible carousel solution with smooth scrolling and touch support. Embla makes it easy to implement sliders for showcasing images, content, or other interactive elements.
-
html-react-parser
The
html-react-parserpackage is used to safely convert HTML strings into React elements. It's used here for the Wiki Page so that it can be easily downloaded from google docs and updated. -
react-simple-wysiwyg
This package provides a simple WYSIWYG (What You See Is What You Get) rich text editor for React. It's used in the
ForumMessageBoxcomponent to allow users to create formatted messages with bold, italic, links, and other text styling options. The editor outputs HTML that can be stored and displayed with formatting intact. -
vite-plugin-pwa
This Vite plugin enables Progressive Web App (PWA) functionality with zero-config service worker generation. It uses Workbox under the hood to create a service worker that precaches app assets and provides offline support. The plugin generates the web app manifest and handles service worker registration automatically. It's configured to cache all stickers, fonts, and API requests for optimal performance and offline capability.