F4: AI Companies Directory Flow - Feature Analysis¶
Overview¶
The AI Companies Directory feature provides users with a comprehensive listing of AI companies in a structured and filterable format. This directory serves as a central hub for users to discover, research, and engage with AI companies across various categories. Each company is presented as a card with rich metadata, interactive elements, and call-to-action buttons. The feature includes capabilities for following companies, booking meetings through Calendly integration, accessing free trials, and viewing detailed company profiles. The directory is designed to be highly interactive, responsive, and personalized based on user preferences and authentication status.
Company Information Display Strategy¶
In alignment with the platform-wide approach to company information display:
- Card View (Directory Listing): Company cards in the directory display only essential information:
- Company name
- Location
- Claimed
- Logo
- Category/tags
- Short description
- Follow button
- Basic action buttons (Calendly, website links)
-
Detail View: Complete company information is only accessible when a user clicks on a company card, which navigates to the dedicated Company Detail Page with comprehensive information and specialized content types (Products, People, Finance, etc.)
-
Benefits of this approach:
- Improves page load performance by limiting initial data transfer
- Creates a cleaner, more focused directory UI
- Maintains consistent information hierarchy across the platform
- Encourages user exploration of company details
API Endpoints¶
1. Fetch Companies Directory¶
GET /api/company/
Request Parameters:
search(string, optional): Text search query for company name or description.category(string, optional): Filter by category id.is_featured_by_admin(boolean, optional): Filter for featured companies only.is_claimed(boolean, optional): Filter for claimed companies only.orderingfor latest to oldest Filtercategory__incategory base Filterstatusfor only publish company Filter
Response:
{
"count": 150,
"next": "https://api.futrconnect.com/api/company/",
"previous": null,
"results": [
{
"id": "uuid-string",
"name": "AI Solutions Inc",
"logo": "https://storage.futrconnect.com/logos/ai-solutions.png",
"overview": "Leading provider of conversational AI solutions",
"short_video": "https://storage.futrconnect.com/videos/ai-solutions-commercial.mp4",
"category": ["uuid-string"],
"is_featured_by_admin": true,
"is_claimed": true,
"is_followed": false,
"meeting_url": "https://calendly.com/ai-solutions/meeting",
"social_link": {
"website": "https://ai-solutions.com/trial"
}
}
// Additional companies...
]
}
2. Follow Company¶
POST /api/company-follow/
Request Body:
{
"company": "company-uuid"
}
Response (201 Created):
{
"id": "uuid-string",
"company": "company-uuid"
}
3. Fetch User’s Followed Companies¶
GET /api/company-follow/
Response:
{
"count": 24,
"next": "https://api.futrconnect.com/api/company-follow/",
"previous": null,
"results": [
{
"id": "uuid-string",
"company": {
"id": "uuid-string",
"name": "AI Solutions Inc",
"logo": "https://storage.futrconnect.com/logos/ai-solutions.png",
"category": ["uuid-string"]
}
}
// Additional followed companies...
]
}
State Management¶
React-query¶
interface AiCompany {
id: string;
category: Category[];
company_addresses: CompanyAddress[];
company_social_links: CompanySocialLinks | null;
is_followed: boolean;
created_on: string;
edited_on: string;
name: string;
email: string;
company_founded: string;
phone_number: string;
logo: string;
meeting_url: string | null;
short_video: string | null;
first_call: string | null;
demo_video: string;
overview: string;
about: string;
best_suited_for: string[];
where_to_use: string;
standout_function: string;
status: string;
publish_date: null;
is_claimed: boolean;
is_featured_by_admin: boolean;
total_followers_count: number;
user: string;
claimed_by: null;
page:”HomeFeedMain” | “AiCompanyListMain” | “AiCompanyDetails”;
}[]
type CompanyAddress = {
address_1: string;
address_2: string;
address_type: string;
company: string;
created_on: string;
edited_on: string;
id: string;
postal_code: string;
state: string;
};
interface Category {
id: string;
title: string;
description: string;
}
interface FollowCompany {
id: string;
follower: string;
created_on: string;
edited_on: string;
company: string;
logo: string;
company_name: string;
}
MobX-State-Tree Models¶
const AiCompanyStore ={
fileForClaimProfile: types.array(AiCompanySchema.FileForClaim),
claimedId: types.maybeNull(types.string)
}
CompaniesDirectoryStore¶
Model AiCompanyStore {
// Actions
getFileLengthWhichAreNotUploaded()
clearFileForClaimProfile()
addFileForArray()
removeFileFromList()
checkFileFailed()
updateClaimedId(id)
followAiCompany({companyId})
unFollowAiCompany({companyId})
claimCompany()
claimCompanyMedia({body,id})
}
Database Models¶
PostgreSQL Models¶
Company¶
id(UUID, primary key)name(varchar, required)logo(ImageField, nullable)overview(text, nullable)short_video(URLField, nullable)category(ManyToManyField to CompanyCategory)is_featured_by_admin(boolean, default: false)is_claimed(boolean, default: false)claimed_by(ForeignKey to User, nullable)meeting_url(URLField, nullable)social_link(OneToOneField to CompanySocialLink)
CompanyFollow¶
id(UUID, primary key)follower(ForeignKey to User)company(ForeignKey to Company)
Backend Logic¶
Company Search and Filtering¶
The backend uses Django REST Framework’s built-in filtering capabilities to provide search and filtering functionality for the company directory.
-
Text Search: The
SearchFilteris used for basic, case-insensitive text search on thenameandoverviewfields of theCompanymodel. -
Filtering: The
DjangoFilterBackendallows for filtering companies based on exact matches for fields likecategory,is_featured_by_admin, andis_claimed.
Follow/Unfollow Mechanism¶
The follow/unfollow functionality is handled by the CompanyFollowViewSet:
-
Follow: When a user follows a company, a
POSTrequest to the/api/company-follow/endpoint creates a newCompanyFollowrecord, linking the user and the company. -
Unfollow: To unfollow, a
DELETErequest is made to the same endpoint with the company ID, which deletes the correspondingCompanyFollowrecord.
Performance and Optimization¶
Database Query Optimization¶
The backend relies on the standard query optimization provided by the Django ORM. The following techniques are used to ensure efficient database queries:
-
Indexing: The
Companymodel has database indexes on thestatus,is_claimed, andis_featured_by_adminfields to speed up filtering. TheCompanyFollowmodel has indexes on thefollowerandcompanyfields. -
Pagination: The API uses Django REST Framework’s standard
PageNumberPaginationto limit the number of results returned per request, which prevents large querysets from being loaded into memory.
Frontend Performance¶
- Lazy Loading and Virtualization:
- Implement lazy loading for company logos and commercial videos
- State Management Optimization:
- Use derived values (computed properties) to minimize redundant calculations
- Implement selective updates to avoid unnecessary re-renders
- Use React query efficient data fetching
- Network Request Optimization:
- Compress API responses using gzip or brotli
- Implement retry logic for transient failures
- Libraries: React query for automatic retry of failed requests
- Image Optimization:
- Serve responsive images based on viewport size
- Use modern image formats (WebP with fallbacks)
- Use image CDN for optimal delivery
- Libraries: next/image for automatic optimization
Technical Considerations¶
Cross-Device Compatibility¶
- Implement responsive design using Tailwind CSS breakpoints
- Optimize for touch interfaces on mobile devices
- Ensure keyboard accessibility for desktop users
- Test on multiple device types and screen sizes
Language Support¶
- Currently, the website is implemented in a single language (English)
- No language-switching capability in the current implementation
- All content, including company descriptions and tags, is in English
- Data structures are designed to support future localization if needed
Company Scoring (Future Implementation)¶
- Company scoring functionality will be added in a future release
- The database schema and API endpoints have been designed to support this feature
- User interface for rating companies will be implemented when this feature is released
- Initial implementation will focus on core directory and following functionality without scoring
Authentication Integration¶
- Handle graceful redirection for unauthenticated users attempting to follow companies
- Maintain authentication state across page navigation
- Implement token refresh logic to avoid session timeouts
- Preserve context (e.g., current page, filters) during authentication flow
Potential Bottlenecks¶
- Search Performance with Large Datasets:
- Challenge: Full-text search can become slow with many companies
- Solution: Implement caching for search results
- Solution: Consider dedicated search service (Elasticsearch) for large scale
- Solution: Implement typeahead search with debouncing
- Follow/Unfollow Concurrency:
- Challenge: Race conditions with multiple follow/unfollow operations
- Solution: Implement optimistic UI updates with server confirmation
- Solution: Use database transactions to ensure data consistency
- Solution: Add rate limiting for follow/unfollow API endpoints
- Image Loading Performance:
- Challenge: Many company logos loading simultaneously can cause performance issues
- Solution: Implement progressive loading with placeholders
- Solution: Prioritize loading visible images first
- Solution: Use appropriate image sizes for different view contexts
- Filter Combination Complexity:
- Challenge: Complex filter combinations can lead to slow queries
- Solution: Cache common filter combinations
- Solution: Use materialized views for complex filter scenarios
- Solution: Implement backend query optimization for high-volume filters
Implementation Considerations¶
Frontend Implementation¶
-
Component Structure:
- Create reusable CompanyCard component with: - Props for all company data - Event handlers for interactions (follow, score (future implementation), etc.) - Conditional rendering for featured/claimed status - Display limited information by design:
const CompanyProfile = memo(forwardRef<HTMLDivElement, AiCompany>((props, ref) => { const { aiCompanyStore, userStore, companyStore } = useStores(); const [internalFollow, setInternalFollow] = useState(props.is_followed); const { errorHandelForPutPatchDelete } = useErrorHandelForPutPatchDelete(); const { followCompany, unfollowCompany } = useUpdateFollowAiCompany(); const [openCalendlyModal, setOpenCalendlyModal] = useState(false); const [calendlyURL, setCalendlyURL] = useState(''); const { mutateAsync, isPending } = useMutation({ mutationFn: companyStore.increaseFreeTrialCount, onSuccess: () => { enqueueSnackbar('successfully submitted!', { autoHideDuration: 3000, variant: 'success' }); }, onError: (err: any) => { enqueueSnackbar(err?.error ?? '', { autoHideDuration: 3000, variant: 'error' }); } }); const { mutateAsync: mutateFollowUnfollowCompany, isPending: isFollowedPending } = useMutation({ mutationFn: aiCompanyStore.followAiCompany, onError: (error: CustomErrorType) => { if (internalFollow == true) { unfollowCompany(props.id, props.page); setInternalFollow(false); } else { setInternalFollow(true); followCompany( { company: props.id, company_name: props.name, created_on: '', edited_on: '', follower: userStore.loggedInUserData?.user?.id!, id: props.id, logo: props.logo }, props.page ); } errorHandelForPutPatchDelete(error.code, error.error); } }); const handleFollowClick = (): void => { if (userStore.loggedInUserData?.user?.id) { setInternalFollow((prev) => !prev); if (internalFollow == true) { unfollowCompany(props.id, props.page); } else { followCompany( { company: props.id, company_name: props.name, created_on: '', edited_on: '', follower: userStore.loggedInUserData?.user?.id, id: props.id, logo: props.logo }, props.page ); } mutateFollowUnfollowCompany({ companyId: props.id }); } else { userStore.updateAuthModalState(true); } }; useEffect(() => { setInternalFollow(props.is_followed); }, [props.is_followed]); const address = props.company_addresses?.[0]?.address_2 + ', ' + props?.company_addresses?.[0]?.state; return (<motion.div ref={ref} initial={{ opacity: 0, y: 40 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, ease: 'easeOut' }} whileHover={{ scale: 1.01, boxShadow: '0 10px 25px rgba(0, 0, 0, 0.1)' }} className={`w-full bg-white shadow-md overflow-hidden rounded-[8px]`}> {props.is_featured_by_admin === true && ( <Badge text='Featured' className='absolute right-0 rounded-bl-md px-6 py-0 font-semibold text-[16px]'/> )} <div className='grid xl:grid-cols-[58%_42%] p-4 sm:p-8 gap-4 '> <div className='space-y-4 '> <div className='flex gap-4 items-center relative mb-8 '> <Link aria-label={`${props.name} company details`} href={`/company-details/${props.id}`} className='bg-white'> <Avatar src={props.logo ? convertS3ImagUrl(props.logo) : ''} letter={props.name.trim()[0]} className='h-[58px] w-[58px] bg-transparent' /> </Link> <div className='max-w-[60%]'> <div className='flex flex-col gap-[10px] sm:flex-row sm:justify-between items-start'> <Link aria-label={`${props.name} company details`} href={`/company-details/${props.id}`}> <h2 className='font-bold text-gray-800 sm:w-[14rem] overflow-hidden leading-[23px] text-[20px]'> {props.name} </h2> </Link> <button onClick={handleFollowClick} className='text-nowrap p-0 font-bold text-[16px] text-primaryColor'> {internalFollow ? 'Following' : '+ Follow'} </button> </div> <div> <p className='text-xs font-normal text-black leading-[22px] text-[14px] one-line sm:max-w-full max-w-[250px] overflow-hidden '> {address.length > 50 ? address.slice(0, 50) + '....' : address} </p> <div className='flex font-semibold gap-2 absolute -bottom-6 sm:-bottom-4 '> {props.is_claimed && ( <div className='border-2 border-alertOrang rounded-r-full relative pl-2 pr-3 ml-3 cursor-default'> <span className='absolute text-alertOrang -left-3 -top-1'> <ShieldIconWithCheck size={0.4} /> </span> <p className='text-alertOrang font-bold text-[10px]'>Claimed</p> </div> )} </div> </div> </div> </div> {props.short_video && ( <div className='aspect-[5/3] w-full rounded-[8px] overflow-hidden xl:hidden relative'><UniversalVideo src={props.short_video} /> </div> )} {props.category.length > 0 && ( <Link aria-label={`${props.name} company details`} href={`/company-details/${props.id}`}> <div className='flex flex-wrap lg:gap-2 gap-2 mt-5 '>{props.category.slice(0, 6).map((service, index) => ( <ServiceBadge key={service.id} service={service.title} color={'lightGray'} className='border rounded-[12px] border-darkGrey p-[6px] text-nowrap font-normal w-fit' /> ))} </div> </Link> )} <Link aria-label={`${props.name} company details`} href={`/company-details/${props.id}`}> <p className='text-sm text-gray-600 mb-6 pt-6 leading-relaxed line-clamp-2'> {props.about} </p> </Link> <div className='grid grid-cols-2 gap-4 pr-6'> {!!props.meeting_url ? ( <CalendlyInlineModal companyId={props.id} triggerBtn={ <Button variant='primary' onClick={() => setCalendlyURL(props.meeting_url as string)} className='border-[3px] border-primaryColor'>Book Meeting</Button>} setOpen={setOpenCalendlyModal} url={calendlyURL} open={openCalendlyModal} /> ) : ( <Button variant='primary' className='border-[3px] border-primaryColor' onClick={() => { enqueueSnackbar('Oops no meeting link found', { variant: 'info', autoHideDuration: 3000 }); }}> Book Meeting </Button> )} {props.company_social_links?.website ? ( <Link href={props.company_social_links?.website} className='min-w-[10rem]' target='_blank'> <Button onClick={async () => { await mutateAsync({ company: props.id, user: userStore.loggedInUserData?.user?.id as string }); }} variant='outline' className='flex-1 w-full border-[3px]'> Free Trial </Button> </Link> ) : ( <Button isLoading={isPending} variant='outline' disabled={!props.company_social_links?.website} className='flex-1 w-full border-[3px] min-w-[10rem]' onClick={async () => { await mutateAsync({ company: props.id, user: userStore.loggedInUserData?.user?.id as string }); }}> Free Trial </Button> )} </div> </div> <div className={` hidden xl:block`}> {props.short_video && ( <div className='h-56 md:h-full rounded-[8px] overflow-hidden w-full'> <UniversalVideo src={props.short_video} className='h-full w-full' /> </div> )} </div> </div> </motion.div> ); }) )- Implement CompanyList container component for:
- Handling pagination and lazy loading
- Managing filter state
- Displaying search results
-
State Management Strategy:
- Use useState for state (companies, filters, pagination)
- Use local component state for UI-specific behavior
- Implement proper error handling and loading states
- Create separation between data fetching and presentation layers
-
Filter UI Implementation:
- Create a filter component with:
- Search input with debounce
- Category selector
- Clear filters button
-
Mobile Responsiveness:
- Implement touch-friendly controls
- Adjust card layout for smaller screens
- Implement responsive grid using Tailwind’s grid system
-
Information Display Strategy:
- Implement a consistent approach to company information display:
- Cards display only essential information (name, logo, category, short description, tags)
- Clicking a card navigates to the Company Detail Page with complete information
- This approach:
- Improves initial page load performance
- Creates a cleaner, more scannable directory UI
- Maintains consistent information hierarchy across the platform
- Encourages exploration of detailed company information
- Use visual hierarchy to emphasize the most important information
- Design cards to be visually appealing while remaining information-focused
Potential Bottlenecks¶
- Search Performance with Large Datasets:
- Challenge: Full-text search can become slow with many companies
- Solution: Implement caching for search results
- Solution: Consider dedicated search service (Elasticsearch) for large scale
- Solution: Implement typeahead search with debouncing
- Follow/Unfollow Concurrency:
- Challenge: Race conditions with multiple follow/unfollow operations
- Solution: Implement optimistic UI updates with server confirmation
- Solution: Use database transactions to ensure data consistency
- Solution: Add rate limiting for follow/unfollow API endpoints
- Image Loading Performance:
- Challenge: Many company logos loading simultaneously can cause performance issues
- Solution: Implement progressive loading with placeholders
- Solution: Prioritize loading visible images first
- Solution: Use appropriate image sizes for different view contexts
- Filter Combination Complexity:
- Challenge: Complex filter combinations can lead to slow queries
- Solution: Cache common filter combinations
- Solution: Use materialized views for complex filter scenarios
- Solution: Implement backend query optimization for high-volume filters
Creative Suggestions¶
Enhanced Company Cards¶
- Interactive Card Animations:
- Implement subtle hover effects for company cards
- Add micro-interactions for follow/unfollow actions
- Use transition animations when filtering changes visible companies
- Libraries: framer-motion for declarative animations
- Rich Media Preview:
- Add hover preview for commercial videos
- Implement image gallery lightbox for company media
- Show “peek” of company details without leaving directory
- Libraries: react-image-lightbox, react-player for media handling
- Social Proof Elements:
- Add follower count badges to company cards
- Implement “Recently followed by” with profile pictures
- Add trending indicators for companies gaining popularity
- Visual indicator for companies followed by user’s network
Advanced Discovery Features¶
- AI-Powered Recommendations:
- Add “Companies you might like” section based on follow history
- Implement similarity-based company suggestions
- Show personalized category recommendations
- Libraries: scikit-learn for basic recommendation algorithms
- Interactive Company Exploration:
- Add an interactive network graph showing relationships between companies
- Implement category-based visualization of the AI ecosystem
- Add timeline view showing company founding dates
- Libraries: d3.js or react-force-graph for visualizations
- Engagement Gamification:
- Add achievement system for exploring different categories
- Implement collection completion indicators
- Create “AI Expert” badges for users who explore thoroughly
- Add shareable collections of favorite companies
Feature Suggestions¶
Enhanced Company Discovery¶
- Company Comparison Tool:
- Allow users to select multiple companies for side-by-side comparison
- Compare features, categories, and offerings
- Generate comparison reports or shareable links
- Advanced Filtering Options:
- Add filtering by funding stage (seed, series A, etc.)
- Filter by company size (startup, mid-size, enterprise)
- Filter by founding date range
- Filter by technologies used or industry focus
- Company Activity Feed:
- Show recent updates from companies in the directory
- Display new content, feature launches, or news mentions
- Allow subscribing to updates from specific companies
- Company Activity Feed:
- Show recent updates from companies in the directory
- Display new content, feature launches, or news mentions
- Allow subscribing to updates from specific companies
- Industry Expert Curation:
- Highlight collections curated by industry experts
- Feature “Editor’s picks” or “AI Innovators to Watch”
- Allow guest curators to create featured collections
Additional Considerations¶
Accessibility Enhancements¶
- Implement proper ARIA attributes for interactive elements
- Ensure keyboard navigability for filter controls
- Add screen reader descriptions for icons and visual elements
- Support text scaling and contrast modes
- Implement focus management for modal dialogs
- Libraries: @axe-core/react for accessibility testing
Content Moderation¶
- Implement flagging system for inappropriate company content
- Add review process for claimed company profiles
- Create guidelines for company commercial videos
- Develop an audit trail for company profile changes
- Consider AI-assisted content screening for scale
Mobile Considerations¶
- Optimize company cards for touch interfaces
- Implement swipe gestures for card interactions
- Create a specialized filter UI for small screens
- Use bottom sheets instead of sidebars on mobile
- Ensure tap targets are appropriately sized
- Libraries: react-swipeable for gesture support
Further Reading & Best Practices¶
- Directory UX Design:
- Search Implementation:
- Performance Optimization:
- Mobile Directory Design:
- Accessibility for Directory Sites:
- WCAG Guidelines for Filtering Interfaces
- Making Complex Tables and Directories Accessible