Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created October 24, 2025 12:01
Show Gist options
  • Save sunmeat/90a57c72def5f76a59e135c915c07274 to your computer and use it in GitHub Desktop.
Save sunmeat/90a57c72def5f76a59e135c915c07274 to your computer and use it in GitHub Desktop.
spring + react
App.jsx:
import {useState} from 'react'
import './App.css'
function App() {
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
subject: '',
message: ''
})
const [response, setResponse] = useState('')
const [loading, setLoading] = useState(false)
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
const handleSubmit = async (e) => {
e.preventDefault()
setLoading(true)
setResponse('')
try {
const res = await fetch('http://localhost:8080/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
if (res.ok) {
const message = await res.text()
setResponse(message)
setFormData({
name: '',
email: '',
phone: '',
subject: '',
message: ''
})
} else {
throw new Error('Помилка сервера')
}
} catch (error) {
setResponse('Помилка: ' + error.message)
} finally {
setLoading(false)
}
}
return (
<div className="app-container">
<div className="form-card">
<h1 className="form-title">Контактна форма</h1>
<form onSubmit={handleSubmit} className="form">
<div className="input-group">
<label htmlFor="name" className="label">Ім'я</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
className="input"
disabled={loading}
/>
</div>
<div className="input-group">
<label htmlFor="email" className="label">Email</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
className="input"
disabled={loading}
/>
</div>
<div className="input-group">
<label htmlFor="phone" className="label">Телефон</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
className="input"
disabled={loading}
/>
</div>
<div className="input-group">
<label htmlFor="subject" className="label">Тема</label>
<input
type="text"
id="subject"
name="subject"
value={formData.subject}
onChange={handleChange}
required
className="input"
disabled={loading}
/>
</div>
<div className="input-group">
<label htmlFor="message" className="label">Повідомлення</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
rows="5"
className="textarea"
disabled={loading}
/>
</div>
<button type="submit" className={`submit-btn ${loading ? 'disabled' : ''}`} disabled={loading}>
{loading ? 'Відправка...' : 'Відправити'}
</button>
{response && (
<div className={`response ${response.includes('Помилка') ? 'error' : 'success'}`}>
{response}
</div>
)}
</form>
</div>
</div>
)
}
export default App
=================================================================================================================================
App.css: (файл index.css має бути пустим!)
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
--success-gradient: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
--error-gradient: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
--bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--card-bg: rgba(255, 255, 255, 0.95);
--border-color: #e1e5e9;
--text-primary: #333;
--text-secondary: #555;
--shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
--shadow-hover: 0 25px 50px rgba(0, 0, 0, 0.15);
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
line-height: 1.6;
color: var(--text-primary);
}
.app-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-gradient);
padding: 20px;
position: relative;
overflow: hidden;
}
.app-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><radialGradient id="a" cx="50%" cy="50%" r="50%"><stop offset="0%" stop-color="rgba(255,255,255,0.1)"/><stop offset="100%" stop-color="rgba(255,255,255,0)"/></radialGradient></defs><circle cx="200" cy="200" r="100" fill="url(%23a)"/><circle cx="800" cy="300" r="150" fill="url(%23a)"/><circle cx="400" cy="800" r="120" fill="url(%23a)"/></svg>');
pointer-events: none;
animation: float 20s ease-in-out infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0px) rotate(0deg);
}
50% {
transform: translateY(-20px) rotate(180deg);
}
}
.form-card {
background: var(--card-bg);
backdrop-filter: blur(20px);
border-radius: 24px;
padding: 48px 40px;
width: 100%;
max-width: 480px;
box-shadow: var(--shadow);
border: 1px solid rgba(255, 255, 255, 0.2);
position: relative;
z-index: 1;
animation: slideUp 0.6s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.form-title {
text-align: center;
color: var(--text-primary);
margin-bottom: 36px;
font-size: 28px;
font-weight: 700;
letter-spacing: -0.5px;
position: relative;
}
.form-title::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 3px;
background: var(--primary-gradient);
border-radius: 2px;
}
.form {
display: flex;
flex-direction: column;
gap: 24px;
}
.input-group {
display: flex;
flex-direction: column;
}
.label {
display: block;
margin-bottom: 8px;
color: var(--text-secondary);
font-size: 14px;
font-weight: 500;
letter-spacing: 0.5px;
transition: var(--transition);
}
.input,
.textarea {
width: 100%;
padding: 14px 18px;
border: 2px solid var(--border-color);
border-radius: 16px;
font-size: 16px;
font-family: inherit;
background: rgba(255, 255, 255, 0.8);
transition: var(--transition);
outline: none;
resize: vertical;
}
.input:focus,
.textarea:focus {
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
background: white;
transform: translateY(-1px);
}
.textarea {
min-height: 120px;
font-family: inherit;
}
.submit-btn {
width: 100%;
padding: 16px;
background: var(--primary-gradient);
color: white;
border: none;
border-radius: 16px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
margin-top: 8px;
position: relative;
overflow: hidden;
}
.submit-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.submit-btn:hover:not(.disabled)::before {
left: 100%;
}
.submit-btn:hover:not(.disabled) {
transform: translateY(-2px);
box-shadow: var(--shadow-hover);
}
.submit-btn.disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.response {
padding: 14px 18px;
border-radius: 16px;
text-align: center;
font-size: 14px;
font-weight: 500;
margin-top: 16px;
animation: fadeIn 0.4s ease-out;
border: 1px solid transparent;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.response.success {
background: var(--success-gradient);
color: #0a6c00;
border-color: rgba(79, 172, 254, 0.3);
}
.response.error {
background: var(--error-gradient);
color: #721c24;
border-color: rgba(250, 112, 154, 0.3);
}
@media (max-width: 480px) {
.form-card {
padding: 32px 24px;
margin: 10px;
}
.form-title {
font-size: 24px;
margin-bottom: 28px;
}
.input,
.textarea {
padding: 12px 16px;
}
}
=================================================================================================================================
ReactApplication.java:
package com.sunmeat.react;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@SpringBootApplication
public class ReactApplication {
public static void main(String[] args) {
SpringApplication.run(ReactApplication.class, args);
}
}
@RestController
@CrossOrigin(origins = "http://localhost:5173")
class SubmitController {
@PostMapping("/api/submit")
public String submitData(@RequestBody Map<String, String> data) {
System.out.println("Отримано дані з форми (з React на 5173):");
data.forEach((key, value) -> System.out.println(key + ": " + value));
return "Дані отримано на сервері! (Перевірте консоль)";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment