Skip to content

Commit 846930a

Browse files
committed
contact form
1 parent abbbd56 commit 846930a

19 files changed

Lines changed: 416 additions & 7 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ Adapted from https://www.linkedin.com/feed/update/urn:li:activity:73308248843165
4040
| Topic | Description | Solution |
4141
|-------------------|---------------------------------------------------|-------------------|
4242
| Todo List | [question](https://www.greatfrontend.com/questions/user-interface/todo-list/react?framework=react) | [solution](./src/react/todo/src/Todo.tsx) |
43-
| Contact Form | [question](https://lnkd.in/gtNHef6T) | |
44-
| Job Board | [question](https://lnkd.in/giHeiXh2) | |
43+
| Contact Form | [question](https://www.greatfrontend.com/questions/user-interface/contact-form/react?framework=react) | [solution](./src/react/contact-form/src/ContactForm.tsx) |
44+
| Job Board | [question]((https://www.greatfrontend.com/interviews/study/gfe75/questions/user-interface/job-board)) | |
4545
| Accordion | [question](https://lnkd.in/gSZYFQA5) | |
4646
| Image Carousel | [question](https://lnkd.in/gnxWuVxe) | |
4747
| Data Table | [question](https://lnkd.in/gwfqzfm6) | |

package-lock.json

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
"@testing-library/react": "^16.3.0",
1010
"@testing-library/user-event": "^14.6.1",
1111
"@types/jest": "^29.5.14",
12+
"@types/react": "^19.1.5",
13+
"@types/react-dom": "^19.1.5",
1214
"@vitejs/plugin-react": "^4.5.0",
15+
"and": "^0.0.3",
1316
"jest": "^29.7.0",
1417
"jsdom": "^26.1.0",
1518
"ts-jest": "^29.3.4",

src/react/contact-form/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

src/react/contact-form/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
The form should contain the following elements:
2+
* Name field.
3+
* Email field.
4+
* Message field. Since the message can be long, a `<textarea>` will be more suitable.
5+
* Submit button
6+
* Contains the text "Send".
7+
* Clicking on the submit button submits the form.
8+
9+
The form and submission should be implemented entirely in HTML. Do not use any JavaScript or framework-specific features for this question.
10+
11+
Run: `npx vite src/react/contact-list`
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import js from '@eslint/js'
2+
import globals from 'globals'
3+
import reactHooks from 'eslint-plugin-react-hooks'
4+
import reactRefresh from 'eslint-plugin-react-refresh'
5+
import tseslint from 'typescript-eslint'
6+
7+
export default tseslint.config(
8+
{ ignores: ['dist'] },
9+
{
10+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
11+
files: ['**/*.{ts,tsx}'],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
globals: globals.browser,
15+
},
16+
plugins: {
17+
'react-hooks': reactHooks,
18+
'react-refresh': reactRefresh,
19+
},
20+
rules: {
21+
...reactHooks.configs.recommended.rules,
22+
'react-refresh/only-export-components': [
23+
'warn',
24+
{ allowConstantExport: true },
25+
],
26+
},
27+
},
28+
)

src/react/contact-form/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + React + TS</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#root {
2+
max-width: 1280px;
3+
margin: 0 auto;
4+
padding: 2rem;
5+
text-align: center;
6+
}
7+
8+
.logo {
9+
height: 6em;
10+
padding: 1.5em;
11+
will-change: filter;
12+
transition: filter 300ms;
13+
}
14+
.logo:hover {
15+
filter: drop-shadow(0 0 2em #646cffaa);
16+
}
17+
.logo.react:hover {
18+
filter: drop-shadow(0 0 2em #61dafbaa);
19+
}
20+
21+
@keyframes logo-spin {
22+
from {
23+
transform: rotate(0deg);
24+
}
25+
to {
26+
transform: rotate(360deg);
27+
}
28+
}
29+
30+
@media (prefers-reduced-motion: no-preference) {
31+
a:nth-of-type(2) .logo {
32+
animation: logo-spin infinite 20s linear;
33+
}
34+
}
35+
36+
.card {
37+
padding: 2em;
38+
}
39+
40+
.read-the-docs {
41+
color: #888;
42+
}
43+
44+
form {
45+
max-width: 400px;
46+
margin: 2rem auto;
47+
padding: 2rem 1.5rem;
48+
background: #fff;
49+
border-radius: 12px;
50+
box-shadow: 0 2px 16px rgba(0,0,0,0.08);
51+
font-family: 'Segoe UI', Arial, sans-serif;
52+
}
53+
54+
form div {
55+
margin-bottom: 1.2rem;
56+
}
57+
58+
label {
59+
display: block;
60+
margin-bottom: 0.4rem;
61+
font-weight: 500;
62+
color: #333;
63+
}
64+
65+
input[type="text"],
66+
input[type="email"],
67+
textarea {
68+
width: 100%;
69+
padding: 0.6rem 0.8rem;
70+
border: 1px solid #ccc;
71+
border-radius: 6px;
72+
font-size: 1rem;
73+
transition: border 0.2s;
74+
background: #fafbfc;
75+
box-sizing: border-box;
76+
}
77+
78+
input[type="text"]:focus,
79+
input[type="email"]:focus,
80+
textarea:focus {
81+
border-color: #4f8cff;
82+
outline: none;
83+
background: #fff;
84+
}
85+
86+
textarea {
87+
resize: vertical;
88+
min-height: 80px;
89+
font-family: inherit;
90+
}
91+
92+
input[type="submit"] {
93+
background: linear-gradient(90deg, #4f8cff 0%, #6ed0ff 100%);
94+
color: #fff;
95+
border: none;
96+
padding: 0.7rem 1.5rem;
97+
border-radius: 6px;
98+
font-size: 1rem;
99+
font-weight: 600;
100+
cursor: pointer;
101+
transition: background 0.2s, box-shadow 0.2s;
102+
box-shadow: 0 1px 4px rgba(79,140,255,0.10);
103+
}
104+
105+
input[type="submit"]:hover,
106+
input[type="submit"]:focus {
107+
background: linear-gradient(90deg, #357ae8 0%, #4f8cff 100%);
108+
box-shadow: 0 2px 8px rgba(79,140,255,0.15);
109+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import { vi } from 'vitest';
3+
import ContactForm from './ContactForm';
4+
5+
describe('Contact Form', () => {
6+
beforeEach(() => {
7+
// Mock window.alert for Vitest
8+
vi.spyOn(window, 'alert').mockImplementation(() => {});
9+
});
10+
11+
afterEach(() => {
12+
vi.restoreAllMocks();
13+
});
14+
15+
it('shows error if any field is empty', () => {
16+
render(<ContactForm noValidate />);
17+
fireEvent.click(screen.getByText(/submit/i));
18+
expect(window.alert).toHaveBeenCalledWith('All fields are required!');
19+
});
20+
21+
it('shows success when all fields are filled', () => {
22+
render(<ContactForm />);
23+
fireEvent.change(screen.getByLabelText(/name/i), { target: { value: 'Alice' } });
24+
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'alice@example.com' } });
25+
fireEvent.change(screen.getByLabelText(/message/i), { target: { value: 'Hello' } });
26+
fireEvent.click(screen.getByText(/submit/i));
27+
expect(window.alert).toHaveBeenCalledWith('Success');
28+
});
29+
});

0 commit comments

Comments
 (0)