Profile menu oluşturuldu. Bağlantı ve Page'ler eklendi.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -71,3 +71,6 @@ movie/movieData/**/backdrop.jpg
|
|||||||
/key.pem
|
/key.pem
|
||||||
/cert.pem
|
/cert.pem
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# Client
|
||||||
|
client/src/assets/avatar.png
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
42
client/src/routes/Profile.svelte
Normal file
42
client/src/routes/Profile.svelte
Normal 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>
|
||||||
40
client/src/routes/Settings.svelte
Normal file
40
client/src/routes/Settings.svelte
Normal 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>
|
||||||
Reference in New Issue
Block a user