+
+
+
+ {comments}
+
+
+
+
diff --git a/src/global.css b/src/global.css
index 7a46428..6efe88c 100644
--- a/src/global.css
+++ b/src/global.css
@@ -2,18 +2,72 @@
@import "tailwindcss";
+/* -------------------------------------------------
+ THEME TOKENS (Tailwind v4 @theme değişkenleri)
+ Default: LIGHT
+ Dark: .dark class'ı ile override
+-------------------------------------------------- */
+
@theme {
- --color-background: #121212;
- --color-surface: #1E1E1E;
- --color-primary: #FF6B00;
- --color-primary-hover: #E65A00;
- --color-accent: #FFD166;
- --color-text: #FFFFFF;
- --color-text-muted: #B3B3B3;
- --color-success: #00C851;
- --color-danger: #FF4444;
+ /* LIGHT (soft graphite) */
+ --color-background: #D6DAE1; /* sayfa: açık gri ama beyaz değil */
+ --color-surface: #E1E5EB; /* kart */
+ --color-surface-2: #CBD1DA; /* input/secondary */
+ --color-border: #B3BBC7; /* border */
+
+ --color-text: #1C212B; /* koyu gri */
+ --color-text-muted: #5D6675;
+
+ --color-primary: #FF6A00;
+ --color-primary-hover: #E85F00;
+ --color-primary-soft: rgba(255, 106, 0, 0.14);
+ --color-primary-ring: rgba(255, 106, 0, 0.30);
+
+ --color-success: #16A34A;
+ --color-danger: #EF4444;
+
+ --color-on-primary: #111318;
+}
+
+/* Dark overrides (class tabanlı) */
+.dark {
+ /* DARK (seninki iyiydi; hafif rafine) */
+ --color-background: #0F0F10;
+ --color-surface: #17181A;
+ --color-surface-2: #1F2124;
+ --color-border: #2A2D31;
+
+ --color-text: #F2F3F5;
+ --color-text-muted: #A7ABB3;
+
+ --color-primary: #FF6A00;
+ --color-primary-hover: #E85F00;
+ --color-primary-soft: rgba(255, 106, 0, 0.16);
+ --color-primary-ring: rgba(255, 106, 0, 0.35);
+
+ --color-success: #2ECC71;
+ --color-danger: #FF4D4D;
+
+ --color-on-primary: #111214;
+}
+/* Base */
+:root {
+ color-scheme: light;
+}
+
+.dark {
+ color-scheme: dark;
}
body {
- @apply bg-background text-text font-sans;
+ font-family: "Rubik", ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
+ @apply bg-background text-text;
+}
+
+a {
+ text-decoration: none;
+}
+
+a:hover {
+ color: var(--color-primary);
}
\ No newline at end of file
diff --git a/src/hooks/useHideOnScroll.ts b/src/hooks/useHideOnScroll.ts
new file mode 100644
index 0000000..362db4d
--- /dev/null
+++ b/src/hooks/useHideOnScroll.ts
@@ -0,0 +1,73 @@
+import { useEffect, useRef, useState } from "react"
+
+type Options = {
+ topThreshold?: number // en üstte her zaman açık
+ hideAfterDownPx?: number // aşağı inerken gizlemek için gereken px
+ revealAfterUpPx?: number // yukarı çıkarken göstermek için gereken px (asıl istediğin)
+}
+
+export function useHideOnScroll({
+ topThreshold = 12,
+ hideAfterDownPx = 12,
+ revealAfterUpPx = 80, // bunu büyüttükçe daha geç gelir (örn 60-120 iyi)
+}: Options = {}) {
+ const [visible, setVisible] = useState(true)
+
+ const lastY = useRef(0)
+ const upAccum = useRef(0)
+ const downAccum = useRef(0)
+ const ticking = useRef(false)
+
+ useEffect(() => {
+ lastY.current = window.scrollY || 0
+
+ const onScroll = () => {
+ const y = window.scrollY || 0
+ if (ticking.current) return
+ ticking.current = true
+
+ requestAnimationFrame(() => {
+ const prev = lastY.current
+ const diff = y - prev // + down, - up
+
+ // top: her zaman göster
+ if (y <= topThreshold) {
+ setVisible(true)
+ upAccum.current = 0
+ downAccum.current = 0
+ lastY.current = y
+ ticking.current = false
+ return
+ }
+
+ if (diff > 0) {
+ // scrolling down
+ downAccum.current += diff
+ upAccum.current = 0
+
+ if (downAccum.current >= hideAfterDownPx) {
+ setVisible(false)
+ downAccum.current = 0
+ }
+ } else if (diff < 0) {
+ // scrolling up
+ upAccum.current += Math.abs(diff)
+ downAccum.current = 0
+
+ if (upAccum.current >= revealAfterUpPx) {
+ setVisible(true)
+ upAccum.current = 0
+ }
+ }
+
+ lastY.current = y
+ ticking.current = false
+ })
+ }
+
+ window.addEventListener("scroll", onScroll, { passive: true })
+ return () => window.removeEventListener("scroll", onScroll)
+ }, [topThreshold, hideAfterDownPx, revealAfterUpPx])
+
+ return visible
+}
diff --git a/src/layouts/MainLayout.tsx b/src/layouts/MainLayout.tsx
index 055415f..4714d71 100644
--- a/src/layouts/MainLayout.tsx
+++ b/src/layouts/MainLayout.tsx
@@ -1,35 +1,29 @@
-import Navbar from "../components/Layout/Navbar/Navbar";
-import Footer from "../components/Layout/Footer";
+import Navbar from "../components/Layout/Navbar/Navbar"
+import Footer from "../components/Layout/Footer"
type Props = {
- children: React.ReactNode;
-};
+ children: React.ReactNode
+}
export default function MainLayout({ children }: Props) {
return (
+ {/* Navbar fixed + full width */}
+
- {/* NAVBAR - tam genişlikte arka plan, ortalı içerik */}
-
+ {/* Content: navbar yüksekliği kadar boşluk */}
+
-
-
-
-
- {/* ANA İÇERİK */}
-
-
{children}
- {/* FOOTER - tam genişlikte arka plan, ortalı içerik */}
-
- );
+ )
}
diff --git a/src/models/Deal.ts b/src/models/Deal.ts
deleted file mode 100644
index 94d2e69..0000000
--- a/src/models/Deal.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import type { DealImage } from "./deal/DealImage.ts"
-
-import type { DealVote } from "./deal/DealVote.ts"
-import type { User } from "./User"
-import type { Seller } from "./seller/Seller.ts"
-
-export type Deal = {
- id: number
- title: string
- description?: string
- url?: string
- price?: number
-
- score: number
- status: "PENDING" | "ACTIVE" | "EXPIRED" | "REJECTED"
- saleType: "ONLINE" | "OFFLINE" | "CODE"
- affiliateType: "AFFILIATE" | "NON_AFFILIATE" | "USER_AFFILIATE"
-
-
- sellerName: string
-
- createdAt: string
- updatedAt: string
-
- // ilişkiler
- user?: Pick
- company?: Pick
- images?: DealImage[]
- votes?: DealVote[]
- comments?: Comment[]
-}
diff --git a/src/models/DealDraft.ts b/src/models/DealDraft.ts
index a19a81f..75446b7 100644
--- a/src/models/DealDraft.ts
+++ b/src/models/DealDraft.ts
@@ -6,7 +6,7 @@ export type DealDraft = {
url: string
price?: number
imageUrl: string
-
+ images?: File[] // max 5
seller: Seller
customCompany?: string
diff --git a/src/models/comment/Comment.ts b/src/models/comment/Comment.ts
new file mode 100644
index 0000000..4e39d83
--- /dev/null
+++ b/src/models/comment/Comment.ts
@@ -0,0 +1,9 @@
+import type { PublicUserSummary } from "../user/User"
+import type { DealCard } from "../deal/DealCard"
+export type Comment = {
+ id: number
+ text: string
+ createdAt: string
+ user:PublicUserSummary
+ deal:DealCard
+}
\ No newline at end of file
diff --git a/src/models/comment/UserComment.ts b/src/models/comment/UserComment.ts
new file mode 100644
index 0000000..c5f4e68
--- /dev/null
+++ b/src/models/comment/UserComment.ts
@@ -0,0 +1,9 @@
+// src/models/comment/UserComment.ts
+import type { Comment } from "./Comment"
+
+export type UserComment = Comment & {
+ deal: {
+ id: number
+ title: string
+ }
+}
diff --git a/src/models/deal/DealCard.ts b/src/models/deal/DealCard.ts
index 4912b64..04d0737 100644
--- a/src/models/deal/DealCard.ts
+++ b/src/models/deal/DealCard.ts
@@ -1,5 +1,5 @@
-import type { PublicUserSummary } from "..//User"
+import type { PublicUserSummary } from "../user/User"
import type { SellerSummary } from "..//seller/Seller"
export type DealCard = {
@@ -12,6 +12,8 @@ export type DealCard = {
score: number
commentsCount: number
+ myVote:-1 | 0 | 1
+
status: "PENDING" | "ACTIVE" | "EXPIRED" | "REJECTED"
saleType: "ONLINE" | "OFFLINE" | "CODE"
affiliateType: "AFFILIATE" | "NON_AFFILIATE" | "USER_AFFILIATE"
diff --git a/src/models/deal/DealComment.ts b/src/models/deal/DealComment.ts
deleted file mode 100644
index 4d968e7..0000000
--- a/src/models/deal/DealComment.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { PublicUserSummary } from "../User"
-
-export type DealComment = {
- id: number
- text: string
- createdAt: string
- user:PublicUserSummary
-}
\ No newline at end of file
diff --git a/src/models/deal/DealDetail.ts b/src/models/deal/DealDetail.ts
index e577749..7ccd6b7 100644
--- a/src/models/deal/DealDetail.ts
+++ b/src/models/deal/DealDetail.ts
@@ -1,8 +1,8 @@
-import type { PublicUserSummary } from "..//User"
+import type { PublicUserSummary } from "../user/User"
import type { SellerSummary } from "..//seller/Seller"
import type { DealImage } from "./DealImage"
-import type { DealComment } from "./DealComment"
+import type { Comment } from "../comment/Comment"
export type DealDetail = {
id: number
title: string
@@ -23,5 +23,5 @@ export type DealDetail = {
user: PublicUserSummary
seller: SellerSummary
images: DealImage[]
- comments: DealComment[]
+ comments: Comment[]
}
diff --git a/src/models/deal/DealImage.ts b/src/models/deal/DealImage.ts
index 32fd1f1..4b67a7e 100644
--- a/src/models/deal/DealImage.ts
+++ b/src/models/deal/DealImage.ts
@@ -1,4 +1,4 @@
export type DealImage = {
- url: string
+ imageUrl: string
order: number
}
diff --git a/src/models/deal/DealVote.ts b/src/models/deal/DealVote.ts
index 3c8043d..de04d0f 100644
--- a/src/models/deal/DealVote.ts
+++ b/src/models/deal/DealVote.ts
@@ -2,6 +2,6 @@ export type DealVote = {
id: number
dealId: number
userId: number
- voteType: string
+ voteType: number
createdAt: string
}
diff --git a/src/models/index.ts b/src/models/index.ts
index 51aa786..15e6423 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -1,5 +1,4 @@
-export * from "./User"
-export * from "./Deal"
+export * from "./user/User"
export * from "./deal/DealImage"
export * from "./deal/DealVote"
-export * from "./deal/DealComment"
+export * from "./comment/Comment"
diff --git a/src/models/User.ts b/src/models/user/User.ts
similarity index 67%
rename from src/models/User.ts
rename to src/models/user/User.ts
index 71f968f..6056f2b 100644
--- a/src/models/User.ts
+++ b/src/models/user/User.ts
@@ -7,4 +7,5 @@ export type User = {
createdAt: string
}
-export type PublicUserSummary = Pick
\ No newline at end of file
+export type PublicUserSummary = Pick
+export type PublicUserDetails = Pick
\ No newline at end of file
diff --git a/src/models/user/UserProfile.ts b/src/models/user/UserProfile.ts
new file mode 100644
index 0000000..97caf48
--- /dev/null
+++ b/src/models/user/UserProfile.ts
@@ -0,0 +1,10 @@
+import type { PublicUserDetails } from "./User"
+import type { DealCard } from "../deal/DealCard"
+import type { UserComment } from "../comment/UserComment"
+import type { UserStats } from "./userStats"
+export type UserProfile = {
+ user: PublicUserDetails
+ deals: DealCard[]
+ comments: UserComment[]
+ stats:UserStats
+}
\ No newline at end of file
diff --git a/src/models/user/userStats.ts b/src/models/user/userStats.ts
new file mode 100644
index 0000000..e725329
--- /dev/null
+++ b/src/models/user/userStats.ts
@@ -0,0 +1,5 @@
+export type UserStats = {
+ totalLikes: number
+ totalShares: number
+ totalComments: number
+}
\ No newline at end of file
diff --git a/src/pages/CreateDealPage.tsx b/src/pages/CreateDealPage.tsx
index db5d35d..583971f 100644
--- a/src/pages/CreateDealPage.tsx
+++ b/src/pages/CreateDealPage.tsx
@@ -4,13 +4,14 @@ import MainLayout from "../layouts/MainLayout"
import { createDeal } from "../api/deal/newDeal"
import { lookupSellerFromLink } from "../api/seller/from-lookup"
-import { mapSellerFromLookupRequest } from "../adapters/requests/sellerFromLookupAdapter"
+
import { mapSellerFromLookupResponse } from "../adapters/responses/sellerFromLookupAdapter"
import { mapDealDraftToCreateRequest } from "../adapters/requests/dealCreateAdapter.ts"
import DealLinkStep from "../components/CreateDeal/DealLinkStep"
import DealDetailsStep from "../components/CreateDeal/DealDetailsStep"
+import type { SellerLookupInput } from "../api/seller/types.ts"
import type { DealDraft } from "../models/DealDraft"
import type { Seller } from "../models/seller/Seller"
@@ -26,6 +27,7 @@ export default function CreateDealPage() {
url: "",
price: undefined,
imageUrl: "",
+ images: [], // <-- ekle
seller: {
id: -1,
name: "",
@@ -51,29 +53,18 @@ export default function CreateDealPage() {
try {
// 🔥 1. URL → Seller (temporary)
- const tempSeller: Seller = {
- id: -1,
- name: "",
- url: dealDraft.url,
- }
+
// 🔥 2. Seller → Lookup Request (ADAPTER)
- const lookupRequest =
- mapSellerFromLookupRequest(tempSeller)
-
+ const input: SellerLookupInput = { url: dealDraft.url }
// 🔥 3. API CALL
- const lookupResponse =
- await lookupSellerFromLink(lookupRequest)
-
- // 🔥 4. Response → Seller (ADAPTER)
- const seller =
- mapSellerFromLookupResponse(lookupResponse)
-
- setDealDraft(d => ({
- ...d,
- seller,
- }))
+ const seller = await lookupSellerFromLink(input)
+ setDealDraft(d => {
+ const next = { ...d, seller }
+ console.log("NEXT:", next)
+ return next
+ })
setStep("details")
} catch (err) {
console.error("Seller lookup failed:", err)
@@ -97,6 +88,7 @@ export default function CreateDealPage() {
const handleFinalSubmit = async () => {
try {
+
await createDeal(
mapDealDraftToCreateRequest(dealDraft)
)
@@ -115,6 +107,7 @@ export default function CreateDealPage() {
url: "",
price: undefined,
imageUrl: "",
+ images: [],
seller: {
id: -1,
name: "",
@@ -127,7 +120,8 @@ export default function CreateDealPage() {
return (
-
+
+
{step === "link" && (
{
if (!id) return
-
- const loadDeal = async () => {
+ ;(async () => {
try {
- const apiDeal = await getDealDetail(Number(id))
- const mapped = mapDealDetailResponseToDealDetail(apiDeal)
- setDeal(mapped)
+ const d = await getDealDetail(Number(id))
+ setDeal(d)
} catch (err) {
console.error("Deal yüklenemedi:", err)
}
- }
-
- loadDeal()
+ })()
}, [id])
- if (!deal) return Yükleniyor...
+ if (!deal) {
+ return (
+
+
+
+ )
+ }
+
+
return (
-
- {/* Sol: görseller */}
-
-
-
+
+ {/* üst ana grid */}
+
+ {/* SOL: Görseller */}
+
- {/* Sağ: temel bilgiler */}
-
-
-
+ {/* SAĞ: Detay kartı (sticky) */}
+
+
- {/* Alt: açıklama + yorumlar */}
-
-
+
+
+
+ {/* küçük yan bilgi alanı */}
+
+
+
+
+ {/* ALT: Açıklama + Yorumlar */}
+{/* ALT: açıklama + yorumlar (tam genişlik) */}
+
-
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
index 8c9318e..1f2c898 100644
--- a/src/pages/HomePage.tsx
+++ b/src/pages/HomePage.tsx
@@ -25,15 +25,15 @@ export default function HomePage({ onRequireLogin }: HomeProps) {
setLoading(true)
try {
- const apiDeals = await getDeals(page)
- if (apiDeals.length === 0) {
+ const deals = await getDeals(page)
+ if (deals.length === 0) {
setHasMore(false)
} else {
- const mappedDeals = apiDeals.map(mapDealCardResponseToDeal)
+
setDeals((prev) => {
const existingIds = new Set(prev.map((d) => d.id))
- const filtered = mappedDeals.filter(
+ const filtered = deals.filter(
(d) => !existingIds.has(d.id)
)
return [...prev, ...filtered]
@@ -84,8 +84,10 @@ export default function HomePage({ onRequireLogin }: HomeProps) {
score={deal.score}
comments={deal.commentsCount}
postedAgo={timeAgo(deal.createdAt)}
+ myVote={deal.myVote}
onRequireLogin={onRequireLogin}
/>
+
))}
{loading && (
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
index 5afe8f4..dbc5c6d 100644
--- a/src/pages/ProfilePage.tsx
+++ b/src/pages/ProfilePage.tsx
@@ -3,80 +3,89 @@ import { useParams } from "react-router-dom"
import MainLayout from "../layouts/MainLayout"
import DealCardMain from "../components/Shared/DealCardMain"
import CommentCard from "../components/Profile/CommentCard"
+import ProfileHeader from "../components/Profile/ProfileHeader"
import { fetchUserProfile } from "../services/userService"
-import type { Deal, Comment } from "../models"
-import type { PublicUserSummary } from "../models/User"
+import { timeAgo } from "../utils/timeAgo"
+import type { UserProfile } from "../models/user/UserProfile"
export default function ProfilePage() {
const { userName } = useParams<{ userName: string }>()
const [activeTab, setActiveTab] = useState<"deals" | "comments">("deals")
- const [user, setUser] = useState
(null)
- const [deals, setDeals] = useState([])
- const [comments, setComments] = useState([])
+ const [userProfile, setUserProfile] = useState(null)
+
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
if (!userName) return
+
const loadUser = async () => {
setLoading(true)
+ setError(null)
try {
- const { user, deals, comments } = await fetchUserProfile(userName)
- setUser(user)
- setDeals(deals)
- setComments(comments)
+ const profile = await fetchUserProfile(userName)
+ setUserProfile(profile)
} catch (err: any) {
console.error(err)
- setError(err.message)
+ setError(err.message || "Sunucu hatası")
} finally {
setLoading(false)
}
}
+
loadUser()
}, [userName])
- if (loading) return Yükleniyor...
- if (error) return {error}
- if (!user) return Kullanıcı bulunamadı.
+ if (loading) return Yükleniyor...
+ if (error) return {error}
+ if (!userProfile) return Kullanıcı bulunamadı.
+
return (
- {/* ÜST: profil bilgisi */}
-
-

-
{user.username}
-
- Katılma: {new Date(user.createdAt).toLocaleDateString("tr-TR")}
-
-
+ {/* ÜST: profil header */}
+
- {/* MENÜ */}
-
+ {/* TAB BAR */}
+
+
@@ -84,43 +93,47 @@ export default function ProfilePage() {
{activeTab === "deals" ? (
- {deals.length > 0 ? (
- deals.map((deal) => {
- const firstImage = deal.images?.[0]?.imageUrl || "/placeholder.png"
- const postedAgo = new Date(deal.createdAt).toLocaleDateString("tr-TR")
-
- return (
-
{}}
- />
- )
- })
- ) : (
-
- Henüz paylaşım yok.
-
- )}
-
+ {userProfile.deals?.length > 0 ? (
+ userProfile.deals.map((deal) => (
+ {}}
+ />
+ ))
+ ) : (
+
+
Henüz paylaşım yok
+
+ Bu kullanıcı daha fırsat paylaşmamış.
+
+
+ )}
) : (
- {comments.length > 0 ? (
- comments.map((c) =>
)
- ) : (
-
- Henüz yorum yok.
-
- )}
+ {userProfile.comments?.length > 0 ? (
+ userProfile.comments.map((c) => (
+
+ ))
+ ) : (
+
+
Henüz yorum yok
+
+ Bu kullanıcı daha yorum yapmamış.
+
+
+ )}
)}
diff --git a/src/pages/SearchPage.tsx b/src/pages/SearchPage.tsx
index 3875ef3..5b1072a 100644
--- a/src/pages/SearchPage.tsx
+++ b/src/pages/SearchPage.tsx
@@ -70,13 +70,13 @@ export default function SearchPage({ onRequireLogin }: { onRequireLogin: () => v
diff --git a/src/services/userService.ts b/src/services/userService.ts
index f2f4a72..b0070f2 100644
--- a/src/services/userService.ts
+++ b/src/services/userService.ts
@@ -1,41 +1,7 @@
import { getUser } from "../api/user/getUser"
-import type { PublicUserSummary, Deal, Comment } from "../models"
-
-export type UserProfile = {
- user: PublicUserSummary
- deals: Deal[]
- comments: Comment[]
-}
+import type { UserProfile } from "../models/user/UserProfile"
export async function fetchUserProfile(userName: string): Promise
{
const data = await getUser(userName)
-
- const user: PublicUserSummary = {
- username: data.user.username,
- avatarUrl: data.user.avatarUrl,
- createdAt: data.user.createdAt,
- }
-
- const deals: Deal[] = data.deals.map((d: any) => ({
- id: d.id,
- title: d.title,
- price: d.price,
- store: d.store,
- score: d.score,
- createdAt: d.createdAt,
- images:
- d.images?.map((img: any) => ({
- imageUrl: img.imageUrl,
- order: img.order,
- })) || [],
- }))
-
- const comments: Comment[] = data.comments.map((c: any) => ({
- id: c.id,
- text: c.text,
- createdAt: c.createdAt,
- deal: { title: c.deal?.title || "Bilinmeyen paylaşım" },
- }))
-
- return { user, deals, comments }
+ return data
}