epam-ai-conductor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/bin/workshop.js +6 -0
- package/dist/auth-check.d.ts +1 -0
- package/dist/auth-check.js +45 -0
- package/dist/auth-check.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +156 -0
- package/dist/cli.js.map +1 -0
- package/dist/content/content/module-1/task/module-1_home-task_components.md +257 -0
- package/dist/content/content/module-1/task/module-1_home-task_good_practices.md +69 -0
- package/dist/content/content/module-1/task/module-1_home-task_task.md +253 -0
- package/dist/content/content/module-1/tests/Button.test.tsx +22 -0
- package/dist/content/content/module-1/tests/CourseCard.test.tsx +64 -0
- package/dist/content/content/module-1/tests/CourseInfo.test.tsx +73 -0
- package/dist/content/content/module-1/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-1/tests/Header.test.tsx +23 -0
- package/dist/content/content/module-1/tests/Input.test.tsx +36 -0
- package/dist/content/content/module-1/tests/helpers.test.ts +13 -0
- package/dist/content/content/module-1/theory/module-1_elements-render.md +66 -0
- package/dist/content/content/module-1/theory/module-1_hooks_useEffect.md +178 -0
- package/dist/content/content/module-1/theory/module-1_hooks_useState.md +131 -0
- package/dist/content/content/module-1/theory/module-1_react.md +64 -0
- package/dist/content/content/module-1/theory/module-1_spa.md +74 -0
- package/dist/content/content/module-2/task/module-2_home-task_components.md +313 -0
- package/dist/content/content/module-2/task/module-2_home-task_good_practices.md +35 -0
- package/dist/content/content/module-2/task/module-2_home-task_task.md +210 -0
- package/dist/content/content/module-2/tests/App.test.tsx +54 -0
- package/dist/content/content/module-2/tests/Login.test.tsx +73 -0
- package/dist/content/content/module-2/tests/Registration.test.tsx +70 -0
- package/dist/content/content/module-2/tests/SearchBar.test.tsx +39 -0
- package/dist/content/content/module-2/theory/module-2_custom-hooks.md +201 -0
- package/dist/content/content/module-2/theory/module-2_hooks.md +117 -0
- package/dist/content/content/module-2/theory/module-2_react-router.md +328 -0
- package/dist/content/content/module-3/task/module-3_home-task_components.md +94 -0
- package/dist/content/content/module-3/task/module-3_home-task_good_practices.md +26 -0
- package/dist/content/content/module-3/task/module-3_home-task_task.md +170 -0
- package/dist/content/content/module-3/tests/App.test.tsx +54 -0
- package/dist/content/content/module-3/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-3/tests/CreateAuthor.test.tsx +44 -0
- package/dist/content/content/module-3/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/content/module-3/theory/module-3_redux-hooks.md +194 -0
- package/dist/content/content/module-3/theory/module-3_state-actions-reducers.md +445 -0
- package/dist/content/content/module-4/task/module-4_home-task_components.md +187 -0
- package/dist/content/content/module-4/task/module-4_home-task_task.md +139 -0
- package/dist/content/content/module-4/tests/App.test.tsx +54 -0
- package/dist/content/content/module-4/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-4/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/content/module-4/tests/Login.test.tsx +73 -0
- package/dist/content/content/module-4/theory/module-4_async-redux.md +99 -0
- package/dist/content/content/module-4/theory/module-4_private-routes.md +55 -0
- package/dist/content/content/module-5/task/module-5_home-task_instruction.md +68 -0
- package/dist/content/content/module-5/task/module-5_home-task_task.md +154 -0
- package/dist/content/content/module-5/tests/App.test.tsx +54 -0
- package/dist/content/content/module-5/tests/CourseCard.test.tsx +64 -0
- package/dist/content/content/module-5/tests/Courses.test.tsx +87 -0
- package/dist/content/content/module-5/tests/Header.test.tsx +23 -0
- package/dist/content/content/module-5/theory/module-5_react-testing-library_example.md +379 -0
- package/dist/content/content/module-5/theory/module-5_redux-writing-tests.md +246 -0
- package/dist/content/module-1/task/module-1_home-task_components.md +257 -0
- package/dist/content/module-1/task/module-1_home-task_good_practices.md +69 -0
- package/dist/content/module-1/task/module-1_home-task_task.md +253 -0
- package/dist/content/module-1/tests/Button.test.tsx +22 -0
- package/dist/content/module-1/tests/CourseCard.test.tsx +64 -0
- package/dist/content/module-1/tests/CourseInfo.test.tsx +73 -0
- package/dist/content/module-1/tests/Courses.test.tsx +87 -0
- package/dist/content/module-1/tests/Header.test.tsx +23 -0
- package/dist/content/module-1/tests/Input.test.tsx +36 -0
- package/dist/content/module-1/tests/helpers.test.ts +13 -0
- package/dist/content/module-1/theory/module-1_elements-render.md +66 -0
- package/dist/content/module-1/theory/module-1_hooks_useEffect.md +178 -0
- package/dist/content/module-1/theory/module-1_hooks_useState.md +131 -0
- package/dist/content/module-1/theory/module-1_react.md +64 -0
- package/dist/content/module-1/theory/module-1_spa.md +74 -0
- package/dist/content/module-2/task/module-2_home-task_components.md +313 -0
- package/dist/content/module-2/task/module-2_home-task_good_practices.md +35 -0
- package/dist/content/module-2/task/module-2_home-task_task.md +210 -0
- package/dist/content/module-2/tests/App.test.tsx +54 -0
- package/dist/content/module-2/tests/Login.test.tsx +73 -0
- package/dist/content/module-2/tests/Registration.test.tsx +70 -0
- package/dist/content/module-2/tests/SearchBar.test.tsx +39 -0
- package/dist/content/module-2/theory/module-2_custom-hooks.md +201 -0
- package/dist/content/module-2/theory/module-2_hooks.md +117 -0
- package/dist/content/module-2/theory/module-2_react-router.md +328 -0
- package/dist/content/module-3/task/module-3_home-task_components.md +94 -0
- package/dist/content/module-3/task/module-3_home-task_good_practices.md +26 -0
- package/dist/content/module-3/task/module-3_home-task_task.md +170 -0
- package/dist/content/module-3/tests/App.test.tsx +54 -0
- package/dist/content/module-3/tests/Courses.test.tsx +87 -0
- package/dist/content/module-3/tests/CreateAuthor.test.tsx +44 -0
- package/dist/content/module-3/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/module-3/theory/module-3_redux-hooks.md +194 -0
- package/dist/content/module-3/theory/module-3_state-actions-reducers.md +445 -0
- package/dist/content/module-4/task/module-4_home-task_components.md +187 -0
- package/dist/content/module-4/task/module-4_home-task_task.md +139 -0
- package/dist/content/module-4/tests/App.test.tsx +54 -0
- package/dist/content/module-4/tests/Courses.test.tsx +87 -0
- package/dist/content/module-4/tests/CreateCourse.test.tsx +122 -0
- package/dist/content/module-4/tests/Login.test.tsx +73 -0
- package/dist/content/module-4/theory/module-4_async-redux.md +99 -0
- package/dist/content/module-4/theory/module-4_private-routes.md +55 -0
- package/dist/content/module-5/task/module-5_home-task_instruction.md +68 -0
- package/dist/content/module-5/task/module-5_home-task_task.md +154 -0
- package/dist/content/module-5/tests/App.test.tsx +54 -0
- package/dist/content/module-5/tests/CourseCard.test.tsx +64 -0
- package/dist/content/module-5/tests/Courses.test.tsx +87 -0
- package/dist/content/module-5/tests/Header.test.tsx +23 -0
- package/dist/content/module-5/theory/module-5_react-testing-library_example.md +379 -0
- package/dist/content/module-5/theory/module-5_redux-writing-tests.md +246 -0
- package/dist/content-loader.d.ts +7 -0
- package/dist/content-loader.js +26 -0
- package/dist/content-loader.js.map +1 -0
- package/dist/context-builder.d.ts +2 -0
- package/dist/context-builder.js +116 -0
- package/dist/context-builder.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
sidebar_label: "COMPONENTS"
|
|
4
|
+
title: Components description for Tasks 2
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## App component
|
|
8
|
+
|
|
9
|
+
### Add the router to the App component.
|
|
10
|
+
|
|
11
|
+
All movements on the pages of the application can be carried out through the [react-router-dom](https://reactrouter.com/en/main/start/tutorial).
|
|
12
|
+
Router should be described in `App.jsx`.
|
|
13
|
+
[Adding routes](/docs/module-2/react-router#adding-routes).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Registration (new component)
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
[Figma link](https://www.figma.com/design/9MsU97rn21GuQIT01xwLuc/React-Fundamentals-Course?node-id=2932-219&t=2XuYekWUy7fL4Ba9-0)
|
|
21
|
+
|
|
22
|
+
### Create `Registration` component.
|
|
23
|
+
|
|
24
|
+
- This component should contain the following elements:
|
|
25
|
+
- `name` field;
|
|
26
|
+
|
|
27
|
+
- `email` field;
|
|
28
|
+
|
|
29
|
+
- `password` field;
|
|
30
|
+
|
|
31
|
+
- `Registration` button;
|
|
32
|
+
|
|
33
|
+
- link that navigates to `Login` component.
|
|
34
|
+
|
|
35
|
+
- This component should be rendered by the route `/registration`.
|
|
36
|
+
`Registration` component should have functionality to send request to API for creating a new user.
|
|
37
|
+
See `/register` endpoint in API Swagger.
|
|
38
|
+
|
|
39
|
+
- After successful registration, user is redirected to the `Login` page by route `/login`.
|
|
40
|
+
|
|
41
|
+
:::tip
|
|
42
|
+
For redirection you can use [`Link`](https://reactrouter.com/en/main/components/link) or
|
|
43
|
+
[`useNavigate`](https://reactrouter.com/en/main/hooks/use-navigate) hook.
|
|
44
|
+
:::
|
|
45
|
+
|
|
46
|
+
- Use `form` tag.
|
|
47
|
+
|
|
48
|
+
- You should reuse `Input` and `Button` components.
|
|
49
|
+
|
|
50
|
+
- Request should be sent by `submit` event. Use `onSubmit` props for `form`.
|
|
51
|
+
|
|
52
|
+
- Validation should be added (all fields are required, empty values are not allowed). When the user submits the form and fields are not valid, validation errors should be displayed.
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
[Figma link](https://www.figma.com/design/9MsU97rn21GuQIT01xwLuc/React-Fundamentals-Course?node-id=2932-257&t=2XuYekWUy7fL4Ba9-0)
|
|
56
|
+
|
|
57
|
+
### Example of POST request using `fetch`:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
const newUser = {
|
|
61
|
+
name,
|
|
62
|
+
password,
|
|
63
|
+
email,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const response = await fetch("http://localhost:4000/register", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: JSON.stringify(newUser),
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const result = await response.json();
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
:::warning
|
|
78
|
+
There is an email validation on the back end side. In case of incorrect email format you will get an error.
|
|
79
|
+

|
|
80
|
+
:::
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Login (new component)
|
|
85
|
+
|
|
86
|
+

|
|
87
|
+
[Figma link](https://www.figma.com/design/9MsU97rn21GuQIT01xwLuc/React-Fundamentals-Course?node-id=2927-216&t=2XuYekWUy7fL4Ba9-0)
|
|
88
|
+
|
|
89
|
+
### Create `Login` component.
|
|
90
|
+
|
|
91
|
+
- This component should contain the following elements:
|
|
92
|
+
- `email` field;
|
|
93
|
+
|
|
94
|
+
- `password` field;
|
|
95
|
+
|
|
96
|
+
- `Login` button;
|
|
97
|
+
|
|
98
|
+
- Link to `Registration` component (use `Link` from `react-router-dom`).
|
|
99
|
+
|
|
100
|
+
- `Login` should be rendered by route `/login`;
|
|
101
|
+
|
|
102
|
+
- `Login` should have functionality that
|
|
103
|
+
sends request to API for getting token.
|
|
104
|
+
See `/login` endpoint in API Swagger.
|
|
105
|
+
|
|
106
|
+
- Use `form` tag.
|
|
107
|
+
|
|
108
|
+
- You should use `Input` and `Button` components.
|
|
109
|
+
|
|
110
|
+
- Request should be sent by `submit` event. Use `onSubmit` props for `form`.
|
|
111
|
+
|
|
112
|
+
- Validation should be added (all fields are required, empty values are not allowed). When the user submits the form and fields are not valid, validation errors should be displayed.
|
|
113
|
+
|
|
114
|
+

|
|
115
|
+
[Figma link](https://www.figma.com/design/9MsU97rn21GuQIT01xwLuc/React-Fundamentals-Course?node-id=2932-191&t=2XuYekWUy7fL4Ba9-0)
|
|
116
|
+
|
|
117
|
+
- Response contains value `result`, it's user's token. You should save it to the `localStorage`.
|
|
118
|
+
|
|
119
|
+
- After successful login, user is redirected to the `Courses` page by route `/courses`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Course info
|
|
124
|
+
|
|
125
|
+
### Implement a new feature for `CourseInfo` component:
|
|
126
|
+
|
|
127
|
+
- This component should be rendered by route `/courses/:courseId`.
|
|
128
|
+
|
|
129
|
+
- Find a course in courses list by an id using `courseId` path-param.
|
|
130
|
+
|
|
131
|
+
- Use `react-router-dom` [hook](https://reactrouter.com/en/main/hooks/use-params) `useParams` to get courseId from url.
|
|
132
|
+
|
|
133
|
+
- `Back` button should navigate to `/courses` and then the `Courses` component appears.
|
|
134
|
+
Use [Link](https://reactrouter.com/en/main/components/link) from `react-router-dom`.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Courses
|
|
139
|
+
|
|
140
|
+
### Implement new features for `Courses` component:
|
|
141
|
+
|
|
142
|
+
- Component `Courses` should be opened by route `/courses`;
|
|
143
|
+
|
|
144
|
+
- If there is a token in the `localStorage`, then App navigates to the `/courses` by default.
|
|
145
|
+
|
|
146
|
+
- **You don't have to get courses and authors lists from backend.** Please continue using mock data. We will use API for these data in the next module.
|
|
147
|
+
|
|
148
|
+
- Use [Link](https://reactrouter.com/en/main/components/link) from `react-router-dom` for `Add New Course` button.
|
|
149
|
+
|
|
150
|
+
- When user clicks the `Add New Course` button the App navigates to `/courses/add`.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Add new course
|
|
155
|
+
|
|
156
|
+

|
|
157
|
+
[Figma link](https://www.figma.com/design/9MsU97rn21GuQIT01xwLuc/React-Fundamentals-Course?node-id=2907-67422&t=2XuYekWUy7fL4Ba9-0)
|
|
158
|
+
|
|
159
|
+
### Create `Input` component.
|
|
160
|
+
|
|
161
|
+
**Input component** should contain `input` and `label` tags.
|
|
162
|
+
|
|
163
|
+
:::note
|
|
164
|
+
Using a `label` tag for `input` is considered good practice.
|
|
165
|
+
[Usage and benefits of label tag.](https://www.w3schools.com/tags/tag_label.asp)
|
|
166
|
+
:::
|
|
167
|
+
|
|
168
|
+
:::tip
|
|
169
|
+
You will use `Input component` in several places through your app,
|
|
170
|
+
so you should use `props` for this component such as `labelText`, `placeholderText` and `onChange`
|
|
171
|
+
(and any props, that you want).
|
|
172
|
+
:::
|
|
173
|
+
|
|
174
|
+
### Create `AuthorItem` component.
|
|
175
|
+
|
|
176
|
+

|
|
177
|
+
This component should be rendered in the `CreateCourse` component.
|
|
178
|
+
|
|
179
|
+
`AuthorItem` component should contain the following elements:
|
|
180
|
+
|
|
181
|
+
- Author's name;
|
|
182
|
+
|
|
183
|
+
- `Button` component (functionality is described below);
|
|
184
|
+
|
|
185
|
+
### Create `CreateCourse` component.
|
|
186
|
+
|
|
187
|
+
This component should be rendered by route `courses/add`.
|
|
188
|
+
|
|
189
|
+
`CreateCourse` component should contain the following elements:
|
|
190
|
+
|
|
191
|
+
- **Title** (input) - field for input course name. Text length should be at least 2 characters;
|
|
192
|
+
|
|
193
|
+
- **Description** (textarea) - text length should be at least 2 characters;
|
|
194
|
+
|
|
195
|
+
- **Authors** - contains a list of all authors and their corresponding `Add author` buttons
|
|
196
|
+
(use loop and `AuthorItem` component);
|
|
197
|
+
|
|
198
|
+
- **Course authors** - contains a list of authors course and their corresponding `Delete author` buttons
|
|
199
|
+
(use loop and `AuthorItem` component);
|
|
200
|
+
|
|
201
|
+
- **Delete author** - when user clicks on this button the corresponding author disappears from the
|
|
202
|
+
**Course authors** list and shows in **Authors**;
|
|
203
|
+
|
|
204
|
+
- **Add author** - when user clicks on this button the corresponding author disappears from the
|
|
205
|
+
**Authors** list and shows in **Course authors**. New author should be added to the initial author's list;
|
|
206
|
+
|
|
207
|
+
- **Author field** (input) - author name length should be at least 2 characters;
|
|
208
|
+
|
|
209
|
+
- **Create author** (button) - when user clicks on this button:
|
|
210
|
+
- the new author appears in **Authors**;
|
|
211
|
+
- the author's id is generated automatically;
|
|
212
|
+
- clean input.
|
|
213
|
+
|
|
214
|
+
- **Duration** - this part provides logic for adding course duration time.
|
|
215
|
+
- the duration of the course is entered in minutes;
|
|
216
|
+
- for the correct display of the course duration,
|
|
217
|
+
you need to format minutes into hours and minutes;
|
|
218
|
+
- duration should be more than 0 minutes;
|
|
219
|
+
- user should have an ability to enter ONLY numbers into the field.
|
|
220
|
+
|
|
221
|
+
- **Create course** (button) - when user clicks on this button:
|
|
222
|
+
- User is navigated to the route`/courses`;
|
|
223
|
+
- New course is added to the courses list.
|
|
224
|
+
|
|
225
|
+
:::caution
|
|
226
|
+
`Create course` button should always be available.
|
|
227
|
+
:::
|
|
228
|
+
|
|
229
|
+
:::danger
|
|
230
|
+
ALL FIELDS ARE REQUIRED. Validation should be added.
|
|
231
|
+
:::
|
|
232
|
+
|
|
233
|
+
In case, when the user clicks on the `Create course` button, and some
|
|
234
|
+
field is not filled in, then validation error messages should be displayed.
|
|
235
|
+
|
|
236
|
+

|
|
237
|
+
[Figma link](https://www.figma.com/design/9MsU97rn21GuQIT01xwLuc/React-Fundamentals-Course?node-id=6131-439&t=2XuYekWUy7fL4Ba9-0)
|
|
238
|
+
|
|
239
|
+
:::tip
|
|
240
|
+
Look at [One more example for controlled inputs (from Home Task)](/docs/module-1/forms#-one-more-example-for-controlled-inputs-from-home-task)
|
|
241
|
+
:::
|
|
242
|
+
|
|
243
|
+
### Course model
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
// Course
|
|
247
|
+
{
|
|
248
|
+
id: string;
|
|
249
|
+
title: string;
|
|
250
|
+
description: string;
|
|
251
|
+
creationDate: string;
|
|
252
|
+
duration: number;
|
|
253
|
+
authors: [authorId];
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Author model
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
// Author
|
|
261
|
+
{
|
|
262
|
+
id: string;
|
|
263
|
+
name: string;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
- Component `CreateCourse` should be opened by router `/courses/add`.
|
|
268
|
+
|
|
269
|
+
- When user clicks on `Create course` button, App navigates to `/courses`
|
|
270
|
+
(the new course should be in the course list in `Courses` component).
|
|
271
|
+
|
|
272
|
+
### To sum up
|
|
273
|
+
|
|
274
|
+
Now that you have created the `CreateCourse` component you see that it is quite large
|
|
275
|
+
and a new person will find it difficult to understand.
|
|
276
|
+
|
|
277
|
+
**The `CreateCourse` component is an ideal place to practice [composition](/docs/module-1/components-and-props#extracting-components).**
|
|
278
|
+
|
|
279
|
+
:::info
|
|
280
|
+
Field **id** for course and for the author should be generated automatically.
|
|
281
|
+
|
|
282
|
+
To do this, you can use the [UUID library](https://www.npmjs.com/package/uuid)
|
|
283
|
+
or any way you like.
|
|
284
|
+
:::
|
|
285
|
+
|
|
286
|
+
:::info
|
|
287
|
+
Field **creationDate** created automatically base on the current date.
|
|
288
|
+
Date format: dd/mm/yyyy (see examples in mockedCoursesList).
|
|
289
|
+
:::
|
|
290
|
+
|
|
291
|
+
:::tip
|
|
292
|
+
Study [this documentation](https://react.dev/reference/react-dom/components#common) to do **conditional rendering** for `CreateCourse` component. (Suggested syntax is (logical && operator)[https://react.dev/reference/react-dom/components#common], easy to read and use.)
|
|
293
|
+
:::
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Header
|
|
298
|
+
|
|
299
|
+
### Implement a new feature for `Header` component:
|
|
300
|
+
|
|
301
|
+
- Show user's name if he is logged in.
|
|
302
|
+
|
|
303
|
+
- When user clicks on `Logout` button, App should navigate to `/login`
|
|
304
|
+
and you should remove `token` from the localStorage.
|
|
305
|
+
|
|
306
|
+
- `Logout` button and user's name should not be on Login and Registration pages.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
:::tip
|
|
311
|
+
You can use hook [`useLocation`](https://reactrouter.com/en/main/hooks/use-location) to determine the current pages.
|
|
312
|
+
:::
|
|
313
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
sidebar_label: 'GOOD PRACTICES'
|
|
4
|
+
title: GOOD PRACTICES
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GOOD PRACTICES THAT YOU CAN APPLY FOR THIS TASK:
|
|
8
|
+
1. It's a good practice to check response status to provide default behavior for failed requests.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
```jsx
|
|
12
|
+
function SignUpButton(props) {
|
|
13
|
+
const [hasError, setError] = React.useState(false);
|
|
14
|
+
const handleClick = async () => {
|
|
15
|
+
try {
|
|
16
|
+
await api.signUp();
|
|
17
|
+
} catch(error) {
|
|
18
|
+
errorService.log(error)
|
|
19
|
+
setError(true);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (hasError) {
|
|
23
|
+
return <p>Sorry, Sign up failed!</p>;
|
|
24
|
+
}
|
|
25
|
+
return <button onClick={handleClick}>Sign up</button>;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. Show duration time in a format [hh:mm] hour(s)
|
|
30
|
+
|
|
31
|
+
In this exercise, think about the helper methods you already created!
|
|
32
|
+
|
|
33
|
+
3. Naming convention
|
|
34
|
+
|
|
35
|
+
Check your variables, state names! Pay attention to (naming conventions)[https://dev.to/michi/tips-on-naming-boolean-variables-cleaner-code-35ig].
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
sidebar_label: 'TASK'
|
|
4
|
+
title: 'Module 2. Router'
|
|
5
|
+
---
|
|
6
|
+
This is the second part of the application.
|
|
7
|
+
In this assignment,
|
|
8
|
+
you should continue to work with your project with a slight modification.
|
|
9
|
+
This module is designed to teach how to work with routing in React.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Prerequisite:
|
|
13
|
+
|
|
14
|
+
First, clone and run repo with a server:
|
|
15
|
+
|
|
16
|
+
1. Clone repo with a server [(link)](https://autocode.git.epam.com/ld-autocode-js-programs/react-fundamentals/react-fundamentals-app-api).
|
|
17
|
+
|
|
18
|
+
2. Go to server project folder, install dependencies and run server:
|
|
19
|
+
```http request
|
|
20
|
+
npm install
|
|
21
|
+
|
|
22
|
+
npm run start
|
|
23
|
+
```
|
|
24
|
+
3. Now you have a backend for your application.
|
|
25
|
+
|
|
26
|
+
Go to the url: [http://localhost:4000/api](http://localhost:4000/api)
|
|
27
|
+
|
|
28
|
+
You should see a page with all APIs:
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
:::caution
|
|
32
|
+
These are the implemented APIs for the app, BUT **you should only use the ones that are specified in the task**.
|
|
33
|
+
:::
|
|
34
|
+
___
|
|
35
|
+
|
|
36
|
+
Secondly, you need to install the `react-router-dom` module in your project using npm:
|
|
37
|
+
|
|
38
|
+
1. Go to your project directory.
|
|
39
|
+
|
|
40
|
+
2. Create a new branch for Module 2 task.
|
|
41
|
+
|
|
42
|
+
3. Install `react-router-dom`.
|
|
43
|
+
```http request
|
|
44
|
+
npm install react-router-dom --save
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
3. Run your project
|
|
48
|
+
```http request
|
|
49
|
+
npm run start
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Project structure requirements
|
|
53
|
+
|
|
54
|
+
1. Create new folders and `jsx` files for each component:
|
|
55
|
+
```
|
|
56
|
+
src
|
|
57
|
+
|-- components
|
|
58
|
+
| |-- CreateCourse
|
|
59
|
+
| | |__ components
|
|
60
|
+
| | |-- AuthorItem
|
|
61
|
+
| | |__ AuthorItem.jsx/.tsx
|
|
62
|
+
| | |__ CreateCourse.jsx/.tsx
|
|
63
|
+
| |
|
|
64
|
+
| |-- Login
|
|
65
|
+
| | |__ Login.jsx/.tsx
|
|
66
|
+
| |
|
|
67
|
+
| |-- Registration
|
|
68
|
+
| | |__ Registration.jsx/.tsx
|
|
69
|
+
| |
|
|
70
|
+
| |__ ...
|
|
71
|
+
|__ ...
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
2. For sending requests to API you should use [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) or
|
|
76
|
+
[axios](https://www.npmjs.com/package/axios).
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
3. APIs from SWAGGER for Module 2:
|
|
80
|
+
- `/login` [POST]
|
|
81
|
+
- `/register` [POST]
|
|
82
|
+
|
|
83
|
+
___
|
|
84
|
+
## Criteria (35 points max)
|
|
85
|
+
|
|
86
|
+
### Common
|
|
87
|
+
|
|
88
|
+
* [3 points] - Use `react-router-dom` hooks: [`useParams`](https://reactrouter.com/en/main/hooks/use-params), [`useNavigate`](https://reactrouter.com/en/main/hooks/use-navigate) etc.
|
|
89
|
+
|
|
90
|
+
* [5 points] - Add data type checking for props to all components using
|
|
91
|
+
[PropTypes](https://react.dev/reference/react/Component#static-proptypes) or [TypeScript](https://create-react-app.dev/docs/adding-typescript/).
|
|
92
|
+
**If you already use TypeScript, please ignore this requirement.**
|
|
93
|
+
|
|
94
|
+
### [Registration Component](/docs/module-2/home-task/components#registration-new-component)
|
|
95
|
+
* [2 points] - Create component **Registration** with.
|
|
96
|
+
|
|
97
|
+
* [1 point] - Registration form should appear after clicking on the *Registration* [link](https://reactrouter.com/en/main/components/link)
|
|
98
|
+
on the `Login` form.
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
* [1 point] - Registration form should appear by route `/registration`.
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
* [1 points] - Registration should have an auth functionality.
|
|
105
|
+
User enters email, name and password, presses the `Registration` button then application sends request to API.
|
|
106
|
+
See `/register` endpoint in API Swagger.
|
|
107
|
+
After successful registration application
|
|
108
|
+
[navigates](https://reactrouter.com/en/main/components/navigate)
|
|
109
|
+
you to `Login` page.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
* [1 point] - Validation required for all fields.
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
### [Login Component](/docs/module-2/home-task/components#login-new-component)
|
|
116
|
+
* [2 points] - Create component **Login**.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
* [1 point] - Login should be shown after [first open application](/docs/module-2/react-router#redirect) by route `/login`.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
* [1 point] - Login form should appear after clicking on the [link](https://reactrouter.com/en/main/components/link) *Login* on the `Registration` form.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
* [1 points] - Login should have an auth functionality.
|
|
126
|
+
When you entered an email and password application sends request to API.
|
|
127
|
+
See `/login` endpoint in API Swagger.
|
|
128
|
+
After successful login application navigates to `Courses` page.
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
* [2 points] - Save token from API after login.
|
|
132
|
+
Add functionality that check if token in `localStorage`.
|
|
133
|
+
If token is in the `localStorage` app automatically
|
|
134
|
+
navigates to `/courses` route.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
* [1 point] - Validation required for all fields.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### [CourseInfo Component](/docs/module-2/home-task/components#course-info)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
* [2 points] - Show information about the course. Use route `/courses/:courseId`
|
|
144
|
+
(`courseId` - id of the current course).
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
* [2 points] - To find out which course info you should render on `CourseInfo` page,
|
|
148
|
+
you should use
|
|
149
|
+
id of the course from [path-parameters](https://reactrouter.com/en/main/hooks/use-params).
|
|
150
|
+
|
|
151
|
+
* [2 points] - `Back` button should navigate to the route `/courses`.
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
### [Courses Component](/docs/module-2/home-task/components#courses)
|
|
155
|
+
|
|
156
|
+
* [2 points] - Component **Courses** should be opened by route `/courses`.
|
|
157
|
+
|
|
158
|
+
* [2 points] - Show `Courses` component by default if there is token in the `localStorage`.
|
|
159
|
+
|
|
160
|
+
* [2 points] - Navigate to the route `courses/add` by clicking `Add New Course` button.
|
|
161
|
+
|
|
162
|
+
### [CreateCourse Component](/docs/module-2/home-task/components#add-new-course)
|
|
163
|
+
|
|
164
|
+
* [1 point] - Possibility to add a title.
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
* [1 point] - Possibility to add a description.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
* [1 point] - Possibility to add a duration.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
* [2 points] - Show duration time in a format «hh:mm».
|
|
174
|
+
Example: **122 min** should be showed as **02:02 hours**.
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
* [1 point] - Add logic for creating a new author.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
* [1 point] - Add logic for adding an author to **Course authors**.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
* [1 point] - Add logic for deleting an author from **Course authors**.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
* [3 points] - Add logic for saving course (new course should be presented in the courses list).
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
* [1 points] - Add validation for required fields.
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
* [2 point] - Open **CreateCourse** component by route `/courses/add`.
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
### [Header Component](/docs/module-2/home-task/components#header)
|
|
196
|
+
* [2 point] - Add logout functionality.
|
|
197
|
+
When user clicks `Logout` button in the `Header` component, token should be
|
|
198
|
+
removed from `localStorage` and user is navigated to `Login` page.
|
|
199
|
+
|
|
200
|
+
* [2 point] - Get user's name from the `Login` response and display it in the header.
|
|
201
|
+
|
|
202
|
+
* [2 point] - Remove user's name for the `Login` and `Registration` pages.
|
|
203
|
+
|
|
204
|
+
* [2 point] - Remove `Logout` button for the `Login` and `Registration` pages.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
:::info
|
|
208
|
+
Please find detailed description of components and functionality in the [COMPONENTS](components) section.
|
|
209
|
+
:::
|
|
210
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { MemoryRouter } from "react-router-dom";
|
|
4
|
+
import App from "../App";
|
|
5
|
+
|
|
6
|
+
describe("App", () => {
|
|
7
|
+
test("renders Login component when token is not present", () => {
|
|
8
|
+
localStorage.removeItem("token");
|
|
9
|
+
render(
|
|
10
|
+
<MemoryRouter initialEntries={["/"]}>
|
|
11
|
+
<App />
|
|
12
|
+
</MemoryRouter>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(
|
|
16
|
+
screen.getByText(/If you don't have an account you can/)
|
|
17
|
+
).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("renders Courses component when token is present", () => {
|
|
21
|
+
localStorage.setItem("token", "token");
|
|
22
|
+
render(
|
|
23
|
+
<MemoryRouter initialEntries={["/"]}>
|
|
24
|
+
<App />
|
|
25
|
+
</MemoryRouter>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const courseElements = screen.getAllByTestId("courseCard");
|
|
29
|
+
|
|
30
|
+
expect(courseElements[0]).toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("renders Registration component", () => {
|
|
34
|
+
render(
|
|
35
|
+
<MemoryRouter initialEntries={["/registration"]}>
|
|
36
|
+
<App />
|
|
37
|
+
</MemoryRouter>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(screen.getByLabelText("name")).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByLabelText("email")).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByLabelText("password")).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("renders course add component", () => {
|
|
46
|
+
render(
|
|
47
|
+
<MemoryRouter initialEntries={["/courses/add"]}>
|
|
48
|
+
<App />
|
|
49
|
+
</MemoryRouter>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByTestId("titleInput")).toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, fireEvent, screen } from "@testing-library/react";
|
|
3
|
+
import { MemoryRouter } from "react-router-dom";
|
|
4
|
+
import { Login } from "../components/Login";
|
|
5
|
+
import * as services from "../../../services";
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.spyOn(services, "login").mockImplementation(
|
|
9
|
+
jest.fn(() =>
|
|
10
|
+
Promise.resolve({
|
|
11
|
+
result: "token",
|
|
12
|
+
user: {
|
|
13
|
+
name: "John Doe",
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
) as jest.Mock
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
jest.spyOn(global, "fetch").mockImplementation(
|
|
20
|
+
jest.fn(() =>
|
|
21
|
+
Promise.resolve({
|
|
22
|
+
ok: true,
|
|
23
|
+
json: () =>
|
|
24
|
+
Promise.resolve({
|
|
25
|
+
result: "token",
|
|
26
|
+
user: {
|
|
27
|
+
name: "John Doe",
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
})
|
|
31
|
+
) as jest.Mock
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("Login", () => {
|
|
36
|
+
test("renders login form", () => {
|
|
37
|
+
render(
|
|
38
|
+
<MemoryRouter>
|
|
39
|
+
<Login />
|
|
40
|
+
</MemoryRouter>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(screen.getByLabelText("email")).toBeInTheDocument();
|
|
44
|
+
expect(screen.getByLabelText("password")).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByText("Login")).toBeInTheDocument();
|
|
46
|
+
expect(
|
|
47
|
+
screen.getByText(/If you don't have an account you can/)
|
|
48
|
+
).toBeInTheDocument();
|
|
49
|
+
expect(screen.getByRole("link")).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("submits login form", async () => {
|
|
53
|
+
render(
|
|
54
|
+
<MemoryRouter>
|
|
55
|
+
<Login />
|
|
56
|
+
</MemoryRouter>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
fireEvent.change(screen.getByLabelText("email"), {
|
|
60
|
+
target: { value: "test@example.com" },
|
|
61
|
+
});
|
|
62
|
+
fireEvent.change(screen.getByLabelText("password"), {
|
|
63
|
+
target: { value: "password123" },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
fireEvent.click(screen.getByText("login"));
|
|
67
|
+
|
|
68
|
+
expect(services.login).toHaveBeenCalledWith({
|
|
69
|
+
email: "test@example.com",
|
|
70
|
+
password: "password123",
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|