mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-16 17:59:39 +02:00
## New Features ### Network Graph Visualization (Contacts, Calendar, Todo) - D3.js force simulation for physics-based layout - Zoom & pan with mouse/touchpad - Keyboard shortcuts: +/- zoom, 0 reset, Esc deselect, / search, F focus - Filtering by tags, company/location/project, connection strength - Shared components in @manacore/shared-ui ### Central Tags API (mana-core-auth) - CRUD endpoints for tags - Schema: tags table with userId, name, color, app - Shared tag components in @manacore/shared-ui ### Custom Themes System - Theme editor with live preview and color picker - Community theme gallery - Theme sharing (public, unlisted, private) - Backend API in mana-core-auth ### Todo App Extensions - Glass-pill design for task input and items - Settings page with 20+ preferences - Task edit modal with inline editing - Statistics page with visualizations - PWA support with offline capabilities - Multiple kanban boards ### Contacts App Features - Duplicate detection - Photo upload - Batch operations - Enhanced favorites page with multiple view modes - Alphabet view improvements - Search modal ### Help System - @manacore/shared-help-content - @manacore/shared-help-ui - @manacore/shared-help-types ### Other Features - Themes page for all apps - Referral system frontend - CommandBar (global search) - Skeleton loaders - Settings page improvements ## Bug Fixes - Network graph simulation initialization - Database schema TEXT for user_id columns (Better Auth compatibility) - Various styling fixes ## Documentation - Daily report for 2025-12-10 - CI/CD deployment guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
206 lines
5.5 KiB
TypeScript
206 lines
5.5 KiB
TypeScript
/**
|
|
* Main Help Screen component for mobile apps
|
|
*/
|
|
|
|
import React, { useState, useMemo } from 'react';
|
|
import { View, Text, ScrollView, SafeAreaView } from 'react-native';
|
|
import type { HelpScreenProps, HelpSection } from '../types';
|
|
import { HelpSearchBar } from '../components/HelpSearchBar';
|
|
import { CategoryTabs } from '../components/CategoryTabs';
|
|
import { FAQList } from '../components/FAQList';
|
|
import { FeaturesList } from '../components/FeaturesList';
|
|
import { ContactCard } from '../components/ContactCard';
|
|
import { useHelpSearch } from '../hooks/useHelpContent';
|
|
import type { SearchResult } from '@manacore/shared-help-types';
|
|
|
|
export function HelpScreen({
|
|
content,
|
|
appName,
|
|
appId: _appId,
|
|
translations,
|
|
onBack: _onBack,
|
|
defaultSection = 'faq',
|
|
}: HelpScreenProps) {
|
|
const [activeSection, setActiveSection] = useState<HelpSection>(defaultSection);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
|
|
|
const { search } = useHelpSearch(content);
|
|
|
|
// Define available sections
|
|
const sections = useMemo(
|
|
() => [
|
|
{ id: 'faq' as HelpSection, label: translations.sections.faq, show: content.faq.length > 0 },
|
|
{
|
|
id: 'features' as HelpSection,
|
|
label: translations.sections.features,
|
|
show: content.features.length > 0,
|
|
},
|
|
{
|
|
id: 'shortcuts' as HelpSection,
|
|
label: translations.sections.shortcuts,
|
|
show: content.shortcuts.length > 0,
|
|
},
|
|
{
|
|
id: 'getting-started' as HelpSection,
|
|
label: translations.sections.gettingStarted,
|
|
show: content.gettingStarted.length > 0,
|
|
},
|
|
{
|
|
id: 'changelog' as HelpSection,
|
|
label: translations.sections.changelog,
|
|
show: content.changelog.length > 0,
|
|
},
|
|
{
|
|
id: 'contact' as HelpSection,
|
|
label: translations.sections.contact,
|
|
show: !!content.contact,
|
|
},
|
|
],
|
|
[content, translations]
|
|
);
|
|
|
|
function handleSearch(query: string) {
|
|
setSearchQuery(query);
|
|
if (query.trim().length >= 2) {
|
|
const results = search(query, 10);
|
|
setSearchResults(results);
|
|
} else {
|
|
setSearchResults([]);
|
|
}
|
|
}
|
|
|
|
function handleClearSearch() {
|
|
setSearchQuery('');
|
|
setSearchResults([]);
|
|
}
|
|
|
|
function handleResultPress(result: SearchResult) {
|
|
// Navigate to appropriate section
|
|
switch (result.type) {
|
|
case 'faq':
|
|
setActiveSection('faq');
|
|
break;
|
|
case 'feature':
|
|
setActiveSection('features');
|
|
break;
|
|
case 'guide':
|
|
setActiveSection('getting-started');
|
|
break;
|
|
case 'changelog':
|
|
setActiveSection('changelog');
|
|
break;
|
|
}
|
|
handleClearSearch();
|
|
}
|
|
|
|
// Use handleResultPress in search results (currently just viewing results)
|
|
void handleResultPress;
|
|
|
|
function renderContent() {
|
|
// Show search results if searching
|
|
if (searchQuery.length >= 2) {
|
|
if (searchResults.length === 0) {
|
|
return (
|
|
<View className="py-8 items-center">
|
|
<Text className="text-gray-500 dark:text-gray-400">
|
|
{translations.search.noResults.replace('{query}', searchQuery)}
|
|
</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View>
|
|
<Text className="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
|
{translations.search.resultsCount.replace('{count}', String(searchResults.length))}
|
|
</Text>
|
|
{searchResults.map((result) => (
|
|
<View
|
|
key={result.id}
|
|
className="bg-white dark:bg-gray-800 rounded-xl p-4 mb-2 border border-gray-200 dark:border-gray-700"
|
|
>
|
|
<Text className="font-medium text-gray-900 dark:text-gray-100">{result.title}</Text>
|
|
<Text className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
{result.excerpt}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
// Show section content
|
|
switch (activeSection) {
|
|
case 'faq':
|
|
return <FAQList items={content.faq} translations={translations} />;
|
|
case 'features':
|
|
return <FeaturesList items={content.features} translations={translations} />;
|
|
case 'contact':
|
|
return <ContactCard contact={content.contact} translations={translations} />;
|
|
case 'shortcuts':
|
|
return (
|
|
<View className="py-8 items-center">
|
|
<Text className="text-gray-500 dark:text-gray-400">
|
|
{translations.shortcuts.noItems}
|
|
</Text>
|
|
</View>
|
|
);
|
|
case 'getting-started':
|
|
return (
|
|
<View className="py-8 items-center">
|
|
<Text className="text-gray-500 dark:text-gray-400">
|
|
{translations.gettingStarted.noItems}
|
|
</Text>
|
|
</View>
|
|
);
|
|
case 'changelog':
|
|
return (
|
|
<View className="py-8 items-center">
|
|
<Text className="text-gray-500 dark:text-gray-400">
|
|
{translations.changelog.noItems}
|
|
</Text>
|
|
</View>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView className="flex-1 bg-gray-50 dark:bg-gray-900">
|
|
<ScrollView className="flex-1 px-4 pt-4">
|
|
{/* Header */}
|
|
<View className="mb-6">
|
|
<Text className="text-2xl font-bold text-gray-900 dark:text-gray-100">
|
|
{translations.title}
|
|
</Text>
|
|
{translations.subtitle && (
|
|
<Text className="text-gray-600 dark:text-gray-400 mt-1">
|
|
{translations.subtitle} - {appName}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
|
|
{/* Search */}
|
|
<HelpSearchBar
|
|
placeholder={translations.searchPlaceholder}
|
|
onSearch={handleSearch}
|
|
onClear={handleClearSearch}
|
|
/>
|
|
|
|
{/* Category Tabs */}
|
|
{searchQuery.length < 2 && (
|
|
<CategoryTabs
|
|
sections={sections}
|
|
activeSection={activeSection}
|
|
onSectionChange={setActiveSection}
|
|
/>
|
|
)}
|
|
|
|
{/* Content */}
|
|
<View className="pb-8">{renderContent()}</View>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|