Самое популярное приложение после Hello World на react - это личный планировщик задач Todolist и мы не будем сильно оригинальничать и напишем его с нуля на react. Разместим приложение в docker контейнере и поможет нам в этом Cursor AI IDE, а точнее сделает все за нас.
Разрабатывать приложение будем в ОС Windows 10, упакуем в docker контейнер и после разместим на хостинге.
Скачаем и установим Cursor IDE.

Установим Node.js на Windows, для этого скачаем и установим Node.js LTS, проверим установку командой:
node -v
Если установка прошла успешно, в терминале отобразится установленная версия Node.js, теперь инициализируем новый проект:
npm create vite@latest todolist -- --template react
При получении ошибки UnauthorizedAccess
в терминале:
Скрытый текст
Откройте PowerShell от имени администратора
Проверьте Текущую Политику Выполнения
Get-ExecutionPolicy
Если он показывает "Ограничено", вам нужно его изменить.
Установите Политику выполнения на Неограниченную
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Проверьте изменение: запустите этот cmd
enter code here
Повторите попытку выполнения команды
npm create vite@latest todolist -- --template react
Запустим приложение npm start
в результате получим следующий вывод:
You can now view todolist in the browser.
Local: http://localhost:3000
Note that the development build is not optimized.
To create a production build, use npm run build.

Мы запустили простейшее приложение на react, а значит подготовили рабочее окружение для разработки собственного приложения на react.
Начнем и озадачим чат, написав ему следующее:

