Skip to content

Jumzeey/microinflux

Repository files navigation

Micro-Influx


Micro-Influx Banner
typescript react vite tailwindcss lucide

🤖 Introduction

Micro-Influx is a comprehensive platform that allows users to create and manage marketing campaigns efficiently. The app provides a responsive dashboard that adapts to both mobile and desktop screens, offering a smooth and seamless user experience.

Table of Contents

🕸️ Features
🕸️ Project Structure
🕸️ Technologies Used
🕸️ Installation
🕸️ Gallery
🕸️ Snippets
🕸️ Footnote

🔋 Features

👉 Campaign Management: Create and view marketing campaigns.

👉 Search Functionality: Filter campaigns by title for quick access.

👉 Responsive Dashboard: Optimized layout for both desktop and mobile views.

👉 Dynamic Gravatars: Generate initials and colored backgrounds for users dynamically using Gravatar.

👉 Intuitive Sidebar Navigation: Easily accessible sidebar for seamless navigation on mobile.

👉 TypeScript Support: Full TypeScript integration for static type checking.

⚙️ Tech Stack

  • TypeScript for static typing and improved developer experience.
  • React as the core UI library for building component-driven user interfaces.
  • Vite as the build tool for fast and optimized bundling.
  • Tailwind CSS for utility-first styling and rapid UI development.
  • Lucide React for iconography with simple yet customizable icons.
  • ShadCN UI components to streamline the interface.

Project Structure

src/
├── assets/                 # Static assets
│   ├── fonts/             # Font files
│   ├── icons/             # Icon files
│   └── images/            # Image files
├── components/             # Reusable React components
│   ├── campaign/          # Campaign-related components
│   ├── dashboard/         # Dashboard components
│   ├── shared/            # Shared components (used across multiple features)
│   └── ui/                # UI components (buttons, forms, etc.)
│       └── index.ts       # Export file for UI components
├── context/                # React context for state management
│   └── campaign-context.tsx # Context for campaign-related state
├── data/                   # Data fetching and mock data
│   ├── categories-data.ts  # Category-related data
│   ├── profile-overview-data.ts # Profile overview data
│   └── sidebar-data.ts     # Sidebar data for navigation
├── lib/                    # Library for utility functions (currently empty)
├── pages/                  # Page components
│   ├── Dashboard.tsx       # Dashboard page
│   ├── RootLayout.tsx      # Root layout component
│   └── index.ts            # Export file for page components
├── repositories/           # Repositories for handling data interactions
│   └── campaign-repository.ts # Campaign-specific repository
├── types/                  # TypeScript types and interfaces
│   └── campaign-types.ts    # Types related to campaigns
├── utils/                  # Utility functions
│   ├── gravatar-generator.ts # Gravatar generation utility
│   └── shorten-text.ts     # Utility to shorten text strings
├── App.tsx                 # Main application component
├── index.css               # Global CSS
└── main.tsx                # Entry point for the app

⚙️ Technologies Used

  • React: A JavaScript library for building user interfaces
  • TypeScript: Adds static typing to JavaScript, ensuring code correctness
  • Vite: A build tool that aims to provide a faster and leaner development experience
  • Tailwind CSS: A utility-first CSS framework
  • Lucide React: Icon library used for rendering icons
  • ShadCN UI: Component library for building user interfaces
  • Gravatar: Used for generating dynamic user avatars

Installation

  1. Clone the repository:

    git clone https://github.com/Jumzeey/microinflux.git
  2. Navigate to the project directory:

    cd micro-influx
  3. Install the dependencies:

    npm install
  4. Start the development server:

    npm run dev
  5. Open your browser and visit http://localhost:5173 to see the app running.

📸 Gallery

Below are some dummy screenshots of the app on mobile and desktop views.

Mobile View

Mobile Screenshot 1 Mobile Screenshot 2
Mobile Screenshot 3 Mobile Screenshot 4

Resolution: 300x600px

Desktop View

Desktop Screenshot 1 Desktop Screenshot 2 Desktop Screenshot 2

Resolution: 1024x768px

🕸️ Snippets

src/components/CampaignCard.tsx
import React from 'react';
import { getInitials, getBackgroundColor } from '@/utils/avatarUtils';

type CampaignCardProps = {
  title: string;
  budget: number;
  user: {
    name: string;
  };
};

