Merge pull request #34 from SlimusMinus/add-rules_accepted

added checkbox rules_accepted in post
This commit is contained in:
SlimusMinus
2026-07-29 01:25:36 +03:00
committed by GitHub
4 changed files with 81 additions and 9 deletions

View File

@@ -1,5 +1,6 @@
import {useCallback, useEffect, useState} from "react"; import {useCallback, useEffect, useState} from "react";
import CloseIcon from "./icons/CloseIcon"; import CloseIcon from "./icons/CloseIcon";
import RulesModal from "./RulesModal";
import { EMPTY_FORM, CATEGORY_KEYS } from "../config/feedConstants"; import { EMPTY_FORM, CATEGORY_KEYS } from "../config/feedConstants";
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { usePostMutation } from "../hooks/usePostMutation"; import { usePostMutation } from "../hooks/usePostMutation";
@@ -32,12 +33,13 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
category: initialData.category || "", category: initialData.category || "",
date: initialData.date || "", date: initialData.date || "",
phone: initialData.phone || "", phone: initialData.phone || "",
address: initialData.city || "" address: initialData.city || "",
rulesAccepted: true
} }
: EMPTY_FORM : EMPTY_FORM
); );
// Ошибки валидации — ключ совпадает с именем поля const [rulesModalOpen, setRulesModalOpen] = useState(false);
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const { const {
@@ -125,6 +127,8 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
data.append("address", form.address.trim()); data.append("address", form.address.trim());
} }
data.append("rulesAccepted", form.rulesAccepted ? "true" : "false");
// save(id, data): id = null → POST (создание), id = string → PUT (обновление) // save(id, data): id = null → POST (создание), id = string → PUT (обновление)
const result = await save(isEdit ? initialData.id : null, data); const result = await save(isEdit ? initialData.id : null, data);
if (!result) { if (!result) {
@@ -307,6 +311,32 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
{errors.phone && <p className="f-err-msg">{errors.phone}</p>} {errors.phone && <p className="f-err-msg">{errors.phone}</p>}
</div> </div>
{/* Чекбокс согласия с правилами */}
<div className="f-m-group">
<label className="f-rules-checkbox">
<input
type="checkbox"
className="f-rules-input"
checked={form.rulesAccepted}
onChange={e => set("rulesAccepted", e.target.checked)}
/>
<span className="f-rules-checkmark" />
<span className="f-rules-text">
{t('post.rulesCheckboxPrefix')}{' '}
<span
className="f-rules-link"
onClick={e => {
e.preventDefault();
setRulesModalOpen(true);
}}
>
{t('post.rulesLinkText')}
</span>
</span>
</label>
{errors.rulesAccepted && <p className="f-err-msg">{errors.rulesAccepted}</p>}
</div>
{/* Глобальная ошибка API (не связана с конкретным полем) */} {/* Глобальная ошибка API (не связана с конкретным полем) */}
{apiError && <p className="f-err-msg">{apiError}</p>} {apiError && <p className="f-err-msg">{apiError}</p>}
@@ -321,6 +351,11 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
</div> </div>
</div> </div>
{/* ── Модалка с правилами площадки ── */}
{rulesModalOpen && (
<RulesModal onClose={() => setRulesModalOpen(false)} />
)}
{/* ── Модалка-результат после отправки ── */} {/* ── Модалка-результат после отправки ── */}
{resultModal && ( {resultModal && (
<div className="f-modal-bg" onClick={e => { <div className="f-modal-bg" onClick={e => {

View File

@@ -23,5 +23,6 @@ export const EMPTY_FORM = {
location: "", location: "",
phone: "", phone: "",
date: "", date: "",
address: "" address: "",
rulesAccepted: false
}; };

View File

@@ -253,6 +253,41 @@
} }
} }
/* ── Rules Checkbox ── */
.f-rules-checkbox {
display: flex; align-items: center; gap: 10px; cursor: pointer;
user-select: none; padding: 6px 0;
}
.f-rules-input {
position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.f-rules-checkmark {
flex-shrink: 0; width: 20px; height: 20px;
border: 2px solid var(--border); border-radius: 5px;
background: var(--bg-input); transition: all .2s;
position: relative;
}
.f-rules-input:checked + .f-rules-checkmark {
background: var(--teal); border-color: var(--teal);
}
.f-rules-input:checked + .f-rules-checkmark::after {
content: ""; position: absolute; left: 5px; top: 1px;
width: 6px; height: 10px;
border: solid #082320; border-width: 0 2.5px 2.5px 0;
transform: rotate(45deg);
}
.f-rules-input:focus + .f-rules-checkmark {
box-shadow: 0 0 0 3px rgba(var(--teal-rgb), .25);
}
.f-rules-text {
font-size: .82rem; color: var(--muted); line-height: 1.4;
}
.f-rules-link {
color: var(--teal); cursor: pointer; text-decoration: underline;
text-underline-offset: 2px; transition: opacity .15s;
}
.f-rules-link:hover { opacity: .75; }
/* ── Result Modal (success / blocked) ── */ /* ── Result Modal (success / blocked) ── */
.f-modal-result { .f-modal-result {
text-align: center; text-align: center;

View File

@@ -6,5 +6,6 @@ export function validatePostForm(form) {
if (!form.description.trim()) errors.description = i18n.t('validation.descriptionRequired'); if (!form.description.trim()) errors.description = i18n.t('validation.descriptionRequired');
if (!form.category) errors.category = i18n.t('validation.categoryRequired'); if (!form.category) errors.category = i18n.t('validation.categoryRequired');
if (!form.phone.trim()) errors.phone = i18n.t('validation.phoneRequired'); if (!form.phone.trim()) errors.phone = i18n.t('validation.phoneRequired');
if (!form.rulesAccepted) errors.rulesAccepted = i18n.t('validation.rulesRequired');
return errors; return errors;
} }