После попытки запустить приложение я получил кучу ошибок
Скрытый текст
ERROR in src/App.tsx:1:33
TS7016: Could not find a declaration file for module 'react'. 'C:/Разработка/todolist/node_modules/react/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react';`
> 1 | import React, { useState } from 'react';
| ^^^^^^^
2 | import './App.css';
3 |
4 | interface Todo {
ERROR in src/App.tsx:31:24
TS7006: Parameter 'todo' implicitly has an 'any' type.
29 |
30 | const handleToggleTodo = (id: number) => {
> 31 | setTodos(todos.map(todo =>
| ^^^^
32 | todo.id === id ? { ...todo, completed: !todo.completed } : todo
33 | ));
34 | };
ERROR in src/App.tsx:37:27
TS7006: Parameter 'todo' implicitly has an 'any' type.
35 |
36 | const handleDeleteTodo = (id: number) => {
> 37 | setTodos(todos.filter(todo => todo.id !== id));
| ^^^^
38 | };
39 |
40 | const filteredTodos = todos.filter(todo => {
ERROR in src/App.tsx:40:38
TS7006: Parameter 'todo' implicitly has an 'any' type.
38 | };
39 |
> 40 | const filteredTodos = todos.filter(todo => {
| ^^^^
41 | if (filter === 'active') return !todo.completed;
42 | if (filter === 'completed') return todo.completed;
43 | return true;
ERROR in src/App.tsx:46:39
TS7006: Parameter 'todo' implicitly has an 'any' type.
44 | });
45 |
> 46 | const remainingTasks = todos.filter(todo => !todo.completed).length;
| ^^^^
47 |
48 | return (
49 | <div className="App">
ERROR in src/App.tsx:49:5
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
47 |
48 | return (
> 49 | <div className="App">
| ^^^^^^^^^^^^^^^^^^^^^
50 | <div className="container">
51 | <h1>Todo List</h1>
52 | <form onSubmit={handleAddTodo} className="todo-form">
ERROR in src/App.tsx:49:5
TS7016: Could not find a declaration file for module 'react/jsx-runtime'. 'C:/Разработка/todolist/node_modules/react/jsx-runtime.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react/jsx-runtime';`
47 |
48 | return (
> 49 | <div className="App">
| ^^^^^^^^^^^^^^^^^^^^^
> 50 | <div className="container">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 51 | <h1>Todo List</h1>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 52 | <form onSubmit={handleAddTodo} className="todo-form">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 53 | <input
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 54 | type="text"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 55 | value={inputValue}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 56 | onChange={(e) => setInputValue(e.target.value)}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 57 | placeholder="Add a new task..."
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 58 | className="todo-input"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 59 | />
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 60 | <button type="submit" className="add-button">Add</button>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 61 | </form>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 62 |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 63 | <div className="filters">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 64 | <button
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 65 | className={`filter-button ${filter === 'all' ? 'active' : ''}`}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 66 | onClick={() => setFilter('all')}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 67 | >
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 68 | All
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 69 | </button>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 70 | <button
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 71 | className={`filter-button ${filter === 'active' ? 'active' : ''}`}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 72 | onClick={() => setFilter('active')}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 73 | >
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 74 | Active
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 75 | </button>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 76 | <button
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 77 | className={`filter-button ${filter === 'completed' ? 'active' : ''}`}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 78 | onClick={() => setFilter('completed')}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 79 | >
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 80 | Completed
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 81 | </button>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 82 | </div>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 83 |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 84 | <div className="todo-stats">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 85 | {remainingTasks} {remainingTasks === 1 ? 'task' : 'tasks'} remaining
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 86 | </div>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 87 |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 88 | <ul className="todo-list">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 89 | {filteredTodos.map(todo => (
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 90 | <li key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 91 | <span
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 92 | onClick={() => handleToggleTodo(todo.id)}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 93 | className="todo-text"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 94 | >
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 95 | {todo.text}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 96 | </span>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 97 | <button
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 98 | onClick={() => handleDeleteTodo(todo.id)}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 99 | className="delete-button"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 100 | >
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 101 | Delete
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 102 | </button>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 103 | </li>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 104 | ))}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 105 | </ul>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 106 | </div>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 107 | </div>
| ^^^^^^^^^^^
108 | );
109 | }
110 |
ERROR in src/App.tsx:50:7
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
48 | return (
49 | <div className="App">
> 50 | <div className="container">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
51 | <h1>Todo List</h1>
52 | <form onSubmit={handleAddTodo} className="todo-form">
53 | <input
ERROR in src/App.tsx:51:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
49 | <div className="App">
50 | <div className="container">
> 51 | <h1>Todo List</h1>
| ^^^^
52 | <form onSubmit={handleAddTodo} className="todo-form">
53 | <input
54 | type="text"
ERROR in src/App.tsx:51:22
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
49 | <div className="App">
50 | <div className="container">
> 51 | <h1>Todo List</h1>
| ^^^^^
52 | <form onSubmit={handleAddTodo} className="todo-form">
53 | <input
54 | type="text"
ERROR in src/App.tsx:52:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
50 | <div className="container">
51 | <h1>Todo List</h1>
> 52 | <form onSubmit={handleAddTodo} className="todo-form">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
53 | <input
54 | type="text"
55 | value={inputValue}
ERROR in src/App.tsx:53:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
51 | <h1>Todo List</h1>
52 | <form onSubmit={handleAddTodo} className="todo-form">
> 53 | <input
| ^^^^^^
> 54 | type="text"
| ^^^^^^^^^^^^^^^^^^^^^^^
> 55 | value={inputValue}
| ^^^^^^^^^^^^^^^^^^^^^^^
> 56 | onChange={(e) => setInputValue(e.target.value)}
| ^^^^^^^^^^^^^^^^^^^^^^^
> 57 | placeholder="Add a new task..."
| ^^^^^^^^^^^^^^^^^^^^^^^
> 58 | className="todo-input"
| ^^^^^^^^^^^^^^^^^^^^^^^
> 59 | />
| ^^^^^^^^^^^^^
60 | <button type="submit" className="add-button">Add</button>
61 | </form>
62 |
ERROR in src/App.tsx:56:24
TS7006: Parameter 'e' implicitly has an 'any' type.
54 | type="text"
55 | value={inputValue}
> 56 | onChange={(e) => setInputValue(e.target.value)}
| ^
57 | placeholder="Add a new task..."
58 | className="todo-input"
59 | />
ERROR in src/App.tsx:60:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
58 | className="todo-input"
59 | />
> 60 | <button type="submit" className="add-button">Add</button>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
61 | </form>
62 |
63 | <div className="filters">
ERROR in src/App.tsx:60:59
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
58 | className="todo-input"
59 | />
> 60 | <button type="submit" className="add-button">Add</button>
| ^^^^^^^^^
61 | </form>
62 |
63 | <div className="filters">
ERROR in src/App.tsx:61:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
59 | />
60 | <button type="submit" className="add-button">Add</button>
> 61 | </form>
| ^^^^^^^
62 |
63 | <div className="filters">
64 | <button
ERROR in src/App.tsx:63:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
61 | </form>
62 |
> 63 | <div className="filters">
| ^^^^^^^^^^^^^^^^^^^^^^^^^
64 | <button
65 | className={`filter-button ${filter === 'all' ? 'active' : ''}`}
66 | onClick={() => setFilter('all')}
ERROR in src/App.tsx:64:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
62 |
63 | <div className="filters">
> 64 | <button
| ^^^^^^^
> 65 | className={`filter-button ${filter === 'all' ? 'active' : ''}`}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 66 | onClick={() => setFilter('all')}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 67 | >
| ^^^^^^^^^^^^
68 | All
69 | </button>
70 | <button
ERROR in src/App.tsx:69:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
67 | >
68 | All
> 69 | </button>
| ^^^^^^^^^
70 | <button
71 | className={`filter-button ${filter === 'active' ? 'active' : ''}`}
72 | onClick={() => setFilter('active')}
ERROR in src/App.tsx:70:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
68 | All
69 | </button>
> 70 | <button
| ^^^^^^^
> 71 | className={`filter-button ${filter === 'active' ? 'active' : ''}`}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 72 | onClick={() => setFilter('active')}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 73 | >
| ^^^^^^^^^^^^
74 | Active
75 | </button>
76 | <button
ERROR in src/App.tsx:75:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
73 | >
74 | Active
> 75 | </button>
| ^^^^^^^^^
76 | <button
77 | className={`filter-button ${filter === 'completed' ? 'active' : ''}`}
78 | onClick={() => setFilter('completed')}
ERROR in src/App.tsx:76:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
74 | Active
75 | </button>
> 76 | <button
| ^^^^^^^
> 77 | className={`filter-button ${filter === 'completed' ? 'active' : ''}`}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 78 | onClick={() => setFilter('completed')}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 79 | >
| ^^^^^^^^^^^^
80 | Completed
81 | </button>
82 | </div>
ERROR in src/App.tsx:81:11
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
79 | >
80 | Completed
> 81 | </button>
| ^^^^^^^^^
82 | </div>
83 |
84 | <div className="todo-stats">
ERROR in src/App.tsx:82:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
80 | Completed
81 | </button>
> 82 | </div>
| ^^^^^^
83 |
84 | <div className="todo-stats">
85 | {remainingTasks} {remainingTasks === 1 ? 'task' : 'tasks'} remaining
ERROR in src/App.tsx:84:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
82 | </div>
83 |
> 84 | <div className="todo-stats">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
85 | {remainingTasks} {remainingTasks === 1 ? 'task' : 'tasks'} remaining
86 | </div>
87 |
ERROR in src/App.tsx:86:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
84 | <div className="todo-stats">
85 | {remainingTasks} {remainingTasks === 1 ? 'task' : 'tasks'} remaining
> 86 | </div>
| ^^^^^^
87 |
88 | <ul className="todo-list">
89 | {filteredTodos.map(todo => (
ERROR in src/App.tsx:88:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
86 | </div>
87 |
> 88 | <ul className="todo-list">
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
89 | {filteredTodos.map(todo => (
90 | <li key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
91 | <span
ERROR in src/App.tsx:89:30
TS7006: Parameter 'todo' implicitly has an 'any' type.
87 |
88 | <ul className="todo-list">
> 89 | {filteredTodos.map(todo => (
| ^^^^
90 | <li key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
91 | <span
92 | onClick={() => handleToggleTodo(todo.id)}
ERROR in src/App.tsx:90:13
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
88 | <ul className="todo-list">
89 | {filteredTodos.map(todo => (
> 90 | <li key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91 | <span
92 | onClick={() => handleToggleTodo(todo.id)}
93 | className="todo-text"
ERROR in src/App.tsx:91:15
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
89 | {filteredTodos.map(todo => (
90 | <li key={todo.id} className={`todo-item ${todo.completed ? 'completed' : ''}`}>
> 91 | <span
| ^^^^^
> 92 | onClick={() => handleToggleTodo(todo.id)}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 93 | className="todo-text"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 94 | >
| ^^^^^^^^^^^^^^^^
95 | {todo.text}
96 | </span>
97 | <button
ERROR in src/App.tsx:96:15
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
94 | >
95 | {todo.text}
> 96 | </span>
| ^^^^^^^
97 | <button
98 | onClick={() => handleDeleteTodo(todo.id)}
99 | className="delete-button"
ERROR in src/App.tsx:97:15
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
95 | {todo.text}
96 | </span>
> 97 | <button
| ^^^^^^^
> 98 | onClick={() => handleDeleteTodo(todo.id)}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 99 | className="delete-button"
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 100 | >
| ^^^^^^^^^^^^^^^^
101 | Delete
102 | </button>
103 | </li>
ERROR in src/App.tsx:102:15
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
100 | >
101 | Delete
> 102 | </button>
| ^^^^^^^^^
103 | </li>
104 | ))}
105 | </ul>
ERROR in src/App.tsx:103:13
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
101 | Delete
102 | </button>
> 103 | </li>
| ^^^^^
104 | ))}
105 | </ul>
106 | </div>
ERROR in src/App.tsx:105:9
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
103 | </li>
104 | ))}
> 105 | </ul>
| ^^^^^
106 | </div>
107 | </div>
108 | );
ERROR in src/App.tsx:106:7
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
104 | ))}
105 | </ul>
> 106 | </div>
| ^^^^^^
107 | </div>
108 | );
109 | }
ERROR in src/App.tsx:107:5
TS7026: JSX element implicitly has type 'any' because no interface 'JSX.IntrinsicElements' exists.
105 | </ul>
106 | </div>
> 107 | </div>
| ^^^^^^
108 | );
109 | }
110 |
ERROR in src/index.tsx:1:19
TS7016: Could not find a declaration file for module 'react'. 'C:/Разработка/todolist/node_modules/react/index.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react';`
> 1 | import React from 'react';
| ^^^^^^^
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
ERROR in src/index.tsx:2:22
TS7016: Could not find a declaration file for module 'react-dom/client'. 'C:/Разработка/todolist/node_modules/react-dom/client.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/react-dom` if it exists or add a new declaration (.d.ts) file containing `declare module 'react-dom/client';`
1 | import React from 'react';
> 2 | import ReactDOM from 'react-dom/client';
| ^^^^^^^^^^^^^^^^^^
3 | import './index.css';
4 | import App from './App';
5 |
ERROR in src/index.tsx:11:3
TS7016: Could not find a declaration file for module 'react/jsx-runtime'. 'C:/Разработка/todolist/node_modules/react/jsx-runtime.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/react` if it exists or add a new declaration (.d.ts) file containing `declare module 'react/jsx-runtime';`
9 |
10 | root.render(
> 11 | <React.StrictMode>
| ^^^^^^^^^^^^^^^^^^
> 12 | <App />
| ^^^^^^^^^^^
> 13 | </React.StrictMode>
| ^^^^^^^^^^^^^^^^^^^^^^
14 | );
Не придумал ничего лучше, как скормить их все в чат Cursor