const CampaignCard: React.FC<CampaignCardProps> = ({ title, budget, user }) => {
  const initials = getInitials(user.name);
  const backgroundColor = getBackgroundColor(user.name);

  return (
    <div className="p-4 shadow-lg bg-white rounded-lg">
      <div className="flex items-center">
        <div
          className="w-10 h-10 rounded-full text-white flex items-center justify-center"
          style={{ backgroundColor }}
        >
          {initials}
        </div>
        <h2 className="ml-4 text-xl font-bold">{title}</h2>
      </div>
      <p className="text-gray-600">Budget: ${budget}</p>
    </div>
  );
};

export default CampaignCard;
src/utils/avatarUtils.ts
export function getInitials(name: string): string {
  const initials = name
    .split(' ')
    .map((n) => n[0])
    .join('');
  return initials.toUpperCase();
}

export function getBackgroundColor(name: string): string {
  const colors = ['#FF5733', '#33FF57', '#3357FF', '#F333FF'];
  const charCodeSum = name
    .split('')
    .reduce((acc, char) => acc + char.charCodeAt(0), 0);
  return colors[charCodeSum % colors.length];
}
src/pages/RootLayout.tsx
import { SideBar } from '@/components';
import { Outlet } from 'react-router-dom';

const RootLayout: React.FC = () => {
  return (
    <div className="h-full flex ">
      <div className="hidden md:block lg:block">
        <SideBar />
      </div>
      <div className="flex-1 w-full lg:ml-[320px] md:ml-[320px]">
        <Outlet />
      </div>
    </div>
  );
};

export default RootLayout;
src/utils/shorten-text.ts
/**
 * Shortens a given text to the specified length and appends "..." if truncated.
 * @param text - The text to be shortened.
 * @param maxLength - The maximum length of the text.
 * @returns The shortened text.
 */
export const shortenText = (text: string, maxLength: number): string => {
  if (text.length <= maxLength) {
    return text;
  }

  return text.substring(0, maxLength - 3) + '...';
};
src/repository/campaign-repository.ts
import { Campaign } from '../types/campaign-types';

class CampaignRepository {
  private campaigns: Campaign[];

  constructor() {
    this.campaigns = [];
  }

  // Get all campaigns
  getCampaigns(): Campaign[] {
    return [...this.campaigns];
  }

  // Add a new campaign
  addCampaign(campaign: Campaign): Campaign[] {
    const exists = this.campaigns.some(
      existingCampaign => existingCampaign.id === campaign.id
    );
    if (exists) {
      throw new Error(`Campaign with ID ${campaign.id} already exists.`);
    }
    this.campaigns = [...this.campaigns, campaign];
    return this.getCampaigns();
  }

  // Search campaigns by title
  searchCampaigns(title: string): Campaign[] {
    return this.campaigns.filter(campaign =>
      campaign.title.toLowerCase().includes(title.toLowerCase())
    );
  }
}

export const campaignRepository = new CampaignRepository();
src/context/campaign-context.ts
import {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from 'react';
import { Campaign, CampaignContextType } from '../types/campaign-types';
import { campaignRepository } from '../repositories/campaign-repository';

// Create context with type or undefined initially
const CampaignContext = createContext<CampaignContextType | undefined>(
  undefined
);

// Provider component
export const CampaignProvider = ({ children }: { children: ReactNode }) => {
  const [campaigns, setCampaigns] = useState<Campaign[]>([]);

  useEffect(() => {
    const initialCampaigns = campaignRepository.getCampaigns();
    setCampaigns(initialCampaigns);
  }, []);

  const addCampaign = (campaign: Campaign) => {
    try {
      const updatedCampaigns = campaignRepository.addCampaign(campaign);
      setCampaigns(updatedCampaigns);
    } catch (error: any) {
      console.error(error.message);
    }
  };

  const searchCampaigns = (title: string) => {
    const filteredCampaigns = campaignRepository.searchCampaigns(title);
    setCampaigns(filteredCampaigns);
  };

  return (
    <CampaignContext.Provider
      value={{ campaigns, addCampaign, searchCampaigns }}
    >
      {children}
    </CampaignContext.Provider>
  );
};

// Hook to access campaigns context
export const useCampaigns = (): CampaignContextType => {
  const context = useContext(CampaignContext);
  if (!context) {
    throw new Error('useCampaigns must be used within a CampaignProvider');
  }
  return context;
};

Footnote

This project was built as a test given to me for the role of Frontend Engineer. Feel free to explore and learn from the codebase!