Самое популярное приложение после 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. Если интересно, могу попробовать продолжить "разработку приложения", разработав бэк и подключить его к базе данных.
Если будут вопросы, мой телеграм.
