Courses
Управление курсами пользователя — список, поиск, детали.
Структура
src/features/courses/
├── api/
│ ├── queries.ts # fetchCourses(), getCourse()
│ └── mutations.ts # createCourse(), deleteCourse()
├── hooks/
│ ├── use-courses.ts # Список курсов
│ ├── use-course.ts # Детали курса
│ ├── use-user-id.ts # Resolve auth UID → Prisma ID
│ ├── use-search-courses.ts
│ └── use-delete-course.ts
├── components/
│ ├── course-list.tsx # Сетка курсов
│ ├── course-card.tsx # Карточка курса
│ ├── course-header.tsx # Заголовок с фильтрами
│ ├── course-empty-state.tsx
│ ├── search-header.tsx
│ ├── module-accordion.tsx
│ └── progress-bar.tsx
├── types.ts
└── index.ts
Доменная модель
Course
├── id: string
├── title: string
├── description: string
├── language: 'en' | 'ru' | 'uk'
├── difficulty: 'beginner' | 'intermediate' | 'advanced'
├── imageUrl: string
├── progress: number (0-100)
├── userId: string
├── createdAt: Date
└── modules: Module[]
└── lessons: Lesson[]
Hooks
useCourses
Получить все курсы пользователя:
import { useCourses } from '@/features/courses';
function CoursesScreen() {
const { data: courses, isLoading, error } = useCourses();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return <CourseList courses={courses} />;
}
useCourse
Получить курс с модулями и уроками:
import { useCourse } from '@/features/courses';
function CourseDetailScreen() {
const { id } = useLocalSearchParams();
const { data: course, isLoading } = useCourse(id);
return (
<View>
<Text>{course?.title}</Text>
{course?.modules.map(module => (
<ModuleAccordion key={module.id} module={module} />
))}
</View>
);
}
useDeleteCourse
import { useDeleteCourse } from '@/features/courses';
function CourseCard({ course }) {
const { mutate: deleteCourse, isPending } = useDeleteCourse();
const handleDelete = () => {
Alert.alert(
'Удалить курс?',
'Это действие нельзя отменить',
[
{ text: 'Отмена', style: 'cancel' },
{
text: 'Удалить',
style: 'destructive',
onPress: () => deleteCourse(course.id),
},
]
);
};
}
Компоненты
CourseList
Сетка курсов с pull-to-refresh:
<CourseList
courses={courses}
onRefresh={refetch}
isRefreshing={isRefetching}
/>
CourseCard
Карточка с изображением, прогрессом, swipe-to-delete:
<CourseCard
course={course}
onPress={() => router.push(`/course/${course.id}`)}
onDelete={() => deleteCourse(course.id)}
/>
ModuleAccordion
Раскрывающийся список модулей с уроками:
<ModuleAccordion
module={module}
isExpanded={expandedId === module.id}
onToggle={() => setExpandedId(module.id)}
/>
useUserId Hook
Конвертирует Supabase Auth UID в Prisma User ID:
// Supabase Auth возвращает UUID
const authId = session.user.id; // "auth-uuid-123"
// Prisma использует свой ID
const userId = useUserId(); // "prisma-user-456"
// Все API запросы используют userId
const { data } = useCourses(); // внутри использует userId
Экраны
| Экран | Путь | Описание |
|---|---|---|
| Courses List | (app)/(tabs)/index | Главный экран с курсами |
| Course Detail | (app)/course/[id] | Детали курса с модулями |
Query Keys
['courses', userId] // Все курсы пользователя
['course', courseId] // Один курс с модулями/уроками
['courses', 'search', query] // Результаты поиска