togethere.cloud/public_html/account/profile/index.php

1100 lines
31 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/session_bootstrap.php';
if (empty($_SESSION['logged_in'])) {
header('Location: https://togethere.cloud/login/');
exit();
}
$host = "localhost";
$db = "togethere_cloud";
$user = "root";
$pass = "HasloDoSQL";
try {
$pdo = og_session_get_pdo();
if (!$pdo instanceof PDO) {
throw new PDOException('Nie udało się zainicjalizować połączenia z bazą danych.');
}
} catch (PDOException $e) {
die("Błąd połączenia z bazą danych: " . $e->getMessage());
}
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$userData = $stmt->fetch(PDO::FETCH_ASSOC);
$phoneCountryOptions = [
'+48' => 'Polska (+48)',
'+44' => 'Wielka Brytania (+44)',
'+49' => 'Niemcy (+49)',
'+33' => 'Francja (+33)',
'+34' => 'Hiszpania (+34)',
'+39' => 'Włochy (+39)',
'+31' => 'Holandia (+31)',
'+420' => 'Czechy (+420)',
'+421' => 'Słowacja (+421)',
'+1' => 'USA/Kanada (+1)'
];
$storedPhoneNumber = trim((string)($userData['phone_number'] ?? ''));
$currentPhoneCountryCode = '';
$currentPhoneNumber = $storedPhoneNumber;
if ($storedPhoneNumber !== '' && preg_match('/^(\+\d{1,4})\s*(.*)$/', $storedPhoneNumber, $matches)) {
$parsedCode = trim((string)$matches[1]);
$parsedLocal = trim((string)$matches[2]);
if (array_key_exists($parsedCode, $phoneCountryOptions)) {
$currentPhoneCountryCode = $parsedCode;
$currentPhoneNumber = $parsedLocal;
}
}
if (!$userData) {
session_destroy();
header('Location: /login/');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/account_suspension.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/user_avatar.php';
$suspensionState = og_is_current_user_suspended($pdo);
$isSuspended = (bool)($suspensionState['is_suspended'] ?? false);
$suspendedReason = (string)($suspensionState['reason'] ?? '');
$suspendedUntil = (string)($suspensionState['suspended_until'] ?? '');
$profileFormDisabled = $isSuspended ? 'disabled' : '';
$avatarFile = og_get_user_avatar_file($pdo, (int)($_SESSION['user_id'] ?? 0));
if (!$avatarFile && !empty($_SESSION['profile_avatar_file'])) {
$avatarFile = trim((string)$_SESSION['profile_avatar_file']);
}
$avatarUrl = og_avatar_file_to_url($avatarFile);
$avatarInitial = og_avatar_initial((string)($userData['username'] ?? 'U'));
$displayFirstName = trim((string)($userData['first_name'] ?? ''));
$displayLastName = trim((string)($userData['last_name'] ?? ''));
$displayFullName = trim($displayFirstName . ' ' . $displayLastName);
if ($displayFullName === '') {
$displayFullName = (string)($userData['username'] ?? 'Użytkownik');
}
$displayNickname = (string)($userData['username'] ?? '');
?>
<!DOCTYPE html>
<html>
<head>
<title>Informacje Profilowe | kontakt: wspolpraca@togethere.cloud</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<meta name="keywords" content="projekty przyszłości"/>
<link rel="stylesheet" href="/css/header.css" type="text/css" media="all"/>
<link rel="stylesheet" href="/css/footer.css" type="text/css" media="all"/>
<link href="//fonts.googleapis.com/css?family=Lato:400,500,600,700,800,900" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #e3f2fd 0%, #ffffff 100%);
min-height: 100vh;
}
h1 {
color: #1976d2;
padding: 30px;
margin-bottom: 20px;
text-align: center;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.nav-link {
display: inline-block;
margin: 0 auto 30px;
padding: 12px 30px;
background: linear-gradient(135deg, #42a5f5, #1976d2);
color: white;
text-decoration: none;
border-radius: 25px;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(25, 118, 210, 0.3);
}
.nav-link:hover {
background: linear-gradient(135deg, #1976d2, #0d47a1);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(25, 118, 210, 0.4);
}
.nav-container {
display: flex;
width: 100%;
text-align: center;
justify-content: center;
align-items: center;
margin-bottom: 30px;
}
.nav-container .box {
display: flex;
gap: 15px;
}
nav.navigation {
margin-top: 0px !important;
}
.settings-container {
max-width: 100%;
width: 100%;
margin: 0 auto;
padding: 20px;
}
.settings-section {
background: white;
border-radius: 15px;
padding: 35px;
margin-bottom: 30px;
box-shadow: 0 10px 30px rgba(100, 181, 246, 0.2);
width: 100%;
max-width: 100%;
}
.settings-section h2 {
color: #1976d2;
font-size: 1.8em;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 3px solid #64b5f6;
}
.form-group {
margin-bottom: 25px;
width: 100% !important;
}
.form-group.highlight-target {
padding: 16px;
border-radius: 12px;
border: 2px solid #ff9800;
background: linear-gradient(180deg, rgba(255, 152, 0, 0.1) 0%, rgba(255, 152, 0, 0.03) 100%);
box-shadow: 0 8px 24px rgba(255, 152, 0, 0.12);
}
.form-help-callout {
margin-top: 10px;
padding: 12px 14px;
border-radius: 10px;
background: #fff3cd;
border-left: 4px solid #ff9800;
color: #7a4b00;
line-height: 1.6;
font-size: 0.95em;
}
form div label {
padding-left: 5px !important;
}
.form-group label {
display: block;
color: #2c3e50;
font-weight: 600;
margin-bottom: 10px;
font-size: 1.05em;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group select {
width: 100% !important;
max-width: 100% !important;
padding: 15px;
border: 2px solid #64b5f6;
border-radius: 8px;
font-size: 1em;
transition: all 0.3s ease;
box-sizing: border-box;
}
.form-group input:focus,
.form-group select:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 10px rgba(25, 118, 210, 0.2);
}
.form-group.highlight-target input:focus {
border-color: #ff9800;
box-shadow: 0 0 0 4px rgba(255, 152, 0, 0.15);
}
.phone-row {
display: grid;
grid-template-columns: 220px 1fr;
gap: 20px;
}
.form-row {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
.btn {
padding: 15px 40px;
border: none;
border-radius: 8px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
width: 100% !important;
max-width: 100% !important;
}
.btn-primary {
background: linear-gradient(135deg, #42a5f5, #1976d2);
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, #1976d2, #0d47a1);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(25, 118, 210, 0.4);
}
.btn-secondary {
background: #95a5a6;
color: white;
}
.btn-secondary:hover {
background: #7f8c8d;
}
.button-group {
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 25px;
}
.profile-hero {
display: grid;
grid-template-columns: 168px 1fr;
gap: 26px;
align-items: center;
padding: 28px;
margin-bottom: 30px;
border-radius: 18px;
background: linear-gradient(130deg, #f3faff 0%, #e3f2fd 55%, #f8fbff 100%);
box-shadow: 0 12px 28px rgba(100, 181, 246, 0.2);
}
.profile-avatar-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.profile-avatar-button {
width: 150px;
height: 150px;
border: 0;
padding: 0;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
background: linear-gradient(135deg, #1e88e5, #42a5f5);
box-shadow: 0 16px 30px rgba(33, 150, 243, 0.28);
position: relative;
}
.profile-avatar-button.disabled {
cursor: not-allowed;
opacity: 0.65;
}
.profile-avatar-button::after {
content: 'Zmień';
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 8px 0;
font-size: 0.85em;
font-weight: 700;
letter-spacing: 0.04em;
color: #fff;
background: rgba(0, 0, 0, 0.46);
transition: opacity 0.2s ease;
}
.profile-avatar-button:hover::after,
.profile-avatar-button:focus-visible::after {
opacity: 1;
}
.profile-avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.profile-avatar-fallback {
width: 100%;
height: 100%;
display: grid;
place-items: center;
font-size: 3.1em;
font-weight: 800;
color: #ffffff;
}
.profile-avatar-hint {
font-size: 0.86em;
color: #607d8b;
text-align: center;
}
.profile-identity {
display: flex;
flex-direction: column;
gap: 8px;
}
.profile-identity-name {
font-size: 2em;
line-height: 1.12;
font-weight: 800;
color: #0d47a1;
margin: 0;
}
.profile-identity-nick {
font-size: 1.05em;
font-weight: 700;
color: #1976d2;
margin: 0;
}
.profile-identity-email {
font-size: 0.95em;
color: #607d8b;
margin: 0;
}
.avatar-upload-overlay {
position: fixed;
inset: 0;
z-index: 12000;
background: rgba(3, 11, 21, 0.72);
display: none;
align-items: center;
justify-content: center;
padding: 20px;
}
.avatar-upload-overlay.active {
display: flex;
}
.avatar-upload-modal {
position: relative;
width: min(680px, 96vw);
background: #ffffff;
border-radius: 16px;
padding: 26px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
}
.avatar-modal-close {
position: absolute;
top: 10px;
right: 10px;
border: 0;
width: 34px;
height: 34px;
border-radius: 50%;
font-size: 1.2em;
cursor: pointer;
background: #eef4fb;
color: #355a75;
}
.avatar-modal-title {
margin: 0 0 8px;
color: #0d47a1;
font-size: 1.45em;
}
.avatar-modal-subtitle {
margin: 0 0 14px;
color: #607d8b;
line-height: 1.5;
}
.avatar-dropzone {
position: relative;
border: 2px dashed #90caf9;
background: #f5fbff;
border-radius: 14px;
padding: 24px;
text-align: center;
margin-bottom: 14px;
transition: border-color 0.2s ease, background 0.2s ease;
}
.avatar-dropzone.dragover {
border-color: #1976d2;
background: #e8f4ff;
}
.avatar-dropzone input[type="file"] {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
}
.avatar-dropzone strong {
display: block;
color: #1565c0;
font-size: 1.05em;
margin-bottom: 6px;
}
.avatar-editor {
display: none;
grid-template-columns: 320px 1fr;
gap: 18px;
align-items: start;
margin-top: 8px;
}
.avatar-editor.active {
display: grid;
}
.avatar-crop-stage {
position: relative;
width: 320px;
height: 320px;
border-radius: 10px;
overflow: hidden;
box-shadow: inset 0 0 0 2px rgba(33, 150, 243, 0.25);
background: repeating-conic-gradient(#f2f8ff 0% 25%, #ebf2fb 0% 50%) 50%/20px 20px;
}
.avatar-crop-canvas {
width: 320px;
height: 320px;
display: block;
}
.avatar-circle-mask {
position: absolute;
inset: 0;
pointer-events: none;
}
.avatar-circle-mask::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 270px;
height: 270px;
border-radius: 50%;
box-shadow: 0 0 0 999px rgba(0, 0, 0, 0.26);
border: 2px solid rgba(255, 255, 255, 0.95);
}
.avatar-editor-controls {
display: flex;
flex-direction: column;
gap: 14px;
}
.avatar-editor-controls label {
margin: 0;
font-size: 0.95em;
color: #2c3e50;
}
.avatar-editor-controls input[type="range"] {
width: 100%;
}
.avatar-upload-hint {
font-size: 0.9em;
color: #607d8b;
line-height: 1.5;
}
.avatar-modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 16px;
}
.avatar-modal-actions .btn {
width: auto !important;
min-width: 130px;
padding-inline: 18px;
}
.avatar-modal-message {
margin-top: 10px;
font-size: 0.9em;
color: #607d8b;
min-height: 20px;
}
@media (max-width: 768px) {
h1 {
font-size: 2em;
padding: 20px;
}
.settings-section {
padding: 25px 20px;
}
.form-row {
grid-template-columns: 1fr;
}
.phone-row {
grid-template-columns: 1fr;
}
.profile-hero {
grid-template-columns: 1fr;
justify-items: center;
text-align: center;
padding: 24px 16px;
}
.profile-identity {
align-items: center;
}
.avatar-upload-modal {
padding: 18px;
}
.avatar-editor {
grid-template-columns: 1fr;
}
.avatar-crop-stage,
.avatar-crop-canvas {
width: 280px;
height: 280px;
}
.avatar-circle-mask::before {
width: 236px;
height: 236px;
}
.button-group {
flex-direction: column;
}
.btn {
width: 100%;
}
}
.footer-copyright {
display: flex;
flex-direction: column;
gap: 40px;
}
div.polices p {
color: black !important;
font-weight: bold !important;
}
div.polices p a {
text-decoration: none !important;
font-size: 1rem;
}
</style>
</head>
<body>
<?php
if (!empty($_SESSION['logged_in'])) {
include $_SERVER['DOCUMENT_ROOT'].'/global/navLogined.php';
} else {
include $_SERVER['DOCUMENT_ROOT'].'/global/navNoLogined.php';
}
?>
<main>
<div class="settings-container">
<?php
$focusField = isset($_GET['focus']) ? trim((string) $_GET['focus']) : '';
$usernameRequired = isset($_GET['username_required']) && $_GET['username_required'] === '1';
?>
<h1>⚙️ Ustawienia Konta</h1>
<div class="nav-container">
<div class="box">
<a href="/account/profile/" class="nav-link">👤 Informacje profilowe</a>
<a href="/account/settings/" class="nav-link">⚙️ Pozostałe ustawienia</a>
</div>
</div>
<?php if (isset($_GET['success']) && $_GET['success'] === 'personal_data'): ?>
<div style="background: #d4edda; color: #155724; padding: 15px; border-radius: 8px; margin-bottom: 20px; text-align: center; border-left: 4px solid #28a745;">
✅ Dane osobowe zostały zaktualizowane!
</div>
<?php elseif (isset($_GET['success']) && $_GET['success'] === 'avatar_updated'): ?>
<div style="background: #d4edda; color: #155724; padding: 15px; border-radius: 8px; margin-bottom: 20px; text-align: center; border-left: 4px solid #28a745;">
✅ Zdjęcie profilowe zostało zaktualizowane!
</div>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<div style="background: #f8d7da; color: #721c24; padding: 15px; border-radius: 8px; margin-bottom: 20px; text-align: center; border-left: 4px solid #dc3545;">
❌ <?= htmlspecialchars($_GET['error']) ?>
</div>
<?php endif; ?>
<div class="profile-hero">
<div class="profile-avatar-wrap">
<button type="button" class="profile-avatar-button<?= $isSuspended ? ' disabled' : '' ?>" id="avatarToggleBtn" <?= $isSuspended ? 'disabled' : '' ?> aria-controls="avatarUploadPanel" aria-expanded="false" title="Kliknij, aby zmienić zdjęcie">
<?php if ($avatarUrl): ?>
<img class="profile-avatar-img" src="<?= htmlspecialchars($avatarUrl, ENT_QUOTES, 'UTF-8') ?>" alt="Zdjęcie profilowe">
<?php else: ?>
<span class="profile-avatar-fallback"><?= htmlspecialchars($avatarInitial, ENT_QUOTES, 'UTF-8') ?></span>
<?php endif; ?>
</button>
<div class="profile-avatar-hint">Kliknij zdjęcie, aby dodać nowe</div>
</div>
<div class="profile-identity">
<h2 class="profile-identity-name"><?= htmlspecialchars($displayFullName, ENT_QUOTES, 'UTF-8') ?></h2>
<p class="profile-identity-nick">@<?= htmlspecialchars($displayNickname, ENT_QUOTES, 'UTF-8') ?></p>
<p class="profile-identity-email"><?= htmlspecialchars((string)($userData['email'] ?? ''), ENT_QUOTES, 'UTF-8') ?></p>
<p class="avatar-upload-hint">Avatar jest zapisywany jako kwadrat. W podglądzie widzisz okrągłą strefę docelową.</p>
</div>
</div>
<div class="avatar-upload-overlay" id="avatarUploadOverlay" aria-hidden="true">
<div class="avatar-upload-modal" role="dialog" aria-modal="true" aria-labelledby="avatarModalTitle">
<button type="button" class="avatar-modal-close" id="avatarModalClose" aria-label="Zamknij">×</button>
<h3 class="avatar-modal-title" id="avatarModalTitle">Przytnij zdjęcie profilowe</h3>
<p class="avatar-modal-subtitle">Przeciągnij obraz w obszarze kadru i ustaw skalę. Zapisany zostanie kwadrat z widocznym podglądem koła.</p>
<div class="avatar-dropzone" id="avatarDropzone">
<strong>Upuść zdjęcie tutaj albo kliknij, aby wybrać</strong>
<span>Dozwolone: JPG, PNG, GIF, WEBP. Maksymalny rozmiar wejściowy 10MB.</span>
<input type="file" id="avatarFileInput" accept="image/png,image/jpeg,image/gif,image/webp" <?= $profileFormDisabled ?>>
</div>
<div class="avatar-editor" id="avatarEditor">
<div class="avatar-crop-stage" id="avatarCropStage">
<canvas id="avatarCropCanvas" class="avatar-crop-canvas" width="320" height="320"></canvas>
<div class="avatar-circle-mask"></div>
</div>
<div class="avatar-editor-controls">
<label for="avatarZoomRange">Skala zdjęcia</label>
<input type="range" id="avatarZoomRange" min="100" max="300" step="1" value="100">
<div class="avatar-upload-hint">Przesuwaj zdjęcie myszką lub palcem, aby ustawić centralny kadr.</div>
</div>
</div>
<div class="avatar-modal-actions">
<button type="button" class="btn btn-secondary" id="avatarCancelBtn">Anuluj</button>
<button type="button" class="btn btn-primary" id="avatarAttachBtn" disabled>Załącz</button>
</div>
<div class="avatar-modal-message" id="avatarModalMessage">Wybierz plik, aby rozpocząć kadrowanie.</div>
<form method="POST" action="/account/settings/update_avatar.php" enctype="multipart/form-data" id="avatarUploadForm" style="display:none;">
<input type="file" name="avatar_file" id="avatarCroppedInput">
</form>
</div>
</div>
<div class="settings-section" id="profile">
<h2>👤 Dane osobowe</h2>
<form method="POST" action="/account/settings/update_settings.php">
<input type="hidden" name="action" value="personal_data">
<div class="form-row">
<div class="form-group">
<label for="firstName">Imię</label>
<input type="text" id="firstName" name="first_name" value="<?= htmlspecialchars($userData['first_name'] ?? '') ?>" required <?= $profileFormDisabled ?>>
</div>
<div class="form-group">
<label for="lastName">Nazwisko</label>
<input type="text" id="lastName" name="last_name" value="<?= htmlspecialchars($userData['last_name'] ?? '') ?>" required <?= $profileFormDisabled ?>>
</div>
</div>
<div class="form-group">
<label for="email">Adres e-mail</label>
<input type="email" id="email" value="<?= htmlspecialchars($userData['email']) ?>" disabled>
<small style="color: #7f8c8d;">
<a href="<?= $isSuspended ? '#' : '/account/settings/change_email_request.php' ?>" style="color: #2196F3; text-decoration: none; font-weight: 600; pointer-events: <?= $isSuspended ? 'none' : 'auto' ?>; opacity: <?= $isSuspended ? '0.6' : '1' ?>;">
📧 Zmień adres email
</a>
</small>
</div>
<div class="form-group<?= $focusField === 'username' ? ' highlight-target' : '' ?>" id="usernameFieldGroup">
<label for="username">Nazwa użytkownika</label>
<input type="text" id="username" name="username" value="<?= htmlspecialchars($userData['username']) ?>" required maxlength="20" pattern="[A-Za-z0-9_&!]{1,20}" title="Dozwolone: litery angielskie, cyfry, _, &, ! (max 20 znaków)" <?= $profileFormDisabled ?>>
<?php if ($usernameRequired): ?>
<div class="form-help-callout">
Ustaw tutaj username, żeby wejść do gry. Wymagany format: 1-20 znaków, dozwolone tylko litery angielskie, cyfry oraz znaki <strong>_ &amp; !</strong>.
</div>
<?php endif; ?>
</div>
<div class="phone-row">
<div class="form-group">
<label for="phoneCountryCode">Kierunkowy państwa</label>
<select id="phoneCountryCode" name="phone_country_code" <?= $profileFormDisabled ?>>
<option value="">Wybierz kierunkowy</option>
<?php foreach ($phoneCountryOptions as $code => $label): ?>
<option value="<?= htmlspecialchars($code, ENT_QUOTES, 'UTF-8') ?>" <?= $currentPhoneCountryCode === $code ? 'selected' : '' ?>>
<?= htmlspecialchars($label, ENT_QUOTES, 'UTF-8') ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="phoneNumber">Numer telefonu</label>
<input type="text" id="phoneNumber" name="phone_number" value="<?= htmlspecialchars($currentPhoneNumber, ENT_QUOTES, 'UTF-8') ?>" maxlength="20" inputmode="numeric" pattern="[0-9\s\-]{4,20}" title="Dozwolone cyfry, spacje i myślnik" <?= $profileFormDisabled ?>>
</div>
</div>
<div class="button-group">
<button type="submit" class="btn btn-primary" <?= $profileFormDisabled ?>>Zapisz zmiany</button>
<button type="button" class="btn btn-secondary" onclick="location.reload()" <?= $profileFormDisabled ?>>Anuluj</button>
</div>
</form>
</div>
</div>
</main>
<?php
if (!empty($_SESSION['logged_in'])) {
include $_SERVER['DOCUMENT_ROOT'].'/global/footerLogined.php';
} else {
include $_SERVER['DOCUMENT_ROOT'].'/global/footerNoLogined.php';
}
?>
<script>
(function() {
var toggleBtn = document.getElementById('avatarToggleBtn');
var overlay = document.getElementById('avatarUploadOverlay');
var closeBtn = document.getElementById('avatarModalClose');
var cancelBtn = document.getElementById('avatarCancelBtn');
var dropzone = document.getElementById('avatarDropzone');
var fileInput = document.getElementById('avatarFileInput');
var editor = document.getElementById('avatarEditor');
var zoomRange = document.getElementById('avatarZoomRange');
var canvas = document.getElementById('avatarCropCanvas');
var attachBtn = document.getElementById('avatarAttachBtn');
var message = document.getElementById('avatarModalMessage');
var uploadForm = document.getElementById('avatarUploadForm');
var croppedInput = document.getElementById('avatarCroppedInput');
if (!toggleBtn || !overlay || !fileInput || !editor || !zoomRange || !canvas || !attachBtn || !message || !uploadForm || !croppedInput) {
return;
}
var ctx = canvas.getContext('2d');
var cropSize = 320;
var loadedImage = null;
var baseScale = 1;
var scale = 1;
var offsetX = 0;
var offsetY = 0;
var dragging = false;
var dragStartX = 0;
var dragStartY = 0;
var startOffsetX = 0;
var startOffsetY = 0;
function setMessage(text, isError) {
message.textContent = text;
message.style.color = isError ? '#c62828' : '#607d8b';
}
function openModal() {
overlay.classList.add('active');
overlay.setAttribute('aria-hidden', 'false');
toggleBtn.setAttribute('aria-expanded', 'true');
}
function closeModal() {
overlay.classList.remove('active');
overlay.setAttribute('aria-hidden', 'true');
toggleBtn.setAttribute('aria-expanded', 'false');
}
function clampOffsets() {
if (!loadedImage) {
return;
}
var scaledW = loadedImage.width * scale;
var scaledH = loadedImage.height * scale;
var minX = cropSize - scaledW;
var minY = cropSize - scaledH;
offsetX = Math.min(0, Math.max(minX, offsetX));
offsetY = Math.min(0, Math.max(minY, offsetY));
}
function drawPreview() {
ctx.clearRect(0, 0, cropSize, cropSize);
if (!loadedImage) {
return;
}
ctx.drawImage(loadedImage, offsetX, offsetY, loadedImage.width * scale, loadedImage.height * scale);
}
function setImage(file) {
if (!file) {
return;
}
var allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (allowed.indexOf(file.type) === -1) {
setMessage('Dozwolone sa tylko obrazy JPG, PNG, GIF, WEBP.', true);
return;
}
if (file.size > 10 * 1024 * 1024) {
setMessage('Plik wejsciowy jest za duzy. Maksymalnie 10MB.', true);
return;
}
var reader = new FileReader();
reader.onload = function(evt) {
var img = new Image();
img.onload = function() {
loadedImage = img;
baseScale = Math.max(cropSize / img.width, cropSize / img.height);
scale = baseScale;
offsetX = (cropSize - (img.width * scale)) / 2;
offsetY = (cropSize - (img.height * scale)) / 2;
zoomRange.value = '100';
editor.classList.add('active');
attachBtn.disabled = false;
setMessage('Ustaw kadr i kliknij "Zalacz".', false);
drawPreview();
};
img.onerror = function() {
setMessage('Nie mozna odczytac wybranego obrazu.', true);
};
img.src = String(evt.target && evt.target.result ? evt.target.result : '');
};
reader.readAsDataURL(file);
}
function getPointFromEvent(evt) {
if (evt.touches && evt.touches.length) {
return { x: evt.touches[0].clientX, y: evt.touches[0].clientY };
}
return { x: evt.clientX, y: evt.clientY };
}
toggleBtn.addEventListener('click', function() {
openModal();
});
if (closeBtn) {
closeBtn.addEventListener('click', closeModal);
}
if (cancelBtn) {
cancelBtn.addEventListener('click', closeModal);
}
overlay.addEventListener('click', function(evt) {
if (evt.target === overlay) {
closeModal();
}
});
document.addEventListener('keydown', function(evt) {
if (evt.key === 'Escape' && overlay.classList.contains('active')) {
closeModal();
}
});
fileInput.addEventListener('change', function() {
setImage(fileInput.files && fileInput.files.length ? fileInput.files[0] : null);
});
dropzone.addEventListener('dragover', function(evt) {
evt.preventDefault();
dropzone.classList.add('dragover');
});
dropzone.addEventListener('dragleave', function() {
dropzone.classList.remove('dragover');
});
dropzone.addEventListener('drop', function(evt) {
evt.preventDefault();
dropzone.classList.remove('dragover');
if (!evt.dataTransfer || !evt.dataTransfer.files || !evt.dataTransfer.files.length) {
return;
}
setImage(evt.dataTransfer.files[0]);
});
zoomRange.addEventListener('input', function() {
if (!loadedImage) {
return;
}
var prevScale = scale;
var ratio = Number(zoomRange.value) / 100;
scale = baseScale * ratio;
var centerX = cropSize / 2;
var centerY = cropSize / 2;
offsetX = centerX - ((centerX - offsetX) / prevScale) * scale;
offsetY = centerY - ((centerY - offsetY) / prevScale) * scale;
clampOffsets();
drawPreview();
});
function startDrag(evt) {
if (!loadedImage) {
return;
}
dragging = true;
var p = getPointFromEvent(evt);
dragStartX = p.x;
dragStartY = p.y;
startOffsetX = offsetX;
startOffsetY = offsetY;
evt.preventDefault();
}
function moveDrag(evt) {
if (!dragging || !loadedImage) {
return;
}
var p = getPointFromEvent(evt);
offsetX = startOffsetX + (p.x - dragStartX);
offsetY = startOffsetY + (p.y - dragStartY);
clampOffsets();
drawPreview();
evt.preventDefault();
}
function endDrag() {
dragging = false;
}
canvas.addEventListener('mousedown', startDrag);
canvas.addEventListener('touchstart', startDrag, { passive: false });
window.addEventListener('mousemove', moveDrag);
window.addEventListener('touchmove', moveDrag, { passive: false });
window.addEventListener('mouseup', endDrag);
window.addEventListener('touchend', endDrag);
attachBtn.addEventListener('click', function() {
if (!loadedImage) {
setMessage('Najpierw wybierz plik do kadrowania.', true);
return;
}
var outputCanvas = document.createElement('canvas');
outputCanvas.width = 512;
outputCanvas.height = 512;
var out = outputCanvas.getContext('2d');
var factor = outputCanvas.width / cropSize;
out.drawImage(
loadedImage,
offsetX * factor,
offsetY * factor,
loadedImage.width * scale * factor,
loadedImage.height * scale * factor
);
function submitCroppedBlob(blob, mimeType, fileName) {
if (!blob) {
setMessage('Nie udalo sie przygotowac pliku do wysylki.', true);
return;
}
if (typeof DataTransfer === 'undefined') {
setMessage('Przegladarka nie wspiera automatycznego przeslania. Uzyj nowszej wersji.', true);
return;
}
var file = new File([blob], fileName, { type: mimeType });
var dt = new DataTransfer();
dt.items.add(file);
croppedInput.files = dt.files;
uploadForm.submit();
}
outputCanvas.toBlob(function(webpBlob) {
if (webpBlob) {
submitCroppedBlob(webpBlob, 'image/webp', 'avatar.webp');
return;
}
// Fallback dla starszych przeglądarek bez wsparcia WebP w canvas.toBlob
outputCanvas.toBlob(function(jpegBlob) {
submitCroppedBlob(jpegBlob, 'image/jpeg', 'avatar.jpg');
}, 'image/jpeg', 0.9);
}, 'image/webp', 0.86);
});
setMessage('Wybierz plik, aby rozpocząć kadrowanie.', false);
})();
(function() {
var shouldFocusUsername = <?php echo $focusField === 'username' ? 'true' : 'false'; ?>;
if (!shouldFocusUsername) {
return;
}
var input = document.getElementById('username');
var group = document.getElementById('usernameFieldGroup');
if (group && typeof group.scrollIntoView === 'function') {
group.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
if (input && typeof input.focus === 'function') {
window.setTimeout(function() {
input.focus();
if (typeof input.select === 'function') {
input.select();
}
}, 160);
}
})();
(function() {
var usernameInput = document.getElementById('username');
if (!usernameInput) {
return;
}
usernameInput.addEventListener('invalid', function() {
if (usernameInput.validity.patternMismatch) {
usernameInput.setCustomValidity('Nazwa użytkownika może zawierać tylko litery angielskie, cyfry oraz znaki _ & ! (max 20 znaków).');
} else {
usernameInput.setCustomValidity('');
}
});
usernameInput.addEventListener('input', function() {
usernameInput.setCustomValidity('');
});
})();
</script>
</body>
</html>