Skip to main content

Frontend Technologies

Frontend Technology Stack

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 SystemOur 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: 800px;
  margin: 0 auto;
}
.selected {
  border: 2px solid #2A2A4A;
  transform: translateY(-2px);
  box-shadow: 0 4px 6px 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:
1

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'
    })
  })
})
2

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
}
3

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)
  })
})

Performance Optimization

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:
  1. Component-Based Design: Reusable, composable components
  2. Atomic Design: Organizing components into atoms, molecules, organisms, templates, and pages
  3. Progressive Enhancement: Core functionality works without JavaScript, enhanced with client-side features
  4. Responsive Design: Mobile-first approach with adaptable layouts
  5. Accessibility: WCAG compliance throughout all interfaces

Resources


For any questions related to our frontend technologies, please contact the MOOD MNKY development team.