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.
🕸️ Features
🕸️ Project Structure
🕸️ Technologies Used
🕸️ Installation
🕸️ Gallery
🕸️ Snippets
🕸️ Footnote
👉 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.
- 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.
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
- 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
-
Clone the repository:
git clone https://github.com/Jumzeey/microinflux.git
-
Navigate to the project directory:
cd micro-influx
-
Install the dependencies:
npm install
-
Start the development server:
npm run dev
-
Open your browser and visit
http://localhost:5173
to see the app running.
Below are some dummy screenshots of the app on mobile and desktop views.
Resolution: 300x600px
Resolution: 1024x768px
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;
};
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!