Profile menu oluşturuldu. Bağlantı ve Page'ler eklendi.

This commit is contained in:
2025-12-03 20:36:24 +03:00
parent 642aeffcd7
commit 0759be39b4
6 changed files with 249 additions and 3 deletions

3
.gitignore vendored
View File

@@ -71,3 +71,6 @@ movie/movieData/**/backdrop.jpg
/key.pem /key.pem
/cert.pem /cert.pem
*.log *.log
# Client
client/src/assets/avatar.png

View File

@@ -9,6 +9,8 @@
import Movies from "./routes/Movies.svelte"; import Movies from "./routes/Movies.svelte";
import TvShows from "./routes/TvShows.svelte"; import TvShows from "./routes/TvShows.svelte";
import Music from "./routes/Music.svelte"; import Music from "./routes/Music.svelte";
import Profile from "./routes/Profile.svelte";
import Settings from "./routes/Settings.svelte";
import Login from "./routes/Login.svelte"; import Login from "./routes/Login.svelte";
import { API, getAccessToken } from "./utils/api.js"; import { API, getAccessToken } from "./utils/api.js";
import { refreshMovieCount } from "./stores/movieStore.js"; import { refreshMovieCount } from "./stores/movieStore.js";
@@ -118,6 +120,8 @@
<Route path="/movies" component={Movies} /> <Route path="/movies" component={Movies} />
<Route path="/tv" component={TvShows} /> <Route path="/tv" component={TvShows} />
<Route path="/music" component={Music} /> <Route path="/music" component={Music} />
<Route path="/profile" component={Profile} />
<Route path="/settings" component={Settings} />
<Route path="/transfers" component={Transfers} /> <Route path="/transfers" component={Transfers} />
<Route path="/trash" component={Trash} /> <Route path="/trash" component={Trash} />
</div> </div>

View File

