sms & SMTP

This commit is contained in:
2026-07-23 23:15:15 +02:00
parent 12fcb60b4c
commit a6b156695f
59 changed files with 4169 additions and 5 deletions
+49 -2
View File
@@ -5,6 +5,7 @@ import type { AuthUser, ContactPreference, PreferredLanguage } from './auth';
import { useNotifications, toDisplayNotifications } from './notifications';
import type { ServerNotification, DisplayNotification } from './notifications';
import { getManualTranslation } from './i18nOverrides';
import { AdminLeadsView, AdminCampaignsView, AdminMailConfigView } from './admin/marketing';
const heroImage = new URL('./assets/hero-interior.png', import.meta.url).href;
const cityImage = new URL('./assets/city-panorama.png', import.meta.url).href;
@@ -3636,7 +3637,8 @@ function accountStorageKey(suffix: string, user: AuthUser | null): string {
type AdminTab =
| 'dashboard' | 'listings' | 'users' | 'messages' | 'reports' | 'payments'
| 'stats' | 'settings' | 'moderation' | 'categories' | 'locations' | 'promotions' | 'forbiddenWords';
| 'stats' | 'settings' | 'moderation' | 'categories' | 'locations' | 'promotions' | 'forbiddenWords'
| 'leads' | 'campaigns' | 'mailConfig';
const ADMIN_TAB_META: Record<AdminTab, { title: string; subtitle: string }> = {
dashboard: { title: 'Panel administratora', subtitle: 'Zarządzaj ogłoszeniami i użytkownikami serwisu.' },
@@ -3652,6 +3654,9 @@ const ADMIN_TAB_META: Record<AdminTab, { title: string; subtitle: string }> = {
locations: { title: 'Lokalizacje', subtitle: 'Zarządzaj obsługiwanymi miastami i dzielnicami.' },
promotions: { title: 'Banery i promocje', subtitle: 'Zarządzaj banerami reklamowymi i promocjami.' },
forbiddenWords: { title: 'Zakazane słowa', subtitle: 'Lista wszystkich niedozwolonych słów używanych przez globalny filtr treści.' },
leads: { title: 'Leady', subtitle: 'Baza kontaktów marketingowych i grupy docelowe (dynamiczne segmenty).' },
campaigns: { title: 'Kampanie marketingowe', subtitle: 'Twórz i wysyłaj kampanie e-mail oraz SMS do grup docelowych.' },
mailConfig: { title: 'Konfiguracja wysyłki', subtitle: 'Ustawienia poczty SMTP i bramki SMS dla kampanii i powiadomień.' },
};
const ADMIN_NAV_ITEMS: { id: AdminTab; icon: string; label: string }[] = [
@@ -3660,6 +3665,8 @@ const ADMIN_NAV_ITEMS: { id: AdminTab; icon: string; label: string }[] = [
{ id: 'users', icon: 'user', label: 'Użytkownicy' },
{ id: 'messages', icon: 'message', label: 'Wiadomości' },
{ id: 'reports', icon: 'warning', label: 'Zgłoszenia' },
{ id: 'leads', icon: 'user', label: 'Leady' },
{ id: 'campaigns', icon: 'mail', label: 'Kampanie marketingowe' },
{ id: 'payments', icon: 'credit-card', label: 'Płatności' },
{ id: 'stats', icon: 'chart', label: 'Statystyki' },
{ id: 'settings', icon: 'gear', label: 'Ustawienia serwisu' },
@@ -3667,6 +3674,7 @@ const ADMIN_NAV_ITEMS: { id: AdminTab; icon: string; label: string }[] = [
const ADMIN_TOOL_ITEMS: { id: AdminTab; icon: string; label: string }[] = [
{ id: 'moderation', icon: 'check', label: 'Weryfikacja ogłoszeń' },
{ id: 'mailConfig', icon: 'gear', label: 'Konfiguracja wysyłki' },
{ id: 'forbiddenWords', icon: 'shield', label: 'Zakazane słowa' },
{ id: 'categories', icon: 'list', label: 'Kategorie' },
{ id: 'locations', icon: 'pin', label: 'Lokalizacje' },
@@ -5028,6 +5036,9 @@ function AdminPage({ onNavigate }: { onNavigate: (view: View) => void }) {
{!loading && tab === 'categories' && <AdminPlaceholderCard icon="list" label="Kategorie" />}
{!loading && tab === 'locations' && <AdminPlaceholderCard icon="pin" label="Lokalizacje" />}
{!loading && tab === 'promotions' && <AdminPlaceholderCard icon="spark" label="Banery i promocje" />}
{!loading && tab === 'leads' && <AdminLeadsView Icon={Icon} />}
{!loading && tab === 'campaigns' && <AdminCampaignsView Icon={Icon} />}
{!loading && tab === 'mailConfig' && <AdminMailConfigView Icon={Icon} />}
</div>
</div>
</section>
@@ -8806,16 +8817,52 @@ function AccountListingsPage({ onNavigate, activeView, onOpenListing }: { onNavi
function AccountSettingsPage({ onNavigate, activeView }: { onNavigate: (view: View) => void; activeView: View }) {
const { user } = useAuth();
const [accountSwitches, setAccountSwitches] = useState([true, true, false, false]);
// Indeks 4 ("Oferty promowane i newsletter") to zgoda marketingowa e-mail zapisywana na serwerze.
const [mailSwitches, setMailSwitches] = useState([true, true, true, false, false]);
const [smsConsent, setSmsConsent] = useState(true);
const address = user?.address ?? '';
const birthDate = user?.birthDate ?? '';
// Wczytaj aktualną zgodę marketingową użytkownika (steruje kwalifikacją do kampanii).
useEffect(() => {
if (!user) {
return;
}
let active = true;
apiFetch<{ emailConsent: boolean; smsConsent: boolean }>('/me/marketing')
.then((consent) => {
if (!active) {
return;
}
setSmsConsent(consent.smsConsent);
setMailSwitches((current) => current.map((value, idx) => (idx === 4 ? consent.emailConsent : value)));
})
.catch(() => {
// Brak zgody/sesji - zostawiamy wartości domyślne.
});
return () => {
active = false;
};
}, [user]);
const toggleAccountSwitch = (index: number) => {
setAccountSwitches((current) => current.map((value, idx) => (idx === index ? !value : value)));
};
const toggleMailSwitch = (index: number) => {
setMailSwitches((current) => current.map((value, idx) => (idx === index ? !value : value)));
setMailSwitches((current) => {
const next = current.map((value, idx) => (idx === index ? !value : value));
// Zgodę marketingową e-mail (indeks 4) utrwalamy na serwerze.
if (index === 4) {
apiFetch('/me/marketing', {
method: 'PUT',
body: JSON.stringify({ emailConsent: next[4], smsConsent }),
}).catch(() => {
// Błąd zapisu nie blokuje UI; wartość zostanie zsynchronizowana przy kolejnym wejściu.
});
}
return next;
});
};
return (