chore: fix ui
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
// ============================================
|
||||
// pages/ConsultationHistorique.tsx - VERSION AVEC FONT AWESOME
|
||||
// ============================================
|
||||
// Page d'historique avec 4 compteurs de points distincts
|
||||
// ✅ AJOUT: Vérification continue de l'authentification
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Navbar from '../../components/Navbar';
|
||||
@@ -19,9 +13,7 @@ import {
|
||||
} from '../../api/api';
|
||||
import type { PublicSettings } from '../../api/api';
|
||||
import type { CompletedOrder, ClientStats, PenaltyInfo } from "../../api/api_types";
|
||||
import { Package, MapPin, User, TrendingUp } from 'lucide-react';
|
||||
|
||||
// ✅ Import Font Awesome
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import {
|
||||
faCannabis,
|
||||
@@ -31,13 +23,20 @@ import {
|
||||
faStar,
|
||||
faTrophy,
|
||||
faExclamationTriangle,
|
||||
faCheckCircle,
|
||||
faShieldAlt,
|
||||
faGift,
|
||||
faReceipt,
|
||||
faMapMarkerAlt,
|
||||
faClock,
|
||||
faBicycle,
|
||||
faChevronRight,
|
||||
faCheckCircle,
|
||||
faHistory,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
function ConsultationHistorique() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
const [orders, setOrders] = useState<CompletedOrder[]>([]);
|
||||
const [clientStats, setClientStats] = useState<ClientStats | null>(null);
|
||||
const [penalties, setPenalties] = useState<PenaltyInfo | null>(null);
|
||||
@@ -46,28 +45,15 @@ function ConsultationHistorique() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
// ✅ VÉRIFICATION INITIALE DE L'AUTHENTIFICATION
|
||||
useEffect(() => {
|
||||
const checkAuth = () => {
|
||||
if (!isUserAuthenticated()) {
|
||||
console.log('❌ [ConsultationHistorique] Utilisateur non authentifié, redirection vers /login/client');
|
||||
navigate('/login/client', { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
checkAuth();
|
||||
if (!isUserAuthenticated()) navigate('/login/client', { replace: true });
|
||||
}, [navigate]);
|
||||
|
||||
// ✅ VÉRIFICATION CONTINUE (toutes les 5 secondes)
|
||||
useEffect(() => {
|
||||
const authInterval = setInterval(() => {
|
||||
if (!isUserAuthenticated()) {
|
||||
console.log('❌ [ConsultationHistorique] Session expirée, redirection vers /login/client');
|
||||
navigate('/login/client', { replace: true });
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
if (!isUserAuthenticated()) navigate('/login/client', { replace: true });
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(authInterval);
|
||||
return () => clearInterval(interval);
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -82,34 +68,18 @@ function ConsultationHistorique() {
|
||||
}, []);
|
||||
|
||||
const fetchHistory = async () => {
|
||||
// ✅ Vérifier l'auth avant de charger l'historique
|
||||
if (!isUserAuthenticated()) {
|
||||
console.log('❌ [fetchHistory] Non authentifié');
|
||||
navigate('/login/client', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isUserAuthenticated()) { navigate('/login/client', { replace: true }); return; }
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
console.log('📚 [HISTORY] Chargement historique...');
|
||||
const result = await getMyCompletedOrders();
|
||||
|
||||
if (result.success) {
|
||||
console.log('✅ [HISTORY] Historique chargé:', result.count, 'commandes');
|
||||
|
||||
console.log('🔍 [DEBUG] result.client_stats:', result.client_stats);
|
||||
console.log('🔍 [DEBUG] points:', result.client_stats?.points);
|
||||
|
||||
setOrders(result.commands);
|
||||
setClientStats(result.client_stats || null);
|
||||
} else {
|
||||
console.error('❌ [HISTORY] Erreur:', result.message);
|
||||
setError(result.message || 'Erreur lors du chargement');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ [HISTORY] Erreur catch:', err);
|
||||
} catch {
|
||||
setError('Erreur de connexion');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -117,39 +87,23 @@ function ConsultationHistorique() {
|
||||
};
|
||||
|
||||
const fetchPenalties = async () => {
|
||||
// ✅ Vérifier l'auth avant de charger les pénalités
|
||||
if (!isUserAuthenticated()) {
|
||||
console.log('❌ [fetchPenalties] Non authentifié');
|
||||
navigate('/login/client', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isUserAuthenticated()) return;
|
||||
try {
|
||||
console.log('🚨 [PENALTIES] Chargement pénalités...');
|
||||
const result = await getMyPenalties();
|
||||
|
||||
if (result.success && result.data) {
|
||||
console.log('✅ [PENALTIES] Pénalités chargées:', result.data);
|
||||
setPenalties(result.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('❌ [PENALTIES] Erreur:', err);
|
||||
}
|
||||
if (result.success && result.data) setPenalties(result.data);
|
||||
} catch { /* ignore */ }
|
||||
};
|
||||
|
||||
const getProductCount = (totalPrix: number): number => {
|
||||
return Math.max(1, Math.round(totalPrix / 25));
|
||||
const viewOrderDetails = (order: CompletedOrder) => {
|
||||
if (!isUserAuthenticated()) { navigate('/login/client', { replace: true }); return; }
|
||||
navigate(`/user/commande/${order.client_order_number ?? order.id}`, {
|
||||
state: { commandId: order.id },
|
||||
});
|
||||
};
|
||||
|
||||
const viewOrderDetails = (orderId: number) => {
|
||||
// ✅ Vérifier l'auth avant de naviguer
|
||||
if (!isUserAuthenticated()) {
|
||||
console.log('❌ [viewOrderDetails] Non authentifié');
|
||||
navigate('/login/client', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(`/user/commande/${orderId}`);
|
||||
const formatDate = (dateStr: string) => {
|
||||
const d = new Date(dateStr);
|
||||
return d.toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
@@ -158,9 +112,7 @@ function ConsultationHistorique() {
|
||||
<Navbar />
|
||||
<div className="history-container">
|
||||
<div className="loading-container">
|
||||
<div className="loading-spinner">
|
||||
<div className="spinner"></div>
|
||||
</div>
|
||||
<div className="loading-spinner"><div className="spinner"></div></div>
|
||||
<p className="loading-text">Chargement de l'historique...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,155 +120,86 @@ function ConsultationHistorique() {
|
||||
);
|
||||
}
|
||||
|
||||
const poolNames = clientStats?.pool_names?.length ? clientStats.pool_names : appSettings.pool_names;
|
||||
const poolPoints = clientStats?.pool_points ?? [clientStats?.points ?? 0];
|
||||
const totalPoints = poolPoints.reduce((s, v) => s + (v || 0), 0);
|
||||
const penaltyCount = penalties?.total_penalty || clientStats?.penalties || 0;
|
||||
const poolIcons = [faCannabis, faPills, faFlask, faMortarPestle, faStar];
|
||||
const poolIconColors = ['#10b981', '#e879f9', '#fb923c', '#38bdf8', '#7c3aed'];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<div className="history-container">
|
||||
|
||||
<div className="history-header">
|
||||
<h1 className="history-title">Historique des commandes</h1>
|
||||
|
||||
<h1 className="history-title">Historique des commandes</h1>
|
||||
|
||||
{/* Stats grid */}
|
||||
<div className="stats-grid">
|
||||
|
||||
{/* Total commandes */}
|
||||
<div className="stat-card2">
|
||||
<div className="stat-icon icon-total-orders">
|
||||
<FontAwesomeIcon icon={faReceipt} />
|
||||
</div>
|
||||
<p className="stat-value">{clientStats?.total_commands ?? orders.length}</p>
|
||||
<p className="stat-label">Commandes</p>
|
||||
</div>
|
||||
|
||||
{/* Points */}
|
||||
{appSettings.points_enabled && (
|
||||
poolNames.length <= 1 ? (
|
||||
<div className="stat-card2">
|
||||
<div className="stat-icon icon-total">
|
||||
<FontAwesomeIcon icon={faTrophy} />
|
||||
</div>
|
||||
<p className="stat-value">{poolPoints[0] || 0}</p>
|
||||
<p className="stat-label">Pts {poolNames[0] ?? 'Points'}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{poolNames.map((name, i) => (
|
||||
<div key={i} className="stat-card2">
|
||||
<div className="stat-icon" style={{ background: `linear-gradient(135deg, ${poolIconColors[i] ?? '#7c3aed'}cc, ${poolIconColors[i] ?? '#7c3aed'})` }}>
|
||||
<FontAwesomeIcon icon={poolIcons[i] ?? faStar} />
|
||||
</div>
|
||||
<p className="stat-value">{poolPoints[i] || 0}</p>
|
||||
<p className="stat-label">Pts {name}</p>
|
||||
</div>
|
||||
))}
|
||||
{totalPoints > 0 && (
|
||||
<div className="stat-card2">
|
||||
<div className="stat-icon icon-total">
|
||||
<FontAwesomeIcon icon={faTrophy} />
|
||||
</div>
|
||||
<p className="stat-value">{totalPoints}</p>
|
||||
<p className="stat-label">Total Points</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Score amendes */}
|
||||
{appSettings.show_amende_score && (
|
||||
<div className={`stat-card2${penaltyCount >= 3 ? ' stat-card-danger' : penaltyCount > 0 ? ' stat-card-warning' : ''}`}>
|
||||
<div className={`stat-icon ${penaltyCount >= 3 ? 'icon-penalty-critical' : penaltyCount > 0 ? 'icon-penalty-warning' : 'icon-penalty-ok'}`}>
|
||||
<FontAwesomeIcon icon={penaltyCount > 0 ? faExclamationTriangle : faShieldAlt} />
|
||||
</div>
|
||||
<p className={`stat-value ${penaltyCount >= 3 ? 'value-danger' : penaltyCount > 0 ? 'value-warning' : ''}`}>{penaltyCount}</p>
|
||||
<p className="stat-label">Score amendes</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ✅ STATISTIQUES CLIENT - 6 CARTES AVEC ICÔNES FONT AWESOME */}
|
||||
{clientStats && (
|
||||
<div className="stats-grid">
|
||||
{/* Carte 1: Total Commandes */}
|
||||
<div className="stat-card2 total-orders">
|
||||
<div className="stat-icon icon-total-orders">
|
||||
<Package size={24} />
|
||||
</div>
|
||||
<div className="stat-content">
|
||||
<p className="stat-label">Total commandes</p>
|
||||
<p className="stat-value">{clientStats.total_commands}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cartes points - affichées uniquement si le système de points est activé */}
|
||||
{appSettings.points_enabled && (() => {
|
||||
const poolNames = clientStats.pool_names?.length ? clientStats.pool_names : appSettings.pool_names;
|
||||
const poolPoints = clientStats.pool_points ?? [clientStats.points];
|
||||
const poolIcons = [faCannabis, faPills, faFlask, faMortarPestle, faStar];
|
||||
const poolClasses = poolNames.map((_, i) => `points-pool-${i}`);
|
||||
const poolIconClasses = poolNames.map((_, i) => `icon-pool-${i}`);
|
||||
|
||||
if (poolNames.length <= 1) {
|
||||
return (
|
||||
<div className="stat-card2 points-total">
|
||||
<div className="stat-icon icon-total">
|
||||
<FontAwesomeIcon icon={faTrophy} size="lg" />
|
||||
</div>
|
||||
<div className="stat-content">
|
||||
<p className="stat-label">
|
||||
<FontAwesomeIcon icon={faTrophy} style={{ marginRight: '0.5rem' }} />
|
||||
Points {poolNames[0] ?? 'Points'}
|
||||
</p>
|
||||
<p className="stat-value">{poolPoints[0] || 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const total = poolPoints.reduce((s, v) => s + (v || 0), 0);
|
||||
return (
|
||||
<>
|
||||
{poolNames.map((name, i) => (
|
||||
<div key={i} className={`stat-card2 ${poolClasses[i] ?? 'points-extra'}`}>
|
||||
<div className={`stat-icon ${poolIconClasses[i] ?? 'icon-total'}`}>
|
||||
<FontAwesomeIcon icon={poolIcons[i] ?? faTrophy} size="lg" />
|
||||
</div>
|
||||
<div className="stat-content">
|
||||
<p className="stat-label">
|
||||
<FontAwesomeIcon icon={poolIcons[i] ?? faTrophy} style={{ marginRight: '0.5rem' }} />
|
||||
Points {name}
|
||||
</p>
|
||||
<p className="stat-value">{poolPoints[i] || 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{total > 0 && poolNames.length > 1 && (
|
||||
<div className="stat-card2 points-total">
|
||||
<div className="stat-icon icon-total">
|
||||
<FontAwesomeIcon icon={faTrophy} size="lg" />
|
||||
</div>
|
||||
<div className="stat-content">
|
||||
<p className="stat-label">
|
||||
<FontAwesomeIcon icon={faTrophy} style={{ marginRight: '0.5rem' }} />
|
||||
Total Points
|
||||
</p>
|
||||
<p className="stat-value">{total}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Carte 5: Commandes Livrées */}
|
||||
<div className="stat-card2 completed-orders">
|
||||
<div className="stat-icon icon-completed-orders">
|
||||
<TrendingUp size={24} />
|
||||
</div>
|
||||
<div className="stat-content">
|
||||
<p className="stat-label">Commandes livrées</p>
|
||||
<p className="stat-value">{orders.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Carte pénalités - affichée uniquement si le score amendes est activé */}
|
||||
{appSettings.show_amende_score && penalties && (
|
||||
<div className={`stat-card2 penalty-stat ${penalties.total_penalty > 0 ? 'has-penalty' : ''}`}>
|
||||
<div
|
||||
className={`stat-icon ${
|
||||
penalties.total_penalty >= 100
|
||||
? 'penalty-critical'
|
||||
: penalties.total_penalty > 0
|
||||
? 'penalty-warning'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} size="lg" />
|
||||
</div>
|
||||
|
||||
<div className="stat-content">
|
||||
<p className="stat-label">Points de pénalité</p>
|
||||
|
||||
<p className="stat-value penalty-value">
|
||||
{penalties.total_penalty}
|
||||
</p>
|
||||
|
||||
{penalties.total_penalty > 0 && (
|
||||
<p className="penalty-warning-text">
|
||||
{penalties.total_penalty >= 100
|
||||
? 'Commandes bloquées'
|
||||
: `${penalties.cancellations_count} annulation${
|
||||
penalties.cancellations_count > 1 ? 's' : ''
|
||||
}`
|
||||
}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Carte parrainage - affichée si le parrainage est activé */}
|
||||
{appSettings.referral_enabled && (
|
||||
<div
|
||||
className="stat-card2 referral-stat"
|
||||
onClick={() => navigate('/user/parrainage')}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<div className="stat-icon icon-referral">
|
||||
<FontAwesomeIcon icon={faGift} size="lg" />
|
||||
</div>
|
||||
<div className="stat-content">
|
||||
<p className="stat-label">Solde parrainage</p>
|
||||
<p className={`stat-value ${referralBalance > 0 ? 'referral-value-active' : ''}`}>
|
||||
{referralBalance.toFixed(2)} €
|
||||
</p>
|
||||
<p className="referral-link-hint">Voir le programme →</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Bouton parrainage */}
|
||||
{appSettings.referral_enabled && (
|
||||
<div className="referral-btn-row" onClick={() => navigate('/user/parrainage')}>
|
||||
<FontAwesomeIcon icon={faGift} className="referral-btn-icon" />
|
||||
<span className="referral-btn-text">
|
||||
Parrainage{referralBalance > 0 ? ` — ${referralBalance.toFixed(2)} €` : ''}
|
||||
</span>
|
||||
<FontAwesomeIcon icon={faChevronRight} className="referral-btn-chevron" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -329,107 +212,67 @@ function ConsultationHistorique() {
|
||||
|
||||
{orders.length === 0 ? (
|
||||
<div className="empty-history">
|
||||
<Package className="empty-icon" size={64} />
|
||||
<h2>Aucune commande terminée</h2>
|
||||
<button
|
||||
className="browse-button"
|
||||
onClick={() => navigate('/user/accueil')}
|
||||
>
|
||||
<FontAwesomeIcon icon={faHistory} className="empty-icon" />
|
||||
<h2>Aucun historique</h2>
|
||||
<p>Vos commandes terminées apparaîtront ici</p>
|
||||
<button className="browse-button" onClick={() => navigate('/user/accueil')}>
|
||||
Découvrir nos produits
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="orders-summary">
|
||||
<p>{orders.length} commande{orders.length > 1 ? 's' : ''} terminée{orders.length > 1 ? 's' : ''}</p>
|
||||
</div>
|
||||
<p className="section-title">Historique des commandes</p>
|
||||
<div className="orders-list">
|
||||
{orders.map((order) => (
|
||||
<div
|
||||
key={order.id}
|
||||
className="order-card"
|
||||
onClick={() => viewOrderDetails(order)}
|
||||
>
|
||||
<div className="order-card-header">
|
||||
<span className="order-card-number">
|
||||
Commande #{(order.client_order_number ?? 0).toString().padStart(4, '0')}
|
||||
</span>
|
||||
<span className="order-card-badge">
|
||||
<FontAwesomeIcon icon={faCheckCircle} style={{ marginRight: '0.35rem' }} />
|
||||
Livrée
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="table-wrapper">
|
||||
<table className="history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>N° Commande</th>
|
||||
<th>Date</th>
|
||||
<th>Adresse</th>
|
||||
<th>Livreur</th>
|
||||
<th>Produits</th>
|
||||
<th>Total</th>
|
||||
<th>Statut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{orders.map((order) => (
|
||||
<tr
|
||||
key={order.id}
|
||||
onClick={() => viewOrderDetails(order.id)}
|
||||
className="clickable-row"
|
||||
>
|
||||
<td className="order-id">
|
||||
#{(order.client_order_number ?? 0).toString().padStart(4, '0')}
|
||||
</td>
|
||||
<td>
|
||||
<div className="date-cell">
|
||||
<span className="date-main">
|
||||
{new Date(order.created_at).toLocaleDateString('fr-FR', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
})}
|
||||
</span>
|
||||
<span className="date-time">
|
||||
{new Date(order.created_at).toLocaleTimeString('fr-FR', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</span>
|
||||
<span className="date-age">{getOrderAge(order.created_at)}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="address-cell">
|
||||
<MapPin size={14} className="address-icon" />
|
||||
<span className="address-text">
|
||||
{order.adresse.length > 40
|
||||
? order.adresse.substring(0, 40) + '...'
|
||||
: order.adresse
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="livreur-cell">
|
||||
{order.livreur_assign ? (
|
||||
<>
|
||||
<User size={14} className="livreur-icon" />
|
||||
<span>{order.livreur_assign}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="no-livreur">Non assigné</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="products-count">
|
||||
~{getProductCount(order.total_prix)} produit{getProductCount(order.total_prix) > 1 ? 's' : ''}
|
||||
</td>
|
||||
<td className="order-total2">
|
||||
{formatPrice(order.total_prix)}
|
||||
</td>
|
||||
<td>
|
||||
<span className="status-badge delivered">
|
||||
<FontAwesomeIcon icon={faCheckCircle} style={{ marginRight: '0.5rem' }} />
|
||||
Livrée
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="order-card-row">
|
||||
<FontAwesomeIcon icon={faMapMarkerAlt} className="order-card-row-icon" />
|
||||
<span className="order-card-row-text">
|
||||
{order.adresse ? (order.adresse.length > 50 ? order.adresse.substring(0, 50) + '…' : order.adresse) : 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="order-card-row">
|
||||
<FontAwesomeIcon icon={faClock} className="order-card-row-icon" />
|
||||
<span className="order-card-row-text">
|
||||
{formatDate(order.created_at)} · {getOrderAge(order.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{order.livreur_assign && (
|
||||
<div className="order-card-row">
|
||||
<FontAwesomeIcon icon={faBicycle} className="order-card-row-icon" />
|
||||
<span className="order-card-row-text">{order.livreur_assign}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="order-card-footer">
|
||||
<span className="order-card-total">{formatPrice(order.total_prix || 0)}</span>
|
||||
<FontAwesomeIcon icon={faChevronRight} className="order-card-chevron" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConsultationHistorique;
|
||||
export default ConsultationHistorique;
|
||||
|
||||
Reference in New Issue
Block a user