и не поверите он все тут же поправил, после запуска открылось приложение:

и отчитался, что сделал

На этом у нас есть готовое приложение, которое можно улучшать и дорабатывать, давая команды ИИ, но приложение работает локально. Останавливаем терминал и оно отключилось.
Теперь наша задача перенести его в интернет. Для того, чтобы запустить приложение мы разворачивали окружение, которое нужно переносить на другой сервер в интернете, чтобы упростить задачу, будем использовать docker на продакт сервере.

Попросим чат сформировать dockerfile для создания докер образа, содержащий все необходимые команды для развертки аналогичного окружения в один клик.
Скрытый текст
# Build stage
FROM node:18-alpine as build
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Production stage
FROM nginx:alpine
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built files from build stage
COPY --from=build /app/build /usr/share/nginx/html
# Expose port 80
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]
ИИ подготовил dockerfile и конфиг nginx.conf, осталось загрузить весь проект на GitHub, зарегистрировать хостинг, сбилдить образ и запустить контейнер с приложением. Звучит сложно, на самом деле все просто.
Не буду заострять внимание, как отправить проект в github, на самом деле много информации в интернете и все сводится к установке git локально и подключении удаленного репозитория в Cursor

Запушим и получим код проекта в репозитории Github.

Далее разворачиваем сервер, для пробы используем сервер за 1 рубль в dockerhosting.ru, на созданном инстансе уже установлен docker и portainer.


Выполним сборку образа и запуск контейнера. Сборку образа сделаем сделаем в portainer

В portainer перейдем в раздел images и кликнем Build a new image

Укажем наименование наименование todolist и путь репозиторию Github.

Кликаем Build the image и ждем, после проверяем, что новый образ появился

Запуск контейнера выполним, так как нам подсказал наш помощник, войдем на сервер по ssh, выполним команду:
docker run -p 80:80 todolist

Контейнер запущен:

Проверим в portainer, что наш контейнер работает:

Проверим наше приложение по IP адресу сервера в браузере:

На выходе получил готовое приложение ни разу не работая с react. Если интересно, могу попробовать продолжить "разработку приложения", разработав бэк и подключить его к базе данных.
Если будут вопросы, мой телеграм.