W tym projekcie stworzysz nowoczesną aplikację React wykorzystującą wszystkie poznane funkcjonalności React 18+. Będziesz budować Advanced Task Management System - zaawansowany system zarządzania zadaniami z wykorzystaniem Concurrent Features, Server Components i nowoczesnych hooków.
Dashboard z real-time updates
Zaawansowane formularze
System notyfikacji
Wydajność i optymalizacja
1src/
2├── app/
3│ ├── layout.tsx # Root layout (Server Component)
4│ ├── page.tsx # Home page
5│ ├── dashboard/
6│ │ ├── page.tsx # Dashboard (Server Component)
7│ │ └── loading.tsx # Loading UI
8│ ├── tasks/
9│ │ ├── page.tsx # Tasks list
10│ │ ├── [id]/page.tsx # Task details
11│ │ └── new/page.tsx # Create task
12│ └── api/
13│ ├── tasks/route.ts # Tasks API
14│ └── notifications/route.ts
15├── components/
16│ ├── ui/ # Basic UI components
17│ ├── forms/ # Form components
18│ ├── dashboard/ # Dashboard components
19│ └── tasks/ # Task components
20├── hooks/ # Custom hooks
21├── lib/ # Utilities
22└── types/ # TypeScript types1// types/index.ts
2export interface Task {
3 id: string;
4 title: string;
5 description: string;
6 status: 'pending' | 'in_progress' | 'completed';
7 priority: 'low' | 'medium' | 'high';
8 assignee?: string;
9 dueDate?: Date;
10 createdAt: Date;
11 updatedAt: Date;
12}
13
14export interface User {
15 id: string;
16 name: string;
17 email: string;
18 avatar?: string;
19}
20
21export interface Notification {
22 id: string;
23 type: 'task_created' | 'task_updated' | 'task_completed';
24 message: string;
25 timestamp: Date;
26 read: boolean;
27}1// app/layout.tsx (Server Component)
2import { Suspense } from 'react';
3import { Navigation } from '@/components/Navigation';
4import { NotificationProvider } from '@/components/NotificationProvider';
5
6async function RootLayout({ children }: { children: React.ReactNode }) {
7 // Pobierz dane użytkownika na serwerze
8 const user = await getCurrentUser();
9 const notifications = await getUnreadNotifications(user.id);
10
11 return (
12 <html lang="pl">
13 <head>
14 <title>Advanced Task Manager</title>
15 <meta name="description" content="Zaawansowany system zarządzania zadaniami" />
16 </head>
17 <body>
18 <NotificationProvider initialNotifications={notifications}>
19 <div className="app-layout">
20 <Suspense fallback={<NavigationSkeleton />}>
21 <Navigation user={user} />
22 </Suspense>
23
24 <main className="main-content">
25 <Suspense fallback={<PageSkeleton />}>
26 {children}
27 </Suspense>
28 </main>
29 </div>
30 </NotificationProvider>
31 </body>
32 </html>
33 );
34}
35
36export default RootLayout;1// app/dashboard/page.tsx (Server Component)
2import { Suspense } from 'react';
3import { TasksOverview } from '@/components/dashboard/TasksOverview';
4import { RecentActivity } from '@/components/dashboard/RecentActivity';
5import { PerformanceMetrics } from '@/components/dashboard/PerformanceMetrics';
6
7async function DashboardPage() {
8 // Równoległe pobieranie danych
9 const [tasksStats, recentTasks] = await Promise.all([
10 getTasksStatistics(),
11 getRecentTasks(10)
12 ]);
13
14 return (
15 <div className="dashboard">
16 <h1>Dashboard</h1>
17
18 {/* Szybko ładująca się sekcja */}
19 <TasksOverview stats={tasksStats} />
20
21 <div className="dashboard-grid">
22 {/* Streams niezależnie */}
23 <Suspense fallback={<RecentActivitySkeleton />}>
24 <RecentActivity initialTasks={recentTasks} />
25 </Suspense>
26
27 {/* Może ładować się później */}
28 <Suspense fallback={<MetricsSkeleton />}>
29 <PerformanceMetrics />
30 </Suspense>
31 </div>
32 </div>
33 );
34}
35
36export default DashboardPage;1// components/dashboard/RecentActivity.tsx (Client Component)
2'use client';
3
4import { useState, useTransition, useDeferredValue } from 'react';
5import { Task } from '@/types';
6
7interface RecentActivityProps {
8 initialTasks: Task[];
9}
10
11export function RecentActivity({ initialTasks }: RecentActivityProps) {
12 const [tasks, setTasks] = useState(initialTasks);
13 const [searchQuery, setSearchQuery] = useState('');
14 const [isPending, startTransition] = useTransition();
15
16 // Opóźniona wartość dla wyszukiwania
17 const deferredSearchQuery = useDeferredValue(searchQuery);
18
19 // Filtrowanie zadań z wykorzystaniem deferredValue
20 const filteredTasks = tasks.filter(task =>
21 task.title.toLowerCase().includes(deferredSearchQuery.toLowerCase()) ||
22 task.description.toLowerCase().includes(deferredSearchQuery.toLowerCase())
23 );
24
25 const handleSearch = (query: string) => {
26 setSearchQuery(query);
27
28 // Użyj transition dla mniej priorytetowych aktualizacji
29 startTransition(() => {
30 // Dodatkowe operacje które mogą być przerwane
31 updateSearchAnalytics(query);
32 });
33 };
34
35 const refreshTasks = async () => {
36 startTransition(async () => {
37 const newTasks = await fetchRecentTasks();
38 setTasks(newTasks);
39 });
40 };
41
42 return (
43 <div className="recent-activity">
44 <div className="activity-header">
45 <h2>Ostatnia aktywność</h2>
46 <button
47 onClick={refreshTasks}
48 disabled={isPending}
49 className={isPending ? 'loading' : ''}
50 >
51 {isPending ? 'Odświeżanie...' : 'Odśwież'}
52 </button>
53 </div>
54
55 <SearchInput
56 value={searchQuery}
57 onChange={handleSearch}
58 isPending={isPending}
59 />
60
61 <div className="tasks-list">
62 {filteredTasks.map(task => (
63 <TaskCard
64 key={task.id}
65 task={task}
66 onUpdate={setTasks}
67 />
68 ))}
69
70 {deferredSearchQuery && filteredTasks.length === 0 && (
71 <div className="no-results">
72 Brak wyników dla "{deferredSearchQuery}"
73 </div>
74 )}
75 </div>
76 </div>
77 );
78}1// components/forms/TaskForm.tsx (Client Component)
2'use client';
3
4import { useActionState, useFormStatus } from 'react';
5import { useId, useOptimistic } from 'react';
6import { createTask, updateTask } from '@/lib/actions';
7
8interface TaskFormProps {
9 task?: Task;
10 onSuccess?: () => void;
11}
12
13export function TaskForm({ task, onSuccess }: TaskFormProps) {
14 const formId = useId();
15 const titleId = useId();
16 const descriptionId = useId();
17
18 // useActionState dla zarządzania stanem akcji
19 const [state, formAction] = useActionState(
20 task ? updateTask.bind(null, task.id) : createTask,
21 {
22 success: false,
23 errors: {},
24 data: task || null
25 }
26 );
27
28 // useOptimistic dla natychmiastowej informacji zwrotnej
29 const [optimisticState, addOptimistic] = useOptimistic(
30 state,
31 (currentState, optimisticUpdate) => ({
32 ...currentState,
33 ...optimisticUpdate,
34 optimistic: true
35 })
36 );
37
38 const handleSubmit = async (formData: FormData) => {
39 // Optymistyczna aktualizacja
40 addOptimistic({
41 success: true,
42 data: {
43 id: task?.id || 'temp-' + Date.now(),
44 title: formData.get('title') as string,
45 description: formData.get('description') as string,
46 status: formData.get('status') as Task['status'],
47 priority: formData.get('priority') as Task['priority'],
48 createdAt: new Date(),
49 updatedAt: new Date()
50 }
51 });
52
53 // Wykonaj rzeczywistą akcję
54 await formAction(formData);
55
56 if (state.success) {
57 onSuccess?.();
58 }
59 };
60
61 return (
62 <form
63 id={formId}
64 action={handleSubmit}
65 className="task-form"
66 >
67 <div className="form-group">
68 <label htmlFor={titleId}>Tytuł zadania</label>
69 <input
70 id={titleId}
71 name="title"
72 type="text"
73 required
74 defaultValue={optimisticState.data?.title || ''}
75 className={optimisticState.errors?.title ? 'error' : ''}
76 />
77 {optimisticState.errors?.title && (
78 <span className="error-message" role="alert">
79 {optimisticState.errors.title}
80 </span>
81 )}
82 </div>
83
84 <div className="form-group">
85 <label htmlFor={descriptionId}>Opis</label>
86 <textarea
87 id={descriptionId}
88 name="description"
89 rows={4}
90 defaultValue={optimisticState.data?.description || ''}
91 />
92 </div>
93
94 <div className="form-group">
95 <label>Status</label>
96 <select name="status" defaultValue={optimisticState.data?.status || 'pending'}>
97 <option value="pending">Oczekujące</option>
98 <option value="in_progress">W trakcie</option>
99 <option value="completed">Zakończone</option>
100 </select>
101 </div>
102
103 <div className="form-group">
104 <label>Priorytet</label>
105 <select name="priority" defaultValue={optimisticState.data?.priority || 'medium'}>
106 <option value="low">Niski</option>
107 <option value="medium">Średni</option>
108 <option value="high">Wysoki</option>
109 </select>
110 </div>
111
112 <FormSubmitButton />
113
114 {optimisticState.optimistic && (
115 <div className="optimistic-indicator">
116 Zapisywanie...
117 </div>
118 )}
119
120 {optimisticState.success && !optimisticState.optimistic && (
121 <div className="success-message">
122 Zadanie zostało {task ? 'zaktualizowane' : 'utworzone'}!
123 </div>
124 )}
125 </form>
126 );
127}
128
129// Komponent wykorzystujący useFormStatus
130function FormSubmitButton() {
131 const { pending } = useFormStatus();
132
133 return (
134 <button
135 type="submit"
136 disabled={pending}
137 className={pending ? 'loading' : ''}
138 >
139 {pending ? 'Zapisywanie...' : 'Zapisz zadanie'}
140 </button>
141 );
142}1// lib/actions.ts
2'use server';
3
4import { revalidatePath } from 'next/cache';
5import { redirect } from 'next/navigation';
6import { z } from 'zod';
7
8const TaskSchema = z.object({
9 title: z.string().min(1, 'Tytuł jest wymagany').max(100, 'Tytuł jest za długi'),
10 description: z.string().optional(),
11 status: z.enum(['pending', 'in_progress', 'completed']),
12 priority: z.enum(['low', 'medium', 'high']),
13 dueDate: z.string().optional().transform(str => str ? new Date(str) : undefined)
14});
15
16export async function createTask(prevState: any, formData: FormData) {
17 try {
18 const validatedFields = TaskSchema.safeParse({
19 title: formData.get('title'),
20 description: formData.get('description'),
21 status: formData.get('status'),
22 priority: formData.get('priority'),
23 dueDate: formData.get('dueDate')
24 });
25
26 if (!validatedFields.success) {
27 return {
28 success: false,
29 errors: validatedFields.error.flatten().fieldErrors,
30 data: null
31 };
32 }
33
34 const task = await db.tasks.create({
35 data: {
36 ...validatedFields.data,
37 id: generateId(),
38 createdAt: new Date(),
39 updatedAt: new Date()
40 }
41 });
42
43 // Revalidate cache
44 revalidatePath('/dashboard');
45 revalidatePath('/tasks');
46
47 return {
48 success: true,
49 errors: {},
50 data: task
51 };
52
53 } catch (error) {
54 return {
55 success: false,
56 errors: { _form: ['Wystąpił błąd podczas tworzenia zadania'] },
57 data: null
58 };
59 }
60}
61
62export async function updateTask(taskId: string, prevState: any, formData: FormData) {
63 try {
64 const validatedFields = TaskSchema.safeParse({
65 title: formData.get('title'),
66 description: formData.get('description'),
67 status: formData.get('status'),
68 priority: formData.get('priority'),
69 dueDate: formData.get('dueDate')
70 });
71
72 if (!validatedFields.success) {
73 return {
74 success: false,
75 errors: validatedFields.error.flatten().fieldErrors,
76 data: null
77 };
78 }
79
80 const task = await db.tasks.update({
81 where: { id: taskId },
82 data: {
83 ...validatedFields.data,
84 updatedAt: new Date()
85 }
86 });
87
88 revalidatePath(`/tasks/${taskId}`);
89 revalidatePath('/dashboard');
90
91 return {
92 success: true,
93 errors: {},
94 data: task
95 };
96
97 } catch (error) {
98 return {
99 success: false,
100 errors: { _form: ['Wystąpił błąd podczas aktualizacji zadania'] },
101 data: null
102 };
103 }
104}1// components/tasks/TasksList.tsx (Server Component)
2import { Suspense } from 'react';
3import { TaskCard } from './TaskCard';
4import { TasksFilter } from './TasksFilter';
5
6async function TasksList({ searchParams }: { searchParams: any }) {
7 return (
8 <div className="tasks-page">
9 <div className="tasks-header">
10 <h1>Zadania</h1>
11
12 {/* Filter nie wymaga Suspense - szybko się ładuje */}
13 <TasksFilter />
14 </div>
15
16 <div className="tasks-content">
17 {/* Główna lista zadań */}
18 <Suspense fallback={<TasksListSkeleton />}>
19 <TasksGrid searchParams={searchParams} />
20 </Suspense>
21
22 {/* Sidebar z dodatkowymi informacjami */}
23 <aside className="tasks-sidebar">
24 <Suspense fallback={<StatsSkeleton />}>
25 <TasksStatistics />
26 </Suspense>
27
28 <Suspense fallback={<ActivitySkeleton />}>
29 <RecentActivity />
30 </Suspense>
31 </aside>
32 </div>
33 </div>
34 );
35}
36
37// Komponent z długim czasem ładowania
38async function TasksGrid({ searchParams }: { searchParams: any }) {
39 // Symuluj długie zapytanie
40 const tasks = await getFilteredTasks(searchParams);
41
42 return (
43 <div className="tasks-grid">
44 {tasks.map(task => (
45 <Suspense
46 key={task.id}
47 fallback={<TaskCardSkeleton />}
48 >
49 <TaskCard task={task} />
50 </Suspense>
51 ))}
52 </div>
53 );
54}1// components/ui/FormField.tsx
2'use client';
3
4import { useId, forwardRef } from 'react';
5
6interface FormFieldProps {
7 label: string;
8 error?: string;
9 help?: string;
10 required?: boolean;
11 children: React.ReactNode;
12}
13
14export const FormField = forwardRef<HTMLDivElement, FormFieldProps>(
15 ({ label, error, help, required, children }, ref) => {
16 const fieldId = useId();
17 const errorId = useId();
18 const helpId = useId();
19
20 return (
21 <div ref={ref} className="form-field">
22 <label
23 htmlFor={fieldId}
24 className={required ? 'required' : ''}
25 >
26 {label}
27 {required && <span aria-hidden="true">*</span>}
28 </label>
29
30 <div className="field-input">
31 {React.cloneElement(children as React.ReactElement, {
32 id: fieldId,
33 'aria-describedby': [
34 help ? helpId : null,
35 error ? errorId : null
36 ].filter(Boolean).join(' ') || undefined,
37 'aria-invalid': error ? 'true' : undefined,
38 required
39 })}
40 </div>
41
42 {help && (
43 <div id={helpId} className="field-help">
44 {help}
45 </div>
46 )}
47
48 {error && (
49 <div
50 id={errorId}
51 className="field-error"
52 role="alert"
53 aria-live="polite"
54 >
55 {error}
56 </div>
57 )}
58 </div>
59 );
60 }
61);
62
63FormField.displayName = 'FormField';1// components/providers/PerformanceProvider.tsx
2'use client';
3
4import { createContext, useContext, useEffect, useState } from 'react';
5
6interface PerformanceMetrics {
7 renderTime: number;
8 interactionTime: number;
9 suspenseTime: number;
10}
11
12const PerformanceContext = createContext<{
13 metrics: PerformanceMetrics;
14 recordMetric: (type: keyof PerformanceMetrics, time: number) => void;
15}>({
16 metrics: { renderTime: 0, interactionTime: 0, suspenseTime: 0 },
17 recordMetric: () => {}
18});
19
20export function PerformanceProvider({ children }: { children: React.ReactNode }) {
21 const [metrics, setMetrics] = useState<PerformanceMetrics>({
22 renderTime: 0,
23 interactionTime: 0,
24 suspenseTime: 0
25 });
26
27 const recordMetric = (type: keyof PerformanceMetrics, time: number) => {
28 setMetrics(prev => ({
29 ...prev,
30 [type]: time
31 }));
32
33 // Wyślij metryki do systemu analitycznego
34 if (time > getThreshold(type)) {
35 analytics.track('performance_issue', {
36 type,
37 time,
38 url: window.location.pathname
39 });
40 }
41 };
42
43 useEffect(() => {
44 // Obserwuj Web Vitals
45 const observer = new PerformanceObserver((list) => {
46 for (const entry of list.getEntries()) {
47 if (entry.entryType === 'measure') {
48 recordMetric('renderTime', entry.duration);
49 }
50 }
51 });
52
53 observer.observe({ entryTypes: ['measure'] });
54
55 return () => observer.disconnect();
56 }, []);
57
58 return (
59 <PerformanceContext.Provider value={{ metrics, recordMetric }}>
60 {children}
61 </PerformanceContext.Provider>
62 );
63}
64
65export const usePerformance = () => useContext(PerformanceContext);
66
67function getThreshold(type: keyof PerformanceMetrics): number {
68 const thresholds = {
69 renderTime: 100, // 100ms
70 interactionTime: 50, // 50ms
71 suspenseTime: 1000 // 1s
72 };
73 return thresholds[type];
74}Ten projekt demonstruje:
Ten projekt to solidna podstawa do budowania nowoczesnych aplikacji React wykorzystujących najnowsze funkcjonalności frameworka.