first commit
This commit is contained in:
142
app/dashboard/users/page.tsx
Normal file
142
app/dashboard/users/page.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
domains: string[];
|
||||
}
|
||||
|
||||
export default function UsersPage() {
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/users")
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
setUsers(Array.isArray(data) ? data : []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const filtered = users.filter(
|
||||
(u) =>
|
||||
u.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
u.email.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="page-header">
|
||||
<div>
|
||||
<h1 className="page-title">Kullanıcılar</h1>
|
||||
<p className="page-subtitle">Panel kullanıcıları .env dosyasından yönetilir</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="page-body">
|
||||
{/* Info card */}
|
||||
<div className="card" style={{ display: "flex", gap: 14, alignItems: "flex-start", border: "1px solid var(--accent-dim)", background: "var(--accent-dim)" }}>
|
||||
<div style={{ color: "var(--accent-hover)", flexShrink: 0, paddingTop: 2 }}>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, color: "var(--accent-hover)", marginBottom: 6 }}>Kullanıcı yönetimi hakkında</div>
|
||||
<div style={{ fontSize: 13, color: "var(--text-secondary)", lineHeight: 1.7 }}>
|
||||
Kullanıcılar <code style={{ background: "var(--bg)", padding: "1px 6px", borderRadius: 4, fontSize: 12 }}>.env</code> dosyasındaki{" "}
|
||||
<code style={{ background: "var(--bg)", padding: "1px 6px", borderRadius: 4, fontSize: 12 }}>USER_0_*</code>,{" "}
|
||||
<code style={{ background: "var(--bg)", padding: "1px 6px", borderRadius: 4, fontSize: 12 }}>USER_1_*</code>… değişkenleriyle tanımlanır.
|
||||
Yeni kullanıcı eklemek için .env dosyasını düzenleyip uygulamayı yeniden başlatın.
|
||||
</div>
|
||||
<div style={{ marginTop: 12, padding: "10px 14px", background: "var(--bg)", borderRadius: "var(--radius)", fontSize: 12, fontFamily: "monospace", color: "var(--text-secondary)", lineHeight: 2 }}>
|
||||
USER_2_NAME="Ahmet Yılmaz"<br />
|
||||
USER_2_EMAIL="ahmet@ayristech.com"<br />
|
||||
USER_2_PASSWORD="güçlü-şifre"<br />
|
||||
USER_2_ROLE="DOMAIN_ADMIN"<br />
|
||||
USER_2_DOMAINS="yenidomain.com"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="search-bar">
|
||||
<div className="search-input-wrap">
|
||||
<span className="search-icon"><SearchIcon /></span>
|
||||
<input
|
||||
type="text"
|
||||
className="input search-input"
|
||||
placeholder="İsim veya e-posta ara..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="table-wrap">
|
||||
{loading ? (
|
||||
<div className="empty-state">
|
||||
<span className="spinner" style={{ width: 24, height: 24 }} />
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<div className="empty-icon"><UsersIcon /></div>
|
||||
<div style={{ fontWeight: 600 }}>Kullanıcı bulunamadı</div>
|
||||
</div>
|
||||
) : (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kullanıcı</th>
|
||||
<th>Rol</th>
|
||||
<th>İzin Verilen Domainler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map((u) => (
|
||||
<tr key={u.id}>
|
||||
<td>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||||
<div className="user-avatar" style={{ width: 32, height: 32, fontSize: 13 }}>
|
||||
{u.name[0]?.toUpperCase()}
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 500 }}>{u.name}</div>
|
||||
<div style={{ fontSize: 11, color: "var(--text-secondary)" }}>{u.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge ${u.role === "SUPER_ADMIN" ? "badge-blue" : "badge-green"}`}>
|
||||
{u.role === "SUPER_ADMIN" ? "★ Süper Admin" : "Domain Admin"}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{u.domains.includes("*") ? (
|
||||
<span className="badge badge-blue">Tüm domainler</span>
|
||||
) : (
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
|
||||
{u.domains.map((d) => (
|
||||
<span key={d} className="badge badge-green">{d}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchIcon() { return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>; }
|
||||
function UsersIcon() { return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>; }
|
||||
function InfoIcon() { return <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>; }
|
||||
Reference in New Issue
Block a user