Frontend Technologies
Core Framework: Next.js
Next.js serves as our primary framework for building server-rendered React applications across the MOOD MNKY ecosystem.
We use Next.js for its powerful hybrid rendering capabilities, built-in routing, API routes, and seamless deployment options.
Key Features We Leverage
Hybrid Rendering Combination of SSR, SSG, and Client-side rendering for optimal performance
API Routes Backend functionality directly within our frontend codebase
File-based Routing Intuitive routing system based on file structure
Image Optimization Automatic image optimization with the Next.js Image component
Vue.js Components
While our primary applications are built with Next.js, we use Vue.js for specific interactive components and standalone applications within the MOOD MNKY ecosystem.
Implementation Approach
// Example Vue component integration in Next.js
import { defineComponent , h } from 'vue'
import { createVueApp } from 'vue-in-react'
const MoodTrackerComponent = defineComponent ({
props: {
userId: String ,
initialMood: String ,
},
setup ( props ) {
// Vue component implementation
return () => h ( 'div' , [
// Component template
])
}
})
const VueMoodTracker = createVueApp ({
component: MoodTrackerComponent
})
// Use in React/Next.js
function MoodTrackerIntegration ({ userId , mood }) {
return (
< div className = "vue-component-wrapper" >
< VueMoodTracker userId = { userId } initialMood = { mood } />
</ div >
)
}
Styling: Tailwind CSS
Tailwind CSS provides our utility-first styling approach, enabling rapid development while maintaining consistent design principles.
Configuration
Usage Pattern
// tailwind.config.js
module . exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}' ,
'./components/**/*.{js,ts,jsx,tsx,mdx}' ,
],
theme: {
extend: {
colors: {
primary: {
light: '#7C9A92' , // Sage Green
DEFAULT: '#2A2A4A' , // Deep Indigo
dark: '#1A1A30' ,
},
accent: {
light: '#E6B17E' , // Warm Amber
DEFAULT: '#D27D5C' , // Terracotta
dark: '#59344F' , // Deep Plum
},
},
fontFamily: {
sans: [ 'Montserrat' , 'sans-serif' ],
serif: [ 'Lora' , 'serif' ],
},
},
},
plugins: [
require ( '@tailwindcss/forms' ),
require ( '@tailwindcss/typography' ),
],
}
MOOD MNKY Typography System Our application uses a custom typography system with the following font families:
RocGroteskWide : Brand font for headings (font-heading, font-brand)
RocGrotesk : More readable brand font for UI elements (font-grotesk)
Helvetica : Clean font for body text (font-body)
HelveticaRounded : Friendly font for buttons and CTAs (font-rounded)
For detailed typography guidelines and usage examples, see the Theme Customization documentation.
State Management: Pinia
For Vue applications, we use Pinia as our state management solution, while for React/Next.js applications, we use a combination of React Context and Redux.
Pinia Store Example
// Mood tracking store
import { defineStore } from 'pinia'
import { supabase } from '@/lib/supabase'
export const useMoodStore = defineStore ( 'mood' , {
state : () => ({
currentMood: null ,
moodHistory: [],
isLoading: false ,
error: null
}),
getters: {
moodTrend : ( state ) => {
// Calculate trends based on mood history
if ( state . moodHistory . length < 2 ) return 'neutral'
// Trend calculation logic
}
},
actions: {
async fetchMoodHistory ( userId ) {
this . isLoading = true
try {
const { data , error } = await supabase
. from ( 'mood_entries' )
. select ( '*' )
. eq ( 'user_id' , userId )
. order ( 'created_at' , { ascending: false })
if ( error ) throw error
this . moodHistory = data
} catch ( e ) {
this . error = e . message
} finally {
this . isLoading = false
}
},
async recordMood ( mood , userId ) {
this . isLoading = true
try {
const { data , error } = await supabase
. from ( 'mood_entries' )
. insert ([{ mood , user_id: userId }])
. select ()
if ( error ) throw error
this . currentMood = mood
this . moodHistory = [ data [ 0 ], ... this . moodHistory ]
} catch ( e ) {
this . error = e . message
} finally {
this . isLoading = false
}
}
}
})
UI Components: Headless UI and Element Plus
We use a combination of Headless UI for React applications and Element Plus for Vue applications to create consistent, accessible, and beautiful interfaces.
import { Dialog , Transition } from '@headlessui/react'
import { Fragment , useState } from 'react'
export function MoodSelectionModal ({ isOpen , closeModal , onSelect }) {
const moods = [
{ name: 'Happy' , emoji: '😊' , color: '#FFD700' },
{ name: 'Calm' , emoji: '😌' , color: '#90EE90' },
{ name: 'Energetic' , emoji: '⚡' , color: '#FF6F61' },
{ name: 'Reflective' , emoji: '🤔' , color: '#7B68EE' },
{ name: 'Tired' , emoji: '😴' , color: '#A9A9A9' },
]
return (
< Transition appear show = { isOpen } as = { Fragment } >
< Dialog as = "div" className = "relative z-50" onClose = { closeModal } >
< Transition.Child
as = { Fragment }
enter = "ease-out duration-300"
enterFrom = "opacity-0"
enterTo = "opacity-100"
leave = "ease-in duration-200"
leaveFrom = "opacity-100"
leaveTo = "opacity-0"
>
< div className = "fixed inset-0 bg-black bg-opacity-25" />
</ Transition.Child >
< div className = "fixed inset-0 overflow-y-auto" >
< div className = "flex min-h-full items-center justify-center p-4 text-center" >
< Transition.Child
as = { Fragment }
enter = "ease-out duration-300"
enterFrom = "opacity-0 scale-95"
enterTo = "opacity-100 scale-100"
leave = "ease-in duration-200"
leaveFrom = "opacity-100 scale-100"
leaveTo = "opacity-0 scale-95"
>
< Dialog.Panel className = "w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all" >
< Dialog.Title
as = "h3"
className = "text-lg font-medium leading-6 text-gray-900"
>
How are you feeling today?
</ Dialog.Title >
< div className = "mt-4 grid grid-cols-5 gap-2" >
{ moods . map (( mood ) => (
< button
key = { mood . name }
className = "flex flex-col items-center p-2 rounded-lg hover:bg-gray-100 transition-colors"
onClick = { () => {
onSelect ( mood )
closeModal ()
} }
style = { { backgroundColor: ` ${ mood . color } 20` } }
>
< span className = "text-3xl" > { mood . emoji } </ span >
< span className = "mt-1 text-sm" > { mood . name } </ span >
</ button >
)) }
</ div >
</ Dialog.Panel >
</ Transition.Child >
</ div >
</ div >
</ Dialog >
</ Transition >
)
}
< template >
< div class = "custom-scent-builder" >
< el-steps : active = " activeStep " finish-status = "success" >
< el-step title = "Base Notes" icon = "nose" />
< el-step title = "Middle Notes" icon = "vial" />
< el-step title = "Top Notes" icon = "spray-can" />
< el-step title = "Review" icon = "check" />
</ el-steps >
< div class = "mt-8" >
< div v-if = " activeStep === 0 " >
< h3 class = "text-xl font-medium mb-4" > Select Base Notes </ h3 >
< el-row : gutter = " 16 " >
< el-col v-for = " note in baseNotes " : key = " note . id " : span = " 8 " >
< el-card @ click = " toggleNote ( note , 'base' ) " : class = " { 'selected' : selectedBase . includes ( note . id ) } " >
< div class = "flex items-center" >
< div class = "w-12 h-12 rounded-full" : style = " { backgroundColor: note . color } " ></ div >
< div class = "ml-4" >
< h4 class = "font-medium" > {{ note . name }} </ h4 >
< p class = "text-sm text-gray-600" > {{ note . description }} </ p >
</ div >
</ div >
</ el-card >
</ el-col >
</ el-row >
</ div >
<!-- Other steps omitted for brevity -->
< div class = "mt-6 flex justify-between" >
< el-button v-if = " activeStep > 0 " @ click = " activeStep -- " > Previous </ el-button >
< el-button v-if = " activeStep < 3 " type = "primary" @ click = " activeStep ++ " > Next </ el-button >
< el-button v-else type = "success" @ click = " createScent " > Create Your Scent </ el-button >
</ div >
</ div >
</ div >
</ template >
< script setup >
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useFragranceStore } from '@/stores/fragrance'
const activeStep = ref ( 0 )
const selectedBase = ref ([])
const selectedMiddle = ref ([])
const selectedTop = ref ([])
const fragranceStore = useFragranceStore ()
const baseNotes = fragranceStore . getAvailableNotes ( 'base' )
function toggleNote ( note , type ) {
if ( type === 'base' ) {
const index = selectedBase . value . indexOf ( note . id )
if ( index === - 1 ) {
if ( selectedBase . value . length < 3 ) {
selectedBase . value . push ( note . id )
} else {
ElMessage . warning ( 'You can select up to 3 base notes' )
}
} else {
selectedBase . value . splice ( index , 1 )
}
}
// Similar logic for middle and top notes
}
function createScent () {
fragranceStore . createCustomFragrance ({
baseNotes: selectedBase . value ,
middleNotes: selectedMiddle . value ,
topNotes: selectedTop . value
})
}
</ script >
< style scoped >
.custom-scent-builder {
max-width : 800 px ;
margin : 0 auto ;
}
.selected {
border : 2 px solid #2A2A4A ;
transform : translateY ( -2 px );
box-shadow : 0 4 px 6 px rgba ( 0 , 0 , 0 , 0.1 );
}
</ style >
Client-side Utilities: VueUse
For Vue applications, we leverage VueUse for composable utilities that enhance developer productivity and application functionality.
Common VueUse Implementations
import { useMouse } from '@vueuse/core'
import { useLocalStorage } from '@vueuse/core'
import { useMediaQuery } from '@vueuse/core'
// Fragrance visualization component
export function useFragranceVisualizer () {
// Track mouse position for interactive visualization
const { x , y } = useMouse ()
// Responsive design detection
const isMobile = useMediaQuery ( '(max-width: 640px)' )
// Persist user preferences
const visualizationStyle = useLocalStorage ( 'fragrance-viz-style' , 'particles' )
// Custom composable logic
function generateVisualization ( fragranceData ) {
// Generate visualization based on fragrance data, mouse position,
// device type, and user preferences
return {
particles: [],
intensity: fragranceData . intensity ,
// ...other visualization data
}
}
return {
x , y ,
isMobile ,
visualizationStyle ,
generateVisualization
}
}
Documentation: Mintlify
Our documentation system leverages Mintlify to create comprehensive, accessible, and beautiful documentation for all aspects of the MOOD MNKY ecosystem.
Key Features
Interactive code samples
Responsive design
Dark/light mode support
Search functionality
Versioning support
Content Organization
Brand documentation
Technical documentation
API references
Product catalog
Developer guides
Frontend Testing Strategy
Our frontend testing approach involves multiple layers to ensure quality and reliability:
Unit Testing
Using Jest and React Testing Library for React components, and Vitest for Vue components. // Example unit test for a React component
import { render , screen , fireEvent } from '@testing-library/react'
import { MoodSelector } from './MoodSelector'
describe ( 'MoodSelector' , () => {
it ( 'renders all mood options' , () => {
render (< MoodSelector onSelect ={() => {}} />)
expect ( screen . getByText ( 'Happy' )). toBeInTheDocument ()
expect ( screen . getByText ( 'Calm' )). toBeInTheDocument ()
expect ( screen . getByText ( 'Energetic' )). toBeInTheDocument ()
expect ( screen . getByText ( 'Reflective' )). toBeInTheDocument ()
expect ( screen . getByText ( 'Tired' )). toBeInTheDocument ()
})
it ( 'calls onSelect with the correct mood when clicked' , () => {
const handleSelect = jest . fn ()
render (< MoodSelector onSelect ={ handleSelect } />)
fireEvent . click ( screen . getByText ( 'Happy' ))
expect ( handleSelect ). toHaveBeenCalledWith ({
name: 'Happy' ,
emoji: '😊' ,
color: '#FFD700'
})
})
})
Component Testing
Using Storybook to develop and test components in isolation. // Example Storybook story
import { MoodSelector } from './MoodSelector'
export default {
title: 'Components/MoodSelector' ,
component: MoodSelector ,
argTypes: {
onSelect: { action: 'selected' }
}
}
const Template = ( args ) => < MoodSelector { ... args } />
export const Default = Template.bind({})
Default . args = {}
export const WithPreselectedMood = Template . bind ({})
WithPreselectedMood . args = {
initialMood: 'Happy'
}
export const Disabled = Template . bind ({})
Disabled . args = {
disabled: true
}
Integration Testing
Using Cypress for end-to-end testing of user flows. // Example Cypress test
describe ( 'Mood Tracking Flow' , () => {
beforeEach (() => {
cy . login ( '[email protected] ' , 'password' )
cy . visit ( '/dashboard' )
})
it ( 'allows a user to record their current mood' , () => {
cy . contains ( 'How are you feeling today?' ). should ( 'be.visible' )
cy . contains ( 'button' , 'Select Mood' ). click ()
cy . contains ( 'Happy' ). click ()
cy . contains ( 'Mood recorded successfully' ). should ( 'be.visible' )
cy . contains ( 'Current Mood: Happy' ). should ( 'be.visible' )
})
it ( 'shows mood history in a chart' , () => {
cy . get ( '[data-testid="mood-history-chart"]' ). should ( 'be.visible' )
cy . get ( '[data-testid="mood-history-chart"] .chart-bar' ). should ( 'have.length.at.least' , 1 )
})
})
We implement various strategies to ensure optimal frontend performance:
Code Splitting Dynamic imports and lazy loading for route-based code splitting
Image Optimization Next.js Image component with WebP format and responsive sizing
Caching Strategy Progressive data loading and local caching with SWR/React Query
Bundle Analysis Regular webpack bundle analysis to identify and reduce bloat
Core Web Vitals Monitoring and optimization of LCP, FID, and CLS metrics
Server Components Leveraging Next.js Server Components for faster rendering
Frontend Architecture
Our frontend architecture follows these principles:
Component-Based Design : Reusable, composable components
Atomic Design : Organizing components into atoms, molecules, organisms, templates, and pages
Progressive Enhancement : Core functionality works without JavaScript, enhanced with client-side features
Responsive Design : Mobile-first approach with adaptable layouts
Accessibility : WCAG compliance throughout all interfaces
Resources
For any questions related to our frontend technologies, please contact the MOOD MNKY development team.