-
-
Notifications
You must be signed in to change notification settings - Fork 1
Complete API
Complete API reference for all components, hooks, stores, and utilities in Thrive.
import { useApplicationStore } from '@/store/applicationStore';interface ApplicationStore {
applications: Application[];
}Add a new application to the store.
const addApplication = useApplicationStore((state) => state.addApplication);
addApplication({
id: '1',
position: 'Software Engineer',
company: 'Tech Corp',
status: 'applied',
appliedDate: '2025-10-01',
});Update an existing application.
const updateApplication = useApplicationStore((state) => state.updateApplication);
updateApplication('1', { status: 'interview' });Delete an application by ID.
const deleteApplication = useApplicationStore((state) => state.deleteApplication);
deleteApplication('1');Get a single application by ID.
const getApplicationById = useApplicationStore((state) => state.getApplicationById);
const application = getApplicationById('1');Get all applications with a specific status.
const getApplicationsByStatus = useApplicationStore((state) => state.getApplicationsByStatus);
const appliedApps = getApplicationsByStatus('applied');Get all applications for a specific company.
const getApplicationsByCompany = useApplicationStore((state) => state.getApplicationsByCompany);
const techCorpApps = getApplicationsByCompany('Tech Corp');import { useInterviewStore } from '@/store/interviewStore';interface InterviewStore {
interviews: Interview[];
}Add a new interview.
const addInterview = useInterviewStore((state) => state.addInterview);
addInterview({
id: '1',
applicationId: 'app-1',
type: 'technical',
date: '2025-10-20T14:00:00Z',
duration: 60,
isRemote: true,
status: 'scheduled',
});Update an interview.
const updateInterview = useInterviewStore((state) => state.updateInterview);
updateInterview('1', { status: 'completed' });Delete an interview.
const deleteInterview = useInterviewStore((state) => state.deleteInterview);
deleteInterview('1');Get all interviews for a specific application.
const getInterviewsByApplication = useInterviewStore((state) => state.getInterviewsByApplication);
const interviews = getInterviewsByApplication('app-1');Get all upcoming (scheduled) interviews sorted by date.
const getUpcomingInterviews = useInterviewStore((state) => state.getUpcomingInterviews);
const upcoming = getUpcomingInterviews();Get all past (completed or cancelled) interviews.
const getPastInterviews = useInterviewStore((state) => state.getPastInterviews);
const past = getPastInterviews();import { useCompanyStore } from '@/store/companyStore';interface CompanyStore {
companies: Company[];
}Add a new company.
const addCompany = useCompanyStore((state) => state.addCompany);
addCompany({
id: '1',
name: 'Tech Corp',
website: 'https://techcorp.com',
industry: 'Technology',
});Update a company.
const updateCompany = useCompanyStore((state) => state.updateCompany);
updateCompany('1', { rating: 5 });Delete a company.
const deleteCompany = useCompanyStore((state) => state.deleteCompany);
deleteCompany('1');Get a company by ID.
const getCompanyById = useCompanyStore((state) => state.getCompanyById);
const company = getCompanyById('1');Get all companies in a specific industry.
const getCompaniesByIndustry = useCompanyStore((state) => state.getCompaniesByIndustry);
const techCompanies = getCompaniesByIndustry('Technology');import { useDocumentStore } from '@/store/documentStore';interface DocumentStore {
documents: Document[];
}Add a new document.
const addDocument = useDocumentStore((state) => state.addDocument);
addDocument({
id: '1',
name: 'Resume 2025.pdf',
type: 'resume',
fileType: 'application/pdf',
size: 102400,
url: 'blob:...',
version: 1,
uploadedAt: new Date().toISOString(),
});Update a document.
const updateDocument = useDocumentStore((state) => state.updateDocument);
updateDocument('1', { tags: ['software', 'senior'] });Delete a document.
const deleteDocument = useDocumentStore((state) => state.deleteDocument);
deleteDocument('1');Get all documents of a specific type.
const getDocumentsByType = useDocumentStore((state) => state.getDocumentsByType);
const resumes = getDocumentsByType('resume');Get all documents linked to an application.
const getDocumentsByApplication = useDocumentStore((state) => state.getDocumentsByApplication);
const docs = getDocumentsByApplication('app-1');import { useSettingsStore } from '@/store/settingsStore';interface SettingsStore {
theme: 'light' | 'dark' | 'system';
notifications: NotificationSettings;
profile: UserProfile;
}Set the theme preference.
const setTheme = useSettingsStore((state) => state.setTheme);
setTheme('dark');Update notification preferences.
const updateNotificationSettings = useSettingsStore((state) => state.updateNotificationSettings);
updateNotificationSettings({
interviewReminders: true,
reminderTime: 60, // 1 hour before
});Update user profile information.
const updateProfile = useSettingsStore((state) => state.updateProfile);
updateProfile({
name: 'John Doe',
email: 'john@example.com',
});import { useApplications } from '@/hooks/useApplications';Custom hook that provides application-related functionality with computed values.
function MyComponent() {
const {
applications,
activeApplications,
archivedApplications,
stats,
addApplication,
updateApplication,
deleteApplication,
} = useApplications();
return (
<div>
<p>Total: {stats.total}</p>
<p>Active: {stats.active}</p>
<p>Response Rate: {stats.responseRate}%</p>
</div>
);
}{
applications: Application[];
activeApplications: Application[];
archivedApplications: Application[];
stats: {
total: number;
active: number;
interviews: number;
offers: number;
responseRate: number;
interviewRate: number;
offerRate: number;
};
addApplication: (app: Application) => void;
updateApplication: (id: string, updates: Partial<Application>) => void;
deleteApplication: (id: string) => void;
}import { useInterviews } from '@/hooks/useInterviews';Custom hook for interview management with computed values.
function MyComponent() {
const {
interviews,
upcomingInterviews,
pastInterviews,
interviewsByDate,
addInterview,
updateInterview,
deleteInterview,
} = useInterviews();
return (
<div>
<p>Upcoming: {upcomingInterviews.length}</p>
</div>
);
}{
interviews: Interview[];
upcomingInterviews: Interview[];
pastInterviews: Interview[];
interviewsByDate: Record<string, Interview[]>;
addInterview: (interview: Interview) => void;
updateInterview: (id: string, updates: Partial<Interview>) => void;
deleteInterview: (id: string) => void;
}import { useCompanies } from '@/hooks/useCompanies';Custom hook for company management.
function MyComponent() {
const {
companies,
companiesWithApplications,
addCompany,
updateCompany,
deleteCompany,
} = useCompanies();
return <div>{companies.length} companies</div>;
}{
companies: Company[];
companiesWithApplications: (Company & { applicationCount: number })[];
addCompany: (company: Company) => void;
updateCompany: (id: string, updates: Partial<Company>) => void;
deleteCompany: (id: string) => void;
}import { useDocuments } from '@/hooks/useDocuments';Custom hook for document management.
function MyComponent() {
const {
documents,
documentsByType,
addDocument,
updateDocument,
deleteDocument,
uploadDocument,
} = useDocuments();
return <div>{documents.length} documents</div>;
}{
documents: Document[];
documentsByType: Record<DocumentType, Document[]>;
addDocument: (document: Document) => void;
updateDocument: (id: string, updates: Partial<Document>) => void;
deleteDocument: (id: string) => void;
uploadDocument: (file: File, type: DocumentType) => Promise<Document>;
}import { useLocalStorage } from '@/hooks/useLocalStorage';Custom hook for localStorage with TypeScript typing.
function MyComponent() {
const [value, setValue, removeValue] = useLocalStorage('key', defaultValue);
return (
<button onClick={() => setValue('new value')}>
Update
</button>
);
}-
key: string- localStorage key -
defaultValue: T- Default value if key doesn't exist
[
value: T,
setValue: (value: T) => void,
removeValue: () => void
]import { useTheme } from '@/hooks/useTheme';Custom hook for theme management.
function MyComponent() {
const { theme, setTheme, systemTheme, effectiveTheme } = useTheme();
return (
<button onClick={() => setTheme('dark')}>
Current: {effectiveTheme}
</button>
);
}{
theme: 'light' | 'dark' | 'system';
setTheme: (theme: 'light' | 'dark' | 'system') => void;
systemTheme: 'light' | 'dark';
effectiveTheme: 'light' | 'dark';
}import { useNotifications } from '@/hooks/useNotifications';Custom hook for notification management.
function MyComponent() {
const { addNotification, removeNotification, clearNotifications } = useNotifications();
const handleSuccess = () => {
addNotification({
type: 'success',
message: 'Application added successfully',
});
};
return <button onClick={handleSuccess}>Add</button>;
}{
notifications: Notification[];
addNotification: (notification: Omit<Notification, 'id'>) => void;
removeNotification: (id: string) => void;
clearNotifications: () => void;
}import { Button } from '@/components/ui/button';
<Button variant="default" size="default" onClick={handleClick}>
Click me
</Button>Props:
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'size?: 'default' | 'sm' | 'lg' | 'icon'asChild?: boolean- Standard button props
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card';
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>Content goes here</CardContent>
<CardFooter>Footer content</CardFooter>
</Card>import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Dialog description</DialogDescription>
</DialogHeader>
{/* Dialog content */}
</DialogContent>
</Dialog>import { Input } from '@/components/ui/input';
<Input
type="text"
placeholder="Enter text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui/select';
<Select value={value} onValueChange={setValue}>
<SelectTrigger>
<SelectValue placeholder="Select an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table';
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>John Doe</TableCell>
<TableCell>Active</TableCell>
</TableRow>
</TableBody>
</Table>import { Badge } from '@/components/ui/badge';
<Badge variant="default">Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="outline">Outline</Badge>import { EmptyState } from '@/components/ui/empty-state';
import { Inbox } from 'lucide-react';
<EmptyState
icon={Inbox}
title="No applications yet"
description="Get started by adding your first job application"
primaryAction={{
label: 'Add Application',
onClick: handleAdd,
}}
secondaryAction={{
label: 'Learn More',
onClick: handleLearnMore,
}}
/>Props:
-
icon: LucideIcon- Icon to display -
title: string- Main heading -
description?: string- Description text -
primaryAction?: { label: string; onClick: () => void }- Primary action button -
secondaryAction?: { label: string; onClick: () => void }- Secondary action button
import { ErrorState } from '@/components/ui/error-state';
<ErrorState
title="Failed to load applications"
description="There was an error loading your applications. Please try again."
onRetry={handleRetry}
mode="card" // or "alert"
/>Props:
-
title: string- Error title -
description?: string- Error description -
onRetry?: () => void- Retry callback -
mode?: 'card' | 'alert'- Display mode
import { CardSkeleton, TableSkeleton, ListSkeleton, DashboardSkeleton } from '@/components/ui/loading-skeletons';
// Card skeleton
<CardSkeleton />
// Table skeleton
<TableSkeleton rows={5} columns={4} />
// List skeleton
<ListSkeleton items={3} />
// Dashboard skeleton
<DashboardSkeleton />import { SkipNav } from '@/components/a11y/SkipNav';
// In root layout
<SkipNav />Provides skip navigation links for keyboard users.
import { VisuallyHidden } from '@/components/a11y/VisuallyHidden';
<VisuallyHidden>
Screen reader only content
</VisuallyHidden>Hides content visually while keeping it accessible to screen readers.
import { FocusTrap } from '@/components/a11y/FocusTrap';
<FocusTrap active={isModalOpen} onClose={handleClose}>
<div>Modal content</div>
</FocusTrap>Props:
-
active: boolean- Whether focus trap is active -
onClose?: () => void- Callback when Escape is pressed -
children: React.ReactNode- Content to trap focus within
import { LiveRegion, useAnnouncer } from '@/components/a11y/LiveRegion';
// In component
function MyComponent() {
const announce = useAnnouncer();
const handleAction = () => {
// Do something
announce('Action completed successfully');
};
return <button onClick={handleAction}>Do Action</button>;
}
// In root layout
<GlobalAnnouncer />import { ApplicationForm } from '@/components/applications/ApplicationForm';
<ApplicationForm
application={existingApplication} // Optional, for editing
onSubmit={handleSubmit}
onCancel={handleCancel}
/>Props:
-
application?: Application- Existing application for editing -
onSubmit: (application: Application) => void- Submit callback -
onCancel?: () => void- Cancel callback
import { ApplicationList } from '@/components/applications/ApplicationList';
<ApplicationList
applications={applications}
onSelect={handleSelect}
selectedId={selectedId}
/>Props:
-
applications: Application[]- Applications to display -
onSelect?: (application: Application) => void- Select callback -
selectedId?: string- Currently selected application ID
import { ApplicationBoard } from '@/components/applications/ApplicationBoard';
<ApplicationBoard
applications={applications}
onStatusChange={handleStatusChange}
/>Props:
-
applications: Application[]- Applications to display -
onStatusChange: (id: string, status: ApplicationStatus) => void- Status change callback
import { StatusBadge } from '@/components/applications/StatusBadge';
<StatusBadge status="applied" />Props:
-
status: ApplicationStatus- Status to display
import { AnalyticsDashboard } from '@/components/analytics/AnalyticsDashboard';
<AnalyticsDashboard applications={applications} interviews={interviews} />import { MetricCard } from '@/components/analytics/MetricCard';
<MetricCard
title="Total Applications"
value={120}
trend={12}
trendLabel="vs last month"
icon={FileText}
/>Props:
-
title: string- Metric title -
value: number | string- Metric value -
trend?: number- Trend percentage (positive or negative) -
trendLabel?: string- Trend label -
icon?: LucideIcon- Icon to display
import { cn } from '@/lib/utils';
const className = cn(
'base-class',
condition && 'conditional-class',
{ 'object-class': condition },
'another-class'
);Combines and merges Tailwind classes intelligently.
import { formatDate, formatDateTime, formatRelativeTime } from '@/lib/utils';
formatDate('2025-10-18'); // "October 18, 2025"
formatDateTime('2025-10-18T14:30:00Z'); // "October 18, 2025 at 2:30 PM"
formatRelativeTime('2025-10-18'); // "today", "2 days ago", etc.import { generateId } from '@/lib/utils';
const id = generateId(); // "550e8400-e29b-41d4-a716-446655440000"import { debounce } from '@/lib/utils';
const debouncedSearch = debounce((query: string) => {
performSearch(query);
}, 300);
<Input onChange={(e) => debouncedSearch(e.target.value)} />import { detectBrowser, isBrowserSupported, isTouchDevice, addBrowserClasses } from '@/lib/browser-detection';
// Detect browser
const browser = detectBrowser();
console.log(browser.name, browser.version, browser.os);
// Check support
if (!isBrowserSupported()) {
showWarning();
}
// Check touch support
if (isTouchDevice()) {
enableTouchFeatures();
}
// Add classes to HTML
addBrowserClasses();
// Adds: browser-chrome os-windows device-desktopimport {
announceToScreenReader,
getStatusAriaLabel,
prefersReducedMotion,
createFormFieldProps
} from '@/lib/accessibility';
// Announce to screen reader
announceToScreenReader('Application saved successfully');
// Get ARIA label for status
const label = getStatusAriaLabel('applied'); // "Application submitted"
// Check reduced motion preference
if (prefersReducedMotion()) {
disableAnimations();
}
// Generate form field props
const fieldProps = createFormFieldProps('email', 'Email address', 'Enter your email', true);
// Returns: { id, name, 'aria-label', 'aria-describedby', 'aria-required' }import { Keys, isActionKey, handleListKeyDown, makeKeyboardAccessible } from '@/lib/keyboard';
// Check if key is action key (Enter/Space)
if (isActionKey(event)) {
performAction();
}
// Handle list keyboard navigation
<div onKeyDown={(e) => handleListKeyDown(e, items, selectedIndex, setSelectedIndex)}>
{items.map((item, index) => (
<div key={index} tabIndex={index === selectedIndex ? 0 : -1}>
{item}
</div>
))}
</div>
// Make element keyboard accessible
<div
{...makeKeyboardAccessible(handleClick, 'Click to view details')}
>
Content
</div>import { exportToCSV, exportToJSON, exportToPDF } from '@/lib/export';
// Export to CSV
exportToCSV(applications, 'applications.csv');
// Export to JSON
exportToJSON({ applications, interviews, companies }, 'data.json');
// Export to PDF
await exportToPDF(applications, 'applications.pdf');type ApplicationStatus =
| 'wishlist'
| 'applied'
| 'screening'
| 'phone-interview'
| 'interview'
| 'assessment'
| 'offer'
| 'accepted'
| 'rejected'
| 'withdrawn';
interface Application {
id: string;
position: string;
company: string;
status: ApplicationStatus;
appliedDate: string;
location?: string;
jobType?: 'remote' | 'hybrid' | 'onsite';
salaryRange?: SalaryRange;
description?: string;
url?: string;
notes?: Note[];
documents?: string[];
contacts?: Contact[];
createdAt: string;
updatedAt: string;
}type InterviewType =
| 'phone-screen'
| 'video-call'
| 'onsite'
| 'technical'
| 'behavioral'
| 'panel'
| 'final';
type InterviewStatus = 'scheduled' | 'completed' | 'cancelled';
interface Interview {
id: string;
applicationId: string;
type: InterviewType;
date: string;
duration: number;
location?: string;
isRemote: boolean;
meetingLink?: string;
interviewers?: Interviewer[];
notes?: string;
preparation?: string;
followUpDate?: string;
status: InterviewStatus;
createdAt: string;
updatedAt: string;
}interface Company {
id: string;
name: string;
website?: string;
industry?: string;
size?: string;
location?: string;
description?: string;
culture?: string;
benefits?: string[];
techStack?: string[];
interviewProcess?: string;
notes?: string;
rating?: number;
createdAt: string;
updatedAt: string;
}type DocumentType =
| 'resume'
| 'cover-letter'
| 'portfolio'
| 'certification'
| 'reference'
| 'other';
interface Document {
id: string;
name: string;
type: DocumentType;
fileType: string;
size: number;
url: string;
applicationIds?: string[];
tags?: string[];
version: number;
uploadedAt: string;
updatedAt: string;
}type NotificationType = 'success' | 'error' | 'warning' | 'info';
interface Notification {
id: string;
type: NotificationType;
title?: string;
message: string;
duration?: number;
action?: {
label: string;
onClick: () => void;
};
createdAt: string;
}Last Updated: October 18, 2025 Version: 1.0.0