Перейти к основному содержимому

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] // Результаты поиска