@@ -1,13 +1,21 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher, onMount, onDestroy } from "svelte";
import { navigate } from "svelte-routing";
import { import {
activePlaceholder, activePlaceholder,
activeSearchTerm, activeSearchTerm,
updateSearchTerm updateSearchTerm
} from "../stores/searchStore.js"; } from "../stores/searchStore.js";
import avatarSrc from "../assets/avatar.png";
import { clearTokens } from "../utils/api.js";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let placeholder = ""; export let placeholder = "";
// Örnek avatar (yerel dosya)
export let avatarUrl = avatarSrc;
let showAvatarMenu = false;
let avatarWrap;
const onToggle = () => dispatch("toggleMenu"); const onToggle = () => dispatch("toggleMenu");
@@ -15,6 +23,41 @@
updateSearchTerm(event.target.value); updateSearchTerm(event.target.value);
} }
function toggleAvatarMenu() {
showAvatarMenu = !showAvatarMenu;
}
function goProfile() {
showAvatarMenu = false;
navigate("/profile");
}
function goSettings() {
showAvatarMenu = false;
navigate("/settings");
}
function logout() {
clearTokens();
showAvatarMenu = false;
window.location.replace("/login");
}
function handleDocumentClick(event) {
if (!showAvatarMenu) return;
if (avatarWrap && !avatarWrap.contains(event.target)) {
showAvatarMenu = false;
}
}
onMount(() => {
document.addEventListener("click", handleDocumentClick);
});
onDestroy(() => {
document.removeEventListener("click", handleDocumentClick);
});
$: resolvedPlaceholder = placeholder || $activePlaceholder; $: resolvedPlaceholder = placeholder || $activePlaceholder;
</script> </script>
@@ -37,6 +80,33 @@
on:input={handleInput} on:input={handleInput}
/> />
</div> </div>
<div class="avatar-wrap" bind:this={avatarWrap}>
<button class="avatar" type="button" aria-label="Profil" on:click={toggleAvatarMenu}>
{#if avatarUrl}
<img src={avatarUrl} alt="Avatar" loading="lazy" />
{:else}
<i class="fa-solid fa-user"></i>
{/if}
</button>
{#if showAvatarMenu}
<div class="avatar-menu">
<button type="button" on:click={goProfile}>
<i class="fa-solid fa-user"></i>
Profile
</button>
<button type="button" on:click={goSettings}>
<i class="fa-solid fa-gear"></i>
Settings
</button>
<button type="button" class="logout" on:click={logout}>
<i class="fa-solid fa-right-from-bracket"></i>
Logout
</button>
</div>
{/if}
</div>
</div> </div>
<style> <style>
@@ -85,4 +155,81 @@
height: 36px; height: 36px;
} }
} }
.avatar {
border: none;
background: #f2f2f2;
border-radius: 10px;
width: 42px;
height: 42px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
overflow: hidden;
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
.avatar i {
color: #666;
font-size: 18px;
}
@media (max-width: 768px) {
.avatar {
width: 38px;
height: 38px;
}
}
.avatar-wrap {
position: relative;
}
.avatar-menu {
position: absolute;
right: 0;
top: calc(100% + 8px);
background: #fff;
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
display: flex;
flex-direction: column;
min-width: 170px;
overflow: hidden;
z-index: 20;
}
.avatar-menu button {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
background: #fff;
border: none;
text-align: left;
cursor: pointer;
color: #1c2440;
}
.avatar-menu button:hover {
background: #f3f4f7;
}
.avatar-menu i {
width: 18px;
text-align: center;
}
.avatar-menu .logout {
color: #c0392b;
}
</style> </style>

View File

@@ -1,11 +1,20 @@
<script> <script>
import { API, persistTokens, clearTokens } from "../utils/api.js"; import { onMount } from "svelte";
import { navigate } from "svelte-routing";
import { API, persistTokens, clearTokens, getAccessToken } from "../utils/api.js";
import logo from "../assets/image/logo.png"; import logo from "../assets/image/logo.png";
let username = ""; let username = "";
let password = ""; let password = "";
let error = ""; let error = "";
onMount(() => {
const token = getAccessToken();
if (token) {
navigate("/", { replace: true });
}
});
async function login() { async function login() {
const res = await fetch(`${API}/api/login`, { const res = await fetch(`${API}/api/login`, {
method: "POST", method: "POST",
@@ -18,7 +27,8 @@
persistTokens({ accessToken, refreshToken }); persistTokens({ accessToken, refreshToken });
if (accessToken) localStorage.setItem("token", accessToken); // Geçiş dönemi uyumluluğu if (accessToken) localStorage.setItem("token", accessToken); // Geçiş dönemi uyumluluğu
if (user) localStorage.setItem("user", JSON.stringify(user)); if (user) localStorage.setItem("user", JSON.stringify(user));
window.location.reload(); // Router state beklemeden anında yönlendir
navigate("/", { replace: true });
} else { } else {
error = "Kullanıcı adı veya şifre hatalı."; error = "Kullanıcı adı veya şifre hatalı.";
clearTokens(); clearTokens();

View File

@@ -0,0 +1,42 @@
<script>
// Tasarım, diğer sayfalardaki yapıyı korur; şimdilik boş içerik.
</script>
<section class="files">
<div class="files-header">
<div class="header-title">
<h2>Profile</h2>
</div>
</div>
<div class="empty">
Profil içeriği yakında.
</div>
</section>
<style>
.files {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.files-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-title {
display: flex;
align-items: center;
gap: 8px;
}
.empty {
padding: 24px;
border: 1px dashed var(--border, #dcdcdc);
border-radius: 10px;
color: #666;
}
</style>

View File

@@ -0,0 +1,40 @@
<script>
// Tasarım diğer sayfalarla aynı iskelette; içerik placeholder.
</script>
<section class="files">
<div class="files-header">
<div class="header-title">
<h2>Settings</h2>
</div>
</div>
<div class="empty">Ayarlar içeriği yakında.</div>
</section>
<style>
.files {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.files-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-title {
display: flex;
align-items: center;
gap: 8px;
}
.empty {
padding: 24px;
border: 1px dashed var(--border, #dcdcdc);
border-radius: 10px;
color: #666;
}
</style>