first commit
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { JournalPost, MoodType, PostType } from "./types";
|
||||
import { MOODS } from "./mockData";
|
||||
import { X, Feather, Save, StickyNote, BookOpen, Code, Camera, Volume2 } from "lucide-react";
|
||||
|
||||
interface EditPostModalProps {
|
||||
post: JournalPost | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onUpdatePost: (updatedPost: JournalPost) => void;
|
||||
}
|
||||
|
||||
export const EditPostModal: React.FC<EditPostModalProps> = ({
|
||||
post,
|
||||
isOpen,
|
||||
onClose,
|
||||
onUpdatePost,
|
||||
}) => {
|
||||
const [type, setType] = useState<PostType>("sticky");
|
||||
const [mood, setMood] = useState<MoodType>("spark");
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [marginNote, setMarginNote] = useState("");
|
||||
const [tagInput, setTagInput] = useState("");
|
||||
const [codeSnippet, setCodeSnippet] = useState("");
|
||||
const [stampedText, setStampedText] = useState("");
|
||||
const [imageUrl, setImageUrl] = useState("");
|
||||
const [imageCaption, setImageCaption] = useState("");
|
||||
const [likes, setLikes] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (post) {
|
||||
setType(post.type);
|
||||
setMood(post.mood);
|
||||
setTitle(post.title || "");
|
||||
setContent(post.content);
|
||||
setMarginNote(post.marginNotes ? post.marginNotes.join(", ") : "");
|
||||
setTagInput(post.tags ? post.tags.join(", ") : "");
|
||||
setCodeSnippet(post.codeSnippet || "");
|
||||
setStampedText(post.stampedText || "");
|
||||
setImageUrl(post.imageUrl || "");
|
||||
setImageCaption(post.imageCaption || "");
|
||||
setLikes(post.likes || 0);
|
||||
}
|
||||
}, [post]);
|
||||
|
||||
if (!isOpen || !post) return null;
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!content.trim()) return;
|
||||
|
||||
const moodObj = MOODS.find((m) => m.id === mood) || MOODS[0];
|
||||
const tags = tagInput
|
||||
.split(",")
|
||||
.map((t) => t.trim().replace(/^#/, ""))
|
||||
.filter((t) => t.length > 0);
|
||||
|
||||
const updated: JournalPost = {
|
||||
...post,
|
||||
type,
|
||||
mood,
|
||||
moodLabel: moodObj.label,
|
||||
title: title.trim() ? title : undefined,
|
||||
content,
|
||||
marginNotes: marginNote.trim()
|
||||
? marginNote.split(",").map((n) => n.trim())
|
||||
: undefined,
|
||||
tags: tags.length > 0 ? tags : ["genel"],
|
||||
stampedText: stampedText.trim() ? stampedText.trim() : undefined,
|
||||
codeSnippet: type === "code" && codeSnippet.trim() ? codeSnippet : undefined,
|
||||
codeLanguage: type === "code" ? "typescript" : undefined,
|
||||
imageUrl: type === "polaroid" && imageUrl.trim() ? imageUrl : undefined,
|
||||
imageCaption: type === "polaroid" && imageCaption.trim() ? imageCaption : undefined,
|
||||
likes,
|
||||
};
|
||||
|
||||
onUpdatePost(updated);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-xs animate-in fade-in duration-200">
|
||||
<div
|
||||
className="relative w-full max-w-xl notebook-sheet rounded-2xl p-6 sm:p-8 shadow-2xl border border-[#18181B]/15 max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Washi Tape */}
|
||||
<div className="washi-tape washi-tape-amber top-[-10px] left-1/2 -translate-x-1/2 w-40" />
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between pb-4 border-b border-[#18181B]/10 mb-5">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-8 h-8 rounded-full bg-[#D97706] text-white flex items-center justify-center font-bold text-sm">
|
||||
✏️
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-heading font-extrabold text-lg text-[#18181B]">
|
||||
Notu Düzenle (Admin)
|
||||
</h2>
|
||||
<p className="text-xs font-mono text-[#787D89]">
|
||||
ID: {post.id} • {post.date}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1.5 rounded-lg text-[#787D89] hover:text-[#18181B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Format Selector */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
|
||||
Not Formatı:
|
||||
</label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setType("sticky")}
|
||||
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
|
||||
type === "sticky"
|
||||
? "bg-[#FEF3C7] border-[#D97706] text-[#B45309] shadow-xs"
|
||||
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
|
||||
}`}
|
||||
>
|
||||
<StickyNote className="w-3.5 h-3.5" />
|
||||
<span>Post-it</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setType("notebook")}
|
||||
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
|
||||
type === "notebook"
|
||||
? "bg-[#FAF7F2] border-[#18181B] text-[#18181B] shadow-xs font-bold"
|
||||
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
|
||||
}`}
|
||||
>
|
||||
<BookOpen className="w-3.5 h-3.5" />
|
||||
<span>Defter</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setType("code")}
|
||||
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
|
||||
type === "code"
|
||||
? "bg-[#1E232D] border-[#1E232D] text-white shadow-xs"
|
||||
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
|
||||
}`}
|
||||
>
|
||||
<Code className="w-3.5 h-3.5" />
|
||||
<span>Kod</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setType("polaroid")}
|
||||
className={`flex items-center justify-center gap-1 p-2 rounded-lg text-xs font-heading font-semibold border transition-all ${
|
||||
type === "polaroid"
|
||||
? "bg-[#E0F2FE] border-[#0284C7] text-[#0369A1] shadow-xs"
|
||||
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
|
||||
}`}
|
||||
>
|
||||
<Camera className="w-3.5 h-3.5" />
|
||||
<span>Polaroid</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mood Selector */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
|
||||
Ruh Hali (Mood):
|
||||
</label>
|
||||
<div className="flex items-center gap-2 overflow-x-auto pb-1 scrollbar-none">
|
||||
{MOODS.filter((m) => m.id !== "all").map((m) => (
|
||||
<button
|
||||
key={m.id}
|
||||
type="button"
|
||||
onClick={() => setMood(m.id)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-heading font-semibold shrink-0 border transition-all ${
|
||||
mood === m.id
|
||||
? "bg-[#18181B] text-[#FAF7F2] border-[#18181B]"
|
||||
: "bg-[#FAF7F2] text-[#52525B] border-[#18181B]/10 hover:bg-[#EFE7D6]"
|
||||
}`}
|
||||
>
|
||||
{m.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Başlık:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Başlık..."
|
||||
className="w-full px-3 py-2 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
İçerik:
|
||||
</label>
|
||||
<textarea
|
||||
required
|
||||
rows={4}
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
className="w-full px-3.5 py-2.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif leading-relaxed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Stamped Text */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Damga Metni (Örn: ONAYLANDI, SUÇLU, COMPILED):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={stampedText}
|
||||
onChange={(e) => setStampedText(e.target.value)}
|
||||
placeholder="ONAYLANDI"
|
||||
className="w-full px-3 py-1.5 text-xs font-mono bg-white/80 border border-[#18181B]/15 rounded-lg uppercase"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Code Snippet */}
|
||||
{type === "code" && (
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Kod Parçası:
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={codeSnippet}
|
||||
onChange={(e) => setCodeSnippet(e.target.value)}
|
||||
className="w-full px-3.5 py-2 text-xs font-mono bg-[#1E232D] text-emerald-400 border border-white/10 rounded-lg focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Polaroid Image URL */}
|
||||
{type === "polaroid" && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Resim URL:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={imageUrl}
|
||||
onChange={(e) => setImageUrl(e.target.value)}
|
||||
placeholder="https://images.unsplash.com/..."
|
||||
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Resim Yazısı (Caption):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={imageCaption}
|
||||
onChange={(e) => setImageCaption(e.target.value)}
|
||||
placeholder="Zirve esintisi..."
|
||||
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg font-handwritten text-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Margin Notes Input */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Kenar Notları (Virgülle ayırın):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={marginNote}
|
||||
onChange={(e) => setMarginNote(e.target.value)}
|
||||
placeholder="← tam olarak bu!, not al"
|
||||
className="w-full px-3 py-1.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-handwritten text-lg text-[#E11D48]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Etiketler (Virgülle ayırın):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tagInput}
|
||||
onChange={(e) => setTagInput(e.target.value)}
|
||||
placeholder="felsefe, gece, kahve"
|
||||
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Likes */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Beğeni Sayısı:
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={likes}
|
||||
onChange={(e) => setLikes(parseInt(e.target.value, 10) || 0)}
|
||||
className="w-32 px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-end gap-2.5 pt-3 border-t border-[#18181B]/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 rounded-lg text-xs font-heading font-semibold text-[#52525B] hover:bg-[#18181B]/5 transition-colors"
|
||||
>
|
||||
Vazgeç
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center gap-1.5 px-5 py-2 rounded-lg bg-[#059669] text-white text-xs font-heading font-bold hover:bg-[#047857] shadow-sm transition-all cursor-pointer"
|
||||
>
|
||||
<Save className="w-3.5 h-3.5" />
|
||||
<span>Günü Güncelle</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Search, Sparkles, Plus, Dices, Feather, Clock } from "lucide-react";
|
||||
|
||||
interface HeaderProps {
|
||||
searchQuery: string;
|
||||
onSearchChange: (q: string) => void;
|
||||
onNewPostClick: () => void;
|
||||
onRandomPostClick: () => void;
|
||||
postCount: number;
|
||||
}
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({
|
||||
searchQuery,
|
||||
onSearchChange,
|
||||
onNewPostClick,
|
||||
onRandomPostClick,
|
||||
postCount,
|
||||
}) => {
|
||||
const [timeString, setTimeString] = useState<string>("16:20");
|
||||
|
||||
useEffect(() => {
|
||||
const updateTime = () => {
|
||||
const now = new Date();
|
||||
setTimeString(
|
||||
now.toLocaleTimeString("tr-TR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})
|
||||
);
|
||||
};
|
||||
updateTime();
|
||||
const timer = setInterval(updateTime, 30000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<header className="w-full border-b border-[#18181B]/10 bg-[#FAF7F2]/80 backdrop-blur-md sticky top-0 z-30 transition-colors">
|
||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-3.5 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
{/* Brand & Identity */}
|
||||
<div className="flex items-center gap-3.5 w-full md:w-auto justify-between md:justify-start">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-9 h-9 rounded-full bg-[#18181B] text-[#FAF7F2] flex items-center justify-center font-heading font-black text-lg shadow-sm border border-[#18181B]/20">
|
||||
M
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-heading font-extrabold text-xl tracking-tight text-[#18181B]">
|
||||
mstfyldz
|
||||
</span>
|
||||
<span className="text-[11px] font-mono uppercase px-2 py-0.5 rounded bg-[#18181B]/5 text-[#18181B]/70 border border-[#18181B]/10">
|
||||
v1.0 log
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs font-handwritten text-[#787D89] text-base leading-none mt-0.5">
|
||||
Mustafa Yıldız • Kişisel Karalama & Mood Defteri
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile CTAs */}
|
||||
<div className="flex items-center gap-2 md:hidden">
|
||||
<button
|
||||
onClick={onRandomPostClick}
|
||||
title="Rastgele Bir Not Aç"
|
||||
aria-label="Rastgele Bir Not Aç"
|
||||
className="p-2 rounded-lg bg-[#FAF7F2] border border-[#18181B]/15 text-[#18181B] hover:bg-[#F0ECE1] transition-all shadow-xs"
|
||||
>
|
||||
<Dices className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={onNewPostClick}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#E11D48] text-white text-xs font-heading font-bold shadow-xs hover:bg-[#9F1239] transition-all"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
<span>Karala</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Telemetry & Search & Actions */}
|
||||
<div className="flex items-center gap-3 w-full md:w-auto justify-end">
|
||||
{/* Live Telemetry Pill */}
|
||||
<div className="hidden lg:flex items-center gap-2 px-3 py-1.5 rounded-full bg-[#F3ECE2] border border-[#18181B]/10 text-[11px] font-mono text-[#52525B]">
|
||||
<span className="w-2 h-2 rounded-full bg-[#059669] animate-pulse" />
|
||||
<span>Muğla, TR</span>
|
||||
<span>•</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3 text-[#787D89]" />
|
||||
<span>{timeString}</span>
|
||||
</div>
|
||||
<span>•</span>
|
||||
<span>{postCount} Not Kayıtlı</span>
|
||||
</div>
|
||||
|
||||
{/* Search Box */}
|
||||
<div className="relative flex-1 md:w-56">
|
||||
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-[#787D89]" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
placeholder="Defterde ara..."
|
||||
className="w-full pl-8 pr-3 py-1.5 text-xs bg-white/70 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 focus:border-[#E11D48] transition-all placeholder:text-[#A6ABB5] font-serif"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => onSearchChange("")}
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-xs text-[#787D89] hover:text-[#18181B]"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Desktop Actions */}
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
<button
|
||||
onClick={onRandomPostClick}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#FAF7F2] border border-[#18181B]/15 text-[#18181B] text-xs font-heading font-medium hover:bg-[#F0ECE1] hover:shadow-xs transition-all cursor-pointer"
|
||||
>
|
||||
<Dices className="w-3.5 h-3.5 text-[#D97706]" />
|
||||
<span>Rastgele Not</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={onNewPostClick}
|
||||
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg bg-[#E11D48] text-white text-xs font-heading font-bold hover:bg-[#9F1239] shadow-xs hover:shadow transition-all cursor-pointer"
|
||||
>
|
||||
<Feather className="w-3.5 h-3.5" />
|
||||
<span>+ Yeni Düşünce</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,450 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { JournalPost } from "./types";
|
||||
import {
|
||||
Heart,
|
||||
MessageSquare,
|
||||
Tag,
|
||||
Play,
|
||||
Pause,
|
||||
Maximize2,
|
||||
Terminal,
|
||||
Volume2,
|
||||
CornerDownRight,
|
||||
Check,
|
||||
Sparkles
|
||||
} from "lucide-react";
|
||||
|
||||
interface JournalCardProps {
|
||||
post: JournalPost;
|
||||
onOpenDetail: (post: JournalPost) => void;
|
||||
onTagClick: (tag: string) => void;
|
||||
}
|
||||
|
||||
export const JournalCard: React.FC<JournalCardProps> = ({
|
||||
post,
|
||||
onOpenDetail,
|
||||
onTagClick,
|
||||
}) => {
|
||||
const [likes, setLikes] = useState(post.likes);
|
||||
const [hasLiked, setHasLiked] = useState(false);
|
||||
const [isPlayingAudio, setIsPlayingAudio] = useState(false);
|
||||
const [stampFeedback, setStampFeedback] = useState(false);
|
||||
|
||||
const handleLike = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (!hasLiked) {
|
||||
setLikes((prev) => prev + 1);
|
||||
setHasLiked(true);
|
||||
setStampFeedback(true);
|
||||
setTimeout(() => setStampFeedback(false), 1200);
|
||||
} else {
|
||||
setLikes((prev) => prev - 1);
|
||||
setHasLiked(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleAudio = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setIsPlayingAudio(!isPlayingAudio);
|
||||
};
|
||||
|
||||
// Render format based on type
|
||||
if (post.type === "sticky") {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onOpenDetail(post)}
|
||||
className="group relative bg-[#FEF3C7] border border-[#D97706]/20 p-5 sm:p-6 rounded-lg shadow-sm hover:shadow-md transition-all duration-300 transform hover:-translate-y-1 rotate-[-1deg] cursor-pointer flex flex-col justify-between"
|
||||
>
|
||||
{/* Washi Tape across top */}
|
||||
<div className="washi-tape washi-tape-amber top-[-10px] left-1/2 -translate-x-1/2 w-28" />
|
||||
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between text-xs font-mono text-[#D97706] mb-3 pt-2">
|
||||
<span className="font-bold">{post.date} • {post.timestamp}</span>
|
||||
{post.stampedText && (
|
||||
<span className="rubber-stamp text-[9px] text-[#B45309] border-[#B45309] rotate-[3deg]">
|
||||
{post.stampedText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<p className="font-serif text-[#18181B] text-base sm:text-lg leading-snug font-medium mb-4">
|
||||
“{post.content}”
|
||||
</p>
|
||||
|
||||
{/* Margin Notes */}
|
||||
{post.marginNotes && post.marginNotes.length > 0 && (
|
||||
<div className="my-2 p-2 rounded bg-[#FDE68A]/40 border border-[#D97706]/20">
|
||||
{post.marginNotes.map((note, idx) => (
|
||||
<div key={idx} className="font-handwritten text-[#92400E] text-base leading-tight">
|
||||
{note}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-[#D97706]/15 mt-3">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{post.tags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTagClick(tag);
|
||||
}}
|
||||
className="text-[11px] font-mono text-[#92400E] hover:underline"
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleLike}
|
||||
className={`flex items-center gap-1 text-xs font-mono px-2 py-1 rounded transition-colors ${
|
||||
hasLiked ? "text-[#E11D48] font-bold" : "text-[#B45309] hover:text-[#18181B]"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
|
||||
<span>{likes}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (post.type === "polaroid") {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onOpenDetail(post)}
|
||||
className="group relative bg-[#FAF7F2] border border-[#18181B]/12 p-3 sm:p-4 rounded-sm shadow-sm hover:shadow-lg transition-all duration-300 transform hover:rotate-[0.5deg] rotate-[1deg] cursor-pointer"
|
||||
>
|
||||
{/* Washi Tape */}
|
||||
<div className="washi-tape top-[-10px] left-1/2 -translate-x-1/2 w-32" />
|
||||
|
||||
{/* Image Container */}
|
||||
<div className="w-full aspect-[4/3] rounded-xs overflow-hidden bg-[#18181B]/5 relative mb-3">
|
||||
{post.imageUrl && (
|
||||
<img
|
||||
src={post.imageUrl}
|
||||
alt={post.imageCaption || "Polaroid memory"}
|
||||
className="w-full h-full object-cover group-hover:scale-102 transition-transform duration-500"
|
||||
/>
|
||||
)}
|
||||
<div className="absolute bottom-2 right-2 px-2 py-0.5 rounded bg-black/60 backdrop-blur-xs text-white text-[10px] font-mono">
|
||||
{post.date}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Polaroid Handwritten Caption */}
|
||||
<div className="px-1 pb-1">
|
||||
<div className="font-handwritten text-[#18181B] text-xl leading-snug font-bold">
|
||||
{post.imageCaption || post.content}
|
||||
</div>
|
||||
<p className="text-xs font-serif text-[#52525B] mt-1.5 line-clamp-2">
|
||||
{post.content}
|
||||
</p>
|
||||
|
||||
{/* Margin Note */}
|
||||
{post.marginNotes && post.marginNotes.length > 0 && (
|
||||
<div className="font-handwritten text-[#0284C7] text-base mt-2 flex items-center gap-1">
|
||||
<CornerDownRight className="w-3.5 h-3.5 shrink-0" />
|
||||
<span>{post.marginNotes[0]}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between pt-2.5 border-t border-[#18181B]/8 mt-3">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{post.tags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTagClick(tag);
|
||||
}}
|
||||
className="text-[10px] font-mono text-[#787D89] hover:text-[#18181B]"
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleLike}
|
||||
className={`flex items-center gap-1 text-xs font-mono transition-colors ${
|
||||
hasLiked ? "text-[#E11D48] font-bold" : "text-[#787D89] hover:text-[#18181B]"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
|
||||
<span>{likes}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (post.type === "code") {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onOpenDetail(post)}
|
||||
className="group relative bg-[#1E232D] text-[#FAF7F2] p-5 sm:p-6 rounded-xl shadow-md hover:shadow-xl transition-all duration-300 border border-white/10 cursor-pointer"
|
||||
>
|
||||
{/* Terminal Header */}
|
||||
<div className="flex items-center justify-between pb-3 border-b border-white/10 mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#EF4444]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#F59E0B]" />
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#10B981]" />
|
||||
</div>
|
||||
<span className="text-xs font-mono text-white/60 ml-2">
|
||||
{post.title || "terminal_scrap.rs"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span className="text-[10px] font-mono uppercase px-2 py-0.5 rounded bg-white/10 text-white/80">
|
||||
{post.codeLanguage || "code"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Snippet */}
|
||||
{post.codeSnippet && (
|
||||
<pre className="p-3.5 rounded-lg bg-black/40 text-emerald-400 font-mono text-xs overflow-x-auto leading-relaxed border border-white/5 my-2">
|
||||
<code>{post.codeSnippet}</code>
|
||||
</pre>
|
||||
)}
|
||||
|
||||
{/* Narrative */}
|
||||
<p className="text-xs sm:text-sm font-serif text-white/80 mt-3 leading-relaxed">
|
||||
{post.content}
|
||||
</p>
|
||||
|
||||
{/* Handwritten Margin Note on Dark Card */}
|
||||
{post.marginNotes && post.marginNotes.length > 0 && (
|
||||
<div className="font-handwritten text-[#FDE047] text-base mt-2 flex items-center gap-1.5">
|
||||
<span>>></span>
|
||||
<span>{post.marginNotes.join(" • ")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-white/10 mt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{post.tags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTagClick(tag);
|
||||
}}
|
||||
className="text-[10px] font-mono text-white/60 hover:text-white"
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleLike}
|
||||
className={`flex items-center gap-1 text-xs font-mono ${
|
||||
hasLiked ? "text-[#E11D48] font-bold" : "text-white/60 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
|
||||
<span>{likes}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (post.type === "audio") {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onOpenDetail(post)}
|
||||
className="group relative bg-[#F3ECE2] border border-[#18181B]/12 p-5 sm:p-6 rounded-xl shadow-sm hover:shadow-md transition-all duration-300 cursor-pointer"
|
||||
>
|
||||
{/* Brass clip top right */}
|
||||
<div className="binder-clip absolute top-[-6px] right-6 w-8 h-4 rounded-xs" />
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between text-xs font-mono text-[#52525B] mb-2">
|
||||
<span className="font-bold flex items-center gap-1.5">
|
||||
<Volume2 className="w-3.5 h-3.5 text-[#0284C7]" />
|
||||
SES KAYDI • {post.date}
|
||||
</span>
|
||||
<span className="text-[11px] font-mono">{post.audioDuration || "01:00"}</span>
|
||||
</div>
|
||||
|
||||
{/* Cassette Tape Mockup */}
|
||||
<div className="my-3 p-3.5 rounded-lg bg-[#18181B] text-[#FAF7F2] flex items-center gap-3">
|
||||
<button
|
||||
onClick={toggleAudio}
|
||||
title={isPlayingAudio ? "Durdur" : "Dinle"}
|
||||
aria-label={isPlayingAudio ? "Durdur" : "Dinle"}
|
||||
className="w-10 h-10 rounded-full bg-[#0284C7] hover:bg-[#0369A1] text-white flex items-center justify-center transition-transform active:scale-95 shrink-0"
|
||||
>
|
||||
{isPlayingAudio ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4 ml-0.5" />}
|
||||
</button>
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="text-xs font-mono font-bold truncate text-white">
|
||||
{post.audioTitle || "voice_note.m4a"}
|
||||
</div>
|
||||
{/* Animated Audio Bars */}
|
||||
<div className="flex items-end gap-1 h-5 mt-1.5">
|
||||
{[40, 75, 30, 90, 60, 85, 45, 95, 35, 70, 50, 80, 65, 40].map((h, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`w-1 rounded-full transition-all duration-150 ${
|
||||
isPlayingAudio ? "bg-[#38BDF8] animate-pulse" : "bg-white/30"
|
||||
}`}
|
||||
style={{
|
||||
height: isPlayingAudio ? `${Math.max(20, (h * (i % 2 === 0 ? 1.2 : 0.8)) % 100)}%` : `${h * 0.4}%`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs sm:text-sm font-serif text-[#18181B] mt-2 leading-relaxed">
|
||||
{post.content}
|
||||
</p>
|
||||
|
||||
{post.marginNotes && post.marginNotes.length > 0 && (
|
||||
<div className="font-handwritten text-[#0284C7] text-base mt-2">
|
||||
* {post.marginNotes[0]}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-[#18181B]/8 mt-3">
|
||||
<div className="flex items-center gap-1.5">
|
||||
{post.tags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTagClick(tag);
|
||||
}}
|
||||
className="text-[10px] font-mono text-[#787D89] hover:text-[#18181B]"
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleLike}
|
||||
className={`flex items-center gap-1 text-xs font-mono ${
|
||||
hasLiked ? "text-[#E11D48] font-bold" : "text-[#787D89] hover:text-[#18181B]"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
|
||||
<span>{likes}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Default: Field Notebook Page (Longform / Deep Note)
|
||||
return (
|
||||
<div
|
||||
onClick={() => onOpenDetail(post)}
|
||||
className="group relative notebook-sheet rounded-xl p-5 sm:p-7 shadow-sm hover:shadow-lg transition-all duration-300 border border-[#18181B]/10 cursor-pointer"
|
||||
>
|
||||
{/* Top Binder Clip */}
|
||||
<div className="binder-clip absolute top-[-7px] left-8 w-10 h-4.5 rounded-xs" />
|
||||
|
||||
{/* Header with stamped date and mood badge */}
|
||||
<div className="flex items-start justify-between gap-2 pb-3 border-b border-[#18181B]/8 mb-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-mono text-xs font-bold text-[#18181B]">
|
||||
{post.date} • {post.timestamp}
|
||||
</span>
|
||||
<span className="text-[10px] font-mono px-2 py-0.5 rounded-full bg-[#18181B]/5 text-[#52525B]">
|
||||
{post.moodLabel}
|
||||
</span>
|
||||
</div>
|
||||
{post.title && (
|
||||
<h3 className="font-heading font-extrabold text-lg sm:text-xl text-[#18181B] mt-1.5 group-hover:text-[#E11D48] transition-colors leading-snug">
|
||||
{post.title}
|
||||
</h3>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{post.stampedText && (
|
||||
<span className="rubber-stamp text-[10px] text-[#059669] border-[#059669] rotate-[-2deg] shrink-0">
|
||||
{post.stampedText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Body with Highlighter markers */}
|
||||
<div className="font-serif text-[#18181B] text-sm sm:text-base leading-relaxed space-y-2">
|
||||
<p className="line-clamp-4 whitespace-pre-line">
|
||||
{post.content}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Marginalia & Handwritten Comments */}
|
||||
{post.marginNotes && post.marginNotes.length > 0 && (
|
||||
<div className="my-3.5 p-3 rounded-lg bg-[#FAF7F2] border border-[#18181B]/8 space-y-1">
|
||||
<div className="text-[10px] font-mono uppercase text-[#787D89] font-bold">Kenar Notları:</div>
|
||||
{post.marginNotes.map((note, idx) => (
|
||||
<div key={idx} className="font-handwritten text-[#E11D48] text-base leading-tight flex items-center gap-1.5">
|
||||
<span>•</span>
|
||||
<span>{note}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-[#18181B]/8 mt-4">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
{post.tags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTagClick(tag);
|
||||
}}
|
||||
className="text-[11px] font-mono px-2 py-0.5 rounded bg-[#F3ECE2] text-[#52525B] hover:bg-[#EFE7D6] hover:text-[#18181B] transition-colors"
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={handleLike}
|
||||
className={`flex items-center gap-1 text-xs font-mono transition-colors ${
|
||||
hasLiked ? "text-[#E11D48] font-bold" : "text-[#787D89] hover:text-[#18181B]"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-3.5 h-3.5 ${hasLiked ? "fill-current" : ""}`} />
|
||||
<span>{likes}</span>
|
||||
</button>
|
||||
|
||||
<span className="text-xs font-heading font-semibold text-[#18181B] flex items-center gap-1 group-hover:translate-x-0.5 transition-transform">
|
||||
<span>Oku</span>
|
||||
<Maximize2 className="w-3 h-3 text-[#787D89]" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,195 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { MOODS } from "./mockData";
|
||||
import { MoodType } from "./types";
|
||||
import { Zap, Moon, Sparkles, Feather, Camera, BookOpen, Sun, CloudRain, Wind, Radio } from "lucide-react";
|
||||
|
||||
interface MoodTrackerProps {
|
||||
activeMood: MoodType;
|
||||
onSelectMood: (mood: MoodType) => void;
|
||||
filteredCount: number;
|
||||
}
|
||||
|
||||
export const MoodTracker: React.FC<MoodTrackerProps> = ({
|
||||
activeMood,
|
||||
onSelectMood,
|
||||
filteredCount,
|
||||
}) => {
|
||||
const activeMoodObj = MOODS.find((m) => m.id === activeMood) || MOODS[0];
|
||||
|
||||
const getMoodIcon = (iconName: string, className = "w-4 h-4") => {
|
||||
switch (iconName) {
|
||||
case "Zap":
|
||||
return <Zap className={className} />;
|
||||
case "Moon":
|
||||
return <Moon className={className} />;
|
||||
case "Sparkles":
|
||||
return <Sparkles className={className} />;
|
||||
case "Feather":
|
||||
return <Feather className={className} />;
|
||||
case "Camera":
|
||||
return <Camera className={className} />;
|
||||
default:
|
||||
return <BookOpen className={className} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full relative mb-8">
|
||||
{/* Notebook Top Header Card */}
|
||||
<div className="notebook-sheet rounded-2xl p-5 sm:p-7 relative overflow-hidden transition-all duration-300">
|
||||
{/* Top Paper Header Elements */}
|
||||
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4 pb-5 border-b border-[#18181B]/10">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-handwritten text-2xl sm:text-3xl text-[#18181B] font-bold tracking-tight">
|
||||
MOOD TRACKER & WEATHER LOG
|
||||
</span>
|
||||
<span className="rubber-stamp text-[10px] text-[#E11D48] border-[#E11D48] rotate-[-2deg] hidden sm:inline-flex">
|
||||
CANLI SİNYAL
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs sm:text-sm font-serif text-[#52525B] mt-1 max-w-xl">
|
||||
Günün farklı saatlerindeki zihinsel dalgalanmalar, anlık aydınlanmalar ve saçma gözlemler haritası.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stamped Date & Wax Seal Badge */}
|
||||
<div className="flex items-center gap-3.5 self-start md:self-auto">
|
||||
<div className="text-right font-mono">
|
||||
<div className="text-xs uppercase tracking-wider text-[#787D89] font-semibold">GÜNCEL DURUM</div>
|
||||
<div className="text-sm font-bold text-[#18181B] flex items-center justify-end gap-1.5 mt-0.5">
|
||||
<span className="w-2 h-2 rounded-full bg-[#E11D48] animate-ping" />
|
||||
<span>{activeMoodObj.stampText}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Interactive Wax Seal */}
|
||||
<button
|
||||
onClick={() => onSelectMood("all")}
|
||||
title="Tüm Arşive Dön"
|
||||
aria-label="Tüm Arşive Dön"
|
||||
className="wax-seal w-12 h-12 rounded-full flex flex-col items-center justify-center text-white cursor-pointer relative shrink-0"
|
||||
>
|
||||
<span className="font-heading font-black text-lg leading-none">M</span>
|
||||
<span className="text-[7px] font-mono tracking-tighter uppercase opacity-80 leading-none">LOG</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* The Atmospheric Mood Frequency Wave */}
|
||||
<div className="py-4 relative">
|
||||
<div className="flex items-center justify-between text-[11px] font-mono text-[#787D89] mb-1">
|
||||
<span className="flex items-center gap-1">
|
||||
<Sun className="w-3.5 h-3.5 text-[#D97706]" /> 08:00 (Sabah Kahvesi)
|
||||
</span>
|
||||
<span className="flex items-center gap-1 hidden sm:flex">
|
||||
<Wind className="w-3.5 h-3.5 text-[#0284C7]" /> 14:30 (Kaos & Fikirler)
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<CloudRain className="w-3.5 h-3.5 text-[#7C3AED]" /> 23:45 (Gece Melankolisi)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* SVG Wave Graphic */}
|
||||
<div className="w-full h-28 sm:h-36 relative my-3 flex items-center justify-center">
|
||||
<svg
|
||||
className="w-full h-full"
|
||||
viewBox="0 0 800 120"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
{/* Reference Grid lines */}
|
||||
<line x1="0" y1="65" x2="800" y2="65" stroke="rgba(24, 24, 27, 0.08)" strokeDasharray="4 4" />
|
||||
<line x1="0" y1="25" x2="800" y2="25" stroke="rgba(24, 24, 27, 0.04)" strokeDasharray="2 4" />
|
||||
<line x1="0" y1="105" x2="800" y2="105" stroke="rgba(24, 24, 27, 0.04)" strokeDasharray="2 4" />
|
||||
|
||||
{/* Background gradient fill */}
|
||||
<path
|
||||
d="M 0 65 C 60 25, 140 25, 200 65 C 260 105, 340 105, 400 65 C 460 25, 540 25, 600 65 C 660 105, 740 105, 800 65 L 800 120 L 0 120 Z"
|
||||
fill="url(#moodGradient)"
|
||||
opacity="0.22"
|
||||
/>
|
||||
|
||||
{/* Mathematically precise smooth continuous wave curve */}
|
||||
<path
|
||||
d="M 0 65 C 60 25, 140 25, 200 65 C 260 105, 340 105, 400 65 C 460 25, 540 25, 600 65 C 660 105, 740 105, 800 65"
|
||||
stroke="#18181B"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
/>
|
||||
|
||||
{/* Perfectly round circles positioned exactly at wave peaks & valleys */}
|
||||
<circle cx="100" cy="45" r="7" fill="#D97706" stroke="#FAF7F2" strokeWidth="2.5" />
|
||||
<circle cx="300" cy="85" r="7" fill="#E11D48" stroke="#FAF7F2" strokeWidth="2.5" />
|
||||
<circle cx="500" cy="45" r="7" fill="#059669" stroke="#FAF7F2" strokeWidth="2.5" />
|
||||
<circle cx="700" cy="85" r="8" fill="#7C3AED" stroke="#FAF7F2" strokeWidth="2.5" className="animate-pulse" />
|
||||
|
||||
<defs>
|
||||
<linearGradient id="moodGradient" x1="0" y1="0" x2="800" y2="0" gradientUnits="userSpaceOnUse">
|
||||
<stop stopColor="#D97706" />
|
||||
<stop offset="0.33" stopColor="#E11D48" />
|
||||
<stop offset="0.66" stopColor="#059669" />
|
||||
<stop offset="1" stopColor="#7C3AED" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
{/* Handwritten Mood Annotations on Wave */}
|
||||
<div className="absolute top-1 left-[8%] text-xs font-handwritten text-[#D97706] rotate-[-4deg] hidden sm:block">
|
||||
"kahve etkisi ⚡"
|
||||
</div>
|
||||
<div className="absolute bottom-1 left-[32%] text-xs font-handwritten text-[#E11D48] rotate-[3deg] hidden sm:block">
|
||||
"ani fikir! 💡"
|
||||
</div>
|
||||
<div className="absolute top-1 left-[58%] text-xs font-handwritten text-[#059669] rotate-[-2deg] hidden sm:block">
|
||||
"sakin anlar 🌿"
|
||||
</div>
|
||||
<div className="absolute bottom-1 right-[8%] text-xs font-handwritten text-[#7C3AED] rotate-[-3deg] hidden sm:block">
|
||||
"gece kodu ☕"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Broadcast Status Banner */}
|
||||
<div className="mt-1 mb-5 px-3.5 py-2.5 rounded-xl bg-[#F4EFE6] border border-[#18181B]/10 flex items-center justify-between gap-3 text-xs">
|
||||
<div className="flex items-center gap-2 text-[#18181B]">
|
||||
<Radio className="w-3.5 h-3.5 text-[#E11D48] animate-pulse shrink-0" />
|
||||
<span className="font-serif">
|
||||
<strong>Şu anki Zihinsel Frekans:</strong> {activeMoodObj.label} • <em>{activeMoodObj.sublabel}</em>
|
||||
</span>
|
||||
</div>
|
||||
<span className="font-mono text-[11px] text-[#787D89] shrink-0">
|
||||
{filteredCount} sonuç gösteriliyor
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Mood Filter Pill Tabs */}
|
||||
<div className="flex items-center gap-2 overflow-x-auto pb-1 scrollbar-none">
|
||||
{MOODS.map((mood) => {
|
||||
const isSelected = activeMood === mood.id;
|
||||
return (
|
||||
<button
|
||||
key={mood.id}
|
||||
onClick={() => onSelectMood(mood.id)}
|
||||
className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-heading font-semibold transition-all duration-200 shrink-0 cursor-pointer ${
|
||||
isSelected
|
||||
? "bg-[#18181B] text-[#FAF7F2] shadow-sm translate-y-[-1px]"
|
||||
: "bg-[#FAF7F2] text-[#52525B] hover:bg-[#EFE7D6] hover:text-[#18181B] border border-[#18181B]/10"
|
||||
}`}
|
||||
>
|
||||
<span style={{ color: isSelected ? "#FAF7F2" : mood.color }}>
|
||||
{getMoodIcon(mood.iconName, "w-3.5 h-3.5")}
|
||||
</span>
|
||||
<span>{mood.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,283 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { JournalPost, MoodType, PostType } from "./types";
|
||||
import { MOODS } from "./mockData";
|
||||
import { X, Feather, Sparkles, Tag, StickyNote, BookOpen, Code, Camera, Volume2, Check } from "lucide-react";
|
||||
|
||||
interface NewPostModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onAddPost: (post: JournalPost) => void;
|
||||
}
|
||||
|
||||
export const NewPostModal: React.FC<NewPostModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onAddPost,
|
||||
}) => {
|
||||
const [type, setType] = useState<PostType>("sticky");
|
||||
const [mood, setMood] = useState<MoodType>("spark");
|
||||
const [title, setTitle] = useState("");
|
||||
const [content, setContent] = useState("");
|
||||
const [marginNote, setMarginNote] = useState("");
|
||||
const [tagInput, setTagInput] = useState("");
|
||||
const [codeSnippet, setCodeSnippet] = useState("");
|
||||
const [stampedText, setStampedText] = useState("KARALAMA");
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!content.trim()) return;
|
||||
|
||||
const now = new Date();
|
||||
const dateStr = now.toLocaleDateString("tr-TR", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
}).toUpperCase();
|
||||
|
||||
const timeStr = now.toLocaleTimeString("tr-TR", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
const moodObj = MOODS.find((m) => m.id === mood) || MOODS[0];
|
||||
const tags = tagInput
|
||||
.split(",")
|
||||
.map((t) => t.trim().replace(/^#/, ""))
|
||||
.filter((t) => t.length > 0);
|
||||
|
||||
if (tags.length === 0) tags.push("yeni-not");
|
||||
|
||||
const newPost: JournalPost = {
|
||||
id: `post-${Date.now()}`,
|
||||
type,
|
||||
date: dateStr,
|
||||
timestamp: timeStr,
|
||||
mood,
|
||||
moodLabel: moodObj.label,
|
||||
title: title.trim() ? title : undefined,
|
||||
content,
|
||||
marginNotes: marginNote.trim() ? [marginNote.trim()] : undefined,
|
||||
tags,
|
||||
likes: 1,
|
||||
stampedText: stampedText || moodObj.stampText,
|
||||
codeSnippet: type === "code" && codeSnippet.trim() ? codeSnippet : undefined,
|
||||
codeLanguage: type === "code" ? "typescript" : undefined,
|
||||
};
|
||||
|
||||
onAddPost(newPost);
|
||||
// Reset form
|
||||
setTitle("");
|
||||
setContent("");
|
||||
setMarginNote("");
|
||||
setTagInput("");
|
||||
setCodeSnippet("");
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-xs animate-in fade-in duration-200">
|
||||
<div
|
||||
className="relative w-full max-w-xl notebook-sheet rounded-2xl p-6 sm:p-8 shadow-2xl border border-[#18181B]/15 max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Washi Tape across top */}
|
||||
<div className="washi-tape washi-tape-rose top-[-10px] left-1/2 -translate-x-1/2 w-40" />
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between pb-4 border-b border-[#18181B]/10 mb-5">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-8 h-8 rounded-full bg-[#E11D48] text-white flex items-center justify-center">
|
||||
<Feather className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-heading font-extrabold text-lg text-[#18181B]">
|
||||
Yeni Düşünce Karala
|
||||
</h2>
|
||||
<p className="text-xs font-handwritten text-[#787D89] text-base leading-none">
|
||||
Aklına gelen o saçma veya harika şeyi hemen kaydet
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
title="Kapat"
|
||||
aria-label="Kapat"
|
||||
className="p-1.5 rounded-lg text-[#787D89] hover:text-[#18181B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Format Selector */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
|
||||
Not Formatı:
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setType("sticky")}
|
||||
className={`flex items-center justify-center gap-1.5 p-2 rounded-lg text-xs font-heading font-semibold border transition-all cursor-pointer ${
|
||||
type === "sticky"
|
||||
? "bg-[#FEF3C7] border-[#D97706] text-[#B45309] shadow-xs"
|
||||
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
|
||||
}`}
|
||||
>
|
||||
<StickyNote className="w-3.5 h-3.5" />
|
||||
<span>Post-it / Fikir</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setType("notebook")}
|
||||
className={`flex items-center justify-center gap-1.5 p-2 rounded-lg text-xs font-heading font-semibold border transition-all cursor-pointer ${
|
||||
type === "notebook"
|
||||
? "bg-[#FAF7F2] border-[#18181B] text-[#18181B] shadow-xs font-bold"
|
||||
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
|
||||
}`}
|
||||
>
|
||||
<BookOpen className="w-3.5 h-3.5" />
|
||||
<span>Derin Yazı</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setType("code")}
|
||||
className={`flex items-center justify-center gap-1.5 p-2 rounded-lg text-xs font-heading font-semibold border transition-all cursor-pointer ${
|
||||
type === "code"
|
||||
? "bg-[#1E232D] border-[#1E232D] text-white shadow-xs"
|
||||
: "bg-[#FAF7F2] border-[#18181B]/10 text-[#52525B]"
|
||||
}`}
|
||||
>
|
||||
<Code className="w-3.5 h-3.5" />
|
||||
<span>Kod Kırıntısı</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mood Selector */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1.5">
|
||||
Ruh Hali (Mood):
|
||||
</label>
|
||||
<div className="flex items-center gap-2 overflow-x-auto pb-1 scrollbar-none">
|
||||
{MOODS.filter((m) => m.id !== "all").map((m) => (
|
||||
<button
|
||||
key={m.id}
|
||||
type="button"
|
||||
onClick={() => setMood(m.id)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-heading font-semibold shrink-0 border transition-all cursor-pointer ${
|
||||
mood === m.id
|
||||
? "bg-[#18181B] text-[#FAF7F2] border-[#18181B]"
|
||||
: "bg-[#FAF7F2] text-[#52525B] border-[#18181B]/10 hover:bg-[#EFE7D6]"
|
||||
}`}
|
||||
>
|
||||
{m.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title (for notebook or code) */}
|
||||
{type !== "sticky" && (
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Başlık (Opsiyonel):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
placeholder="Yazı veya karalama başlığı..."
|
||||
className="w-full px-3 py-2 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Düşünce / İçerik:
|
||||
</label>
|
||||
<textarea
|
||||
required
|
||||
rows={4}
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder="Aklındakileri dök... (Filtresiz ve dürüst)"
|
||||
className="w-full px-3.5 py-2.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-serif leading-relaxed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Code Snippet (if code type) */}
|
||||
{type === "code" && (
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Kod Parçası:
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={codeSnippet}
|
||||
onChange={(e) => setCodeSnippet(e.target.value)}
|
||||
placeholder="// const bug = fixLater();"
|
||||
className="w-full px-3.5 py-2 text-xs font-mono bg-[#1E232D] text-emerald-400 border border-white/10 rounded-lg focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Margin Notes Input */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
El Yazısı Kenar Notu:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={marginNote}
|
||||
onChange={(e) => setMarginNote(e.target.value)}
|
||||
placeholder="← tam olarak bu! / not al"
|
||||
className="w-full px-3 py-1.5 text-sm bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-handwritten text-lg text-[#E11D48]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono uppercase text-[#787D89] font-bold mb-1">
|
||||
Etiketler (Virgülle ayırın):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tagInput}
|
||||
onChange={(e) => setTagInput(e.target.value)}
|
||||
placeholder="felsefe, gece, kahve, kod"
|
||||
className="w-full px-3 py-1.5 text-xs bg-white/80 border border-[#18181B]/15 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#E11D48]/30 font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-end gap-2.5 pt-3 border-t border-[#18181B]/10">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 rounded-lg text-xs font-heading font-semibold text-[#52525B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
|
||||
>
|
||||
Vazgeç
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex items-center gap-1.5 px-5 py-2 rounded-lg bg-[#E11D48] text-white text-xs font-heading font-bold hover:bg-[#9F1239] shadow-sm transition-all cursor-pointer"
|
||||
>
|
||||
<Feather className="w-3.5 h-3.5" />
|
||||
<span>Deftere Ekle</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { JournalPost } from "./types";
|
||||
import { X, Heart, Share2, CornerDownRight, Check, BookOpen, Volume2 } from "lucide-react";
|
||||
|
||||
interface PostDetailModalProps {
|
||||
post: JournalPost | null;
|
||||
onClose: () => void;
|
||||
onTagClick: (tag: string) => void;
|
||||
}
|
||||
|
||||
export const PostDetailModal: React.FC<PostDetailModalProps> = ({
|
||||
post,
|
||||
onClose,
|
||||
onTagClick,
|
||||
}) => {
|
||||
const [likes, setLikes] = useState(post?.likes || 0);
|
||||
const [hasLiked, setHasLiked] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
if (!post) return null;
|
||||
|
||||
const handleLike = () => {
|
||||
if (!hasLiked) {
|
||||
setLikes((prev) => prev + 1);
|
||||
setHasLiked(true);
|
||||
} else {
|
||||
setLikes((prev) => prev - 1);
|
||||
setHasLiked(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleShare = () => {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-xs animate-in fade-in duration-200">
|
||||
<div
|
||||
className="relative w-full max-w-2xl notebook-sheet rounded-2xl p-6 sm:p-9 shadow-2xl border border-[#18181B]/15 max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Top binder clip */}
|
||||
<div className="binder-clip absolute top-[-8px] left-10 w-12 h-5 rounded-xs" />
|
||||
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
title="Kapat"
|
||||
aria-label="Kapat"
|
||||
className="absolute top-5 right-5 p-1.5 rounded-lg text-[#787D89] hover:text-[#18181B] hover:bg-[#18181B]/5 transition-colors cursor-pointer"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* Metadata Header */}
|
||||
<div className="flex items-center gap-2 text-xs font-mono text-[#787D89] mb-3">
|
||||
<span className="font-bold text-[#18181B]">{post.date} • {post.timestamp}</span>
|
||||
<span>•</span>
|
||||
<span className="px-2 py-0.5 rounded-full bg-[#18181B]/5 text-[#52525B]">
|
||||
{post.moodLabel}
|
||||
</span>
|
||||
{post.stampedText && (
|
||||
<span className="rubber-stamp text-[10px] text-[#E11D48] border-[#E11D48] ml-auto">
|
||||
{post.stampedText}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Post Title */}
|
||||
{post.title && (
|
||||
<h1 className="font-heading font-black text-2xl sm:text-3xl text-[#18181B] leading-tight mb-4">
|
||||
{post.title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{/* Polaroid Image if any */}
|
||||
{post.imageUrl && (
|
||||
<div className="my-4 p-3 bg-white border border-[#18181B]/10 rounded shadow-sm">
|
||||
<img
|
||||
src={post.imageUrl}
|
||||
alt={post.imageCaption || "Polaroid"}
|
||||
className="w-full max-h-80 object-cover rounded-xs"
|
||||
/>
|
||||
{post.imageCaption && (
|
||||
<div className="font-handwritten text-[#18181B] text-xl font-bold mt-2 px-1">
|
||||
{post.imageCaption}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Code Snippet if any */}
|
||||
{post.codeSnippet && (
|
||||
<div className="my-4">
|
||||
<div className="px-3 py-1.5 bg-[#1E232D] text-white/70 text-xs font-mono rounded-t-lg border border-b-0 border-white/10 flex items-center justify-between">
|
||||
<span>{post.title || "snippet.rs"}</span>
|
||||
<span className="uppercase text-[10px]">{post.codeLanguage || "code"}</span>
|
||||
</div>
|
||||
<pre className="p-4 rounded-b-lg bg-[#141820] text-emerald-400 font-mono text-xs overflow-x-auto leading-relaxed border border-white/10">
|
||||
<code>{post.codeSnippet}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Body Content */}
|
||||
<div className="font-serif text-[#18181B] text-base sm:text-lg leading-relaxed whitespace-pre-line my-4 space-y-4">
|
||||
{post.content}
|
||||
</div>
|
||||
|
||||
{/* Margin Notes */}
|
||||
{post.marginNotes && post.marginNotes.length > 0 && (
|
||||
<div className="my-5 p-4 rounded-xl bg-[#FAF7F2] border border-[#18181B]/10 space-y-1.5">
|
||||
<div className="text-[11px] font-mono uppercase text-[#787D89] font-bold">
|
||||
Yazarın El Yazısı Notları:
|
||||
</div>
|
||||
{post.marginNotes.map((note, idx) => (
|
||||
<div key={idx} className="font-handwritten text-[#E11D48] text-xl leading-tight flex items-center gap-2">
|
||||
<CornerDownRight className="w-4 h-4 shrink-0" />
|
||||
<span>{note}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex items-center gap-2 flex-wrap pt-4 border-t border-[#18181B]/10 mt-6">
|
||||
{post.tags.map((tag) => (
|
||||
<button
|
||||
key={tag}
|
||||
onClick={() => {
|
||||
onTagClick(tag);
|
||||
onClose();
|
||||
}}
|
||||
className="text-xs font-mono px-2.5 py-1 rounded bg-[#F3ECE2] text-[#52525B] hover:bg-[#EFE7D6] hover:text-[#18181B] transition-colors"
|
||||
>
|
||||
#{tag}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between pt-4 mt-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleLike}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg border text-xs font-mono transition-all ${
|
||||
hasLiked
|
||||
? "bg-[#FFE4E6] border-[#E11D48] text-[#E11D48] font-bold"
|
||||
: "bg-white border-[#18181B]/15 text-[#52525B] hover:text-[#18181B]"
|
||||
}`}
|
||||
>
|
||||
<Heart className={`w-4 h-4 ${hasLiked ? "fill-current" : ""}`} />
|
||||
<span>{likes} Beğeni</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-white border border-[#18181B]/15 text-xs font-mono text-[#52525B] hover:text-[#18181B] transition-all"
|
||||
>
|
||||
{copied ? <Check className="w-4 h-4 text-[#059669]" /> : <Share2 className="w-4 h-4" />}
|
||||
<span>{copied ? "Kopyalandı!" : "Paylaş"}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-1.5 rounded-lg bg-[#18181B] text-[#FAF7F2] text-xs font-heading font-semibold hover:bg-black transition-colors"
|
||||
>
|
||||
Kapat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,241 @@
|
||||
import { JournalPost, MoodConfig } from "./types";
|
||||
|
||||
export const MOODS: MoodConfig[] = [
|
||||
{
|
||||
id: "all",
|
||||
label: "Tüm Notlar",
|
||||
sublabel: "Bütün karalamalar",
|
||||
iconName: "BookOpen",
|
||||
color: "#18181B",
|
||||
bgColor: "#EFE7D6",
|
||||
stampText: "ARŞİV",
|
||||
},
|
||||
{
|
||||
id: "focus",
|
||||
label: "Aşırı Odak & Kaos",
|
||||
sublabel: "Hyper Focus & Coffee",
|
||||
iconName: "Zap",
|
||||
color: "#D97706",
|
||||
bgColor: "#FEF3C7",
|
||||
stampText: "⚡ HYPER",
|
||||
},
|
||||
{
|
||||
id: "night",
|
||||
label: "Gece Melankolisi",
|
||||
sublabel: "Saat 03:00 kafası",
|
||||
iconName: "Moon",
|
||||
color: "#7C3AED",
|
||||
bgColor: "#EDE9FE",
|
||||
stampText: "☕ GECE",
|
||||
},
|
||||
{
|
||||
id: "spark",
|
||||
label: "Fikir Patlaması",
|
||||
sublabel: "Rastgele aydınlanmalar",
|
||||
iconName: "Sparkles",
|
||||
color: "#E11D48",
|
||||
bgColor: "#FFE4E6",
|
||||
stampText: "💡 SPARK",
|
||||
},
|
||||
{
|
||||
id: "calm",
|
||||
label: "Sakin & Karalama",
|
||||
sublabel: "Dingin anlar & çay",
|
||||
iconName: "Feather",
|
||||
color: "#059669",
|
||||
bgColor: "#D1FAE5",
|
||||
stampText: "🌿 SAKİN",
|
||||
},
|
||||
{
|
||||
id: "visual",
|
||||
label: "Polaroid & Kırıntılar",
|
||||
sublabel: "Görsel hafıza & ses",
|
||||
iconName: "Camera",
|
||||
color: "#0284C7",
|
||||
bgColor: "#E0F2FE",
|
||||
stampText: "📷 SCRAP",
|
||||
},
|
||||
];
|
||||
|
||||
export const INITIAL_POSTS: JournalPost[] = [
|
||||
{
|
||||
id: "post-today-1",
|
||||
type: "notebook",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "16:45",
|
||||
mood: "spark",
|
||||
moodLabel: "Fikir Patlaması",
|
||||
title: "Prisma & PostgreSQL ile Mimari Senkronizasyon",
|
||||
content: `Bugün defterin tüm veri katmanını uzaktaki PostgreSQL sunucusuna ve Prisma ORM mimarisine bağladık. Metin kırıntılarını kaybetmeden canlı veritabanına taşımak harika hissettiriyor.
|
||||
|
||||
Bir sistem karmaşıklaştıkça tasarım dilinin doğallığını koruyabilmesi en kritik başarı kriterimizdir.`,
|
||||
marginNotes: [
|
||||
"← veritabanı canlıda! 🚀",
|
||||
"65.109... sunucusu aktif"
|
||||
],
|
||||
tags: ["postgresql", "prisma", "mimari", "backend"],
|
||||
likes: 18,
|
||||
stampedText: "SENKRONİZE",
|
||||
},
|
||||
{
|
||||
id: "post-today-2",
|
||||
type: "sticky",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "16:55",
|
||||
mood: "focus",
|
||||
moodLabel: "Aşırı Odak & Kaos",
|
||||
content: "NextAuth paketini projeye dahil etmeden, HMAC SHA-256 imzalı HTTP-only çerezler ve .env değişkenleri ile kendi hafif admin yetkilendirmemizi yazdık. Bazen en iyi bağımlılık, hiç eklenmemiş olan bağımlılıktır.",
|
||||
tags: ["güvenlik", "admin", "cookies", "sadelik"],
|
||||
likes: 29,
|
||||
stampedText: "DOĞRU TESPİT",
|
||||
marginNotes: ["(0 bağımlılık, %100 hafiflik)"],
|
||||
},
|
||||
{
|
||||
id: "post-today-3",
|
||||
type: "code",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "17:08",
|
||||
mood: "night",
|
||||
moodLabel: "Gece Melankolisi",
|
||||
title: "SVG Bezier Matematiksel Düzeltmesi",
|
||||
content: "SVG grafiklerinde preserveAspectRatio parametresini unuttuğunuzda daireleriniz elipse dönüşebilir. Kubik Bezier simetrik dalga denklemi ve 1:1 dairesel oranlar ile frekans haritası mükemmel geometriye kavuştu.",
|
||||
codeSnippet: `// Kubik Bezier simetrik dalga & dairesel koordinatlar
|
||||
const wavePath = "M 0 65 C 60 25, 140 25, 200 65 C 260 105, 340 105, 400 65";
|
||||
// Point 1 (Kahve etkisi): cx=100, cy=45, r=7 (tam yuvarlak)`,
|
||||
codeLanguage: "typescript",
|
||||
tags: ["svg", "math", "frontend", "ui-ux"],
|
||||
likes: 31,
|
||||
marginNotes: ["r=7 tam yuvarlak", "yamuk dairelere son!"],
|
||||
stampedText: "FIXED",
|
||||
},
|
||||
{
|
||||
id: "post-today-4",
|
||||
type: "polaroid",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "17:15",
|
||||
mood: "visual",
|
||||
moodLabel: "Polaroid & Kırıntılar",
|
||||
content: "Kod derlendi, PostgreSQL veritabanı eşitlendi, harita eğrileri yerini buldu. Akşamüstü Muğla rüzgarında demli bir fincan çay molası.",
|
||||
imageCaption: "Muğla Esintisi & Veritabanı Zaferi ☕🏔️",
|
||||
imageUrl: "https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=1000&q=80",
|
||||
tags: ["muğla", "doğa", "kahve", "mola"],
|
||||
likes: 45,
|
||||
marginNotes: ["rakım: Muğla", "derleme süresi: 693ms"],
|
||||
},
|
||||
{
|
||||
id: "post-today-5",
|
||||
type: "audio",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "17:20",
|
||||
mood: "calm",
|
||||
moodLabel: "Sakin & Karalama",
|
||||
content: "Admin panelini tasarlarken kurumsal soğuk paneller yerine günlüğün mum mühürlü, kağıt dokulu estetigini bozmamak harika bir karardı. İçerik yönetim alanı bile defterin bir parçası gibi hissettiriyor.",
|
||||
audioTitle: "Defter_Ruhu_Ve_Yonetim_Notu.m4a",
|
||||
audioDuration: "02:15",
|
||||
tags: ["ses-kaydı", "tasarım", "ux", "düşünceler"],
|
||||
likes: 22,
|
||||
marginNotes: ["* arka planda rüzgar sesi mevcut"],
|
||||
},
|
||||
{
|
||||
id: "post-1",
|
||||
type: "notebook",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "15:42",
|
||||
mood: "spark",
|
||||
moodLabel: "Fikir Patlaması",
|
||||
title: "Neden Her Şeyi Karmaşıklaştırmaya Bayılıyoruz?",
|
||||
content: `Bazen oturup kod yazarken veya hayatı planlarken kendimi 15 katmanlı bir soyutlama tasarlarken buluyorum. Oysa tek ihtiyacım olan bir metin dosyası ve bir fincan iyi demlenmiş çay.
|
||||
|
||||
Basitlik tembellik değil, aksine zihinsel bir lükstür. Bir sistemi basit tutabilmek için önce onun tüm karmaşıklığını çiğneyip sindirmiş olmak gerekir. Tıpkı bu defter gibi: ne kadar az kurumsal filtre, o kadar çok hakikat.`,
|
||||
marginNotes: [
|
||||
"← tam olarak bu!",
|
||||
"not: yarın bu konuyu tekrar oku",
|
||||
"çayı tazelemeyi unutma ☕"
|
||||
],
|
||||
highlightWords: ["Basitlik tembellik değil", "zihinsel bir lükstür"],
|
||||
tags: ["felsefe", "kodlama", "sadelik", "düşünceler"],
|
||||
likes: 12,
|
||||
stampedText: "ONAYLANDI",
|
||||
},
|
||||
{
|
||||
id: "post-2",
|
||||
type: "polaroid",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "14:15",
|
||||
mood: "visual",
|
||||
moodLabel: "Polaroid & Kırıntılar",
|
||||
content: "Zirveye doğru tırmanırken kahve termosunu açtığım o rüzgarlı an. Şehirdeki tüm o Slack bildirimleri ve sonsuz toplantılar burada sadece birer fısıltı.",
|
||||
imageCaption: "The ridge line view! 🏔️ Zirve esintisi ve dökülen kahve.",
|
||||
imageUrl: "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=1000&q=80",
|
||||
tags: ["doğa", "kaçış", "fotoğraf", "dağlar"],
|
||||
likes: 24,
|
||||
marginNotes: ["rakım: ~2100m", "kahve soğumadan önce"],
|
||||
},
|
||||
{
|
||||
id: "post-3",
|
||||
type: "sticky",
|
||||
date: "16 AĞU 2026",
|
||||
timestamp: "11:20",
|
||||
mood: "focus",
|
||||
moodLabel: "Aşırı Odak & Kaos",
|
||||
content: "İnsan beyni bazen 100 sayfalık teknik dokümanı 20 saniyede tarar, ama evden çıkarken kapıyı kilitleyip kilitlemediğini 5 dakika boyunca sokak ortasında sorgular.",
|
||||
tags: ["beyin", "saçmalıklar", "gözlem"],
|
||||
likes: 42,
|
||||
stampedText: "DOĞRU TESPİT",
|
||||
marginNotes: ["(anahtarı yine cebimde buldum)"],
|
||||
},
|
||||
{
|
||||
id: "post-4",
|
||||
type: "code",
|
||||
date: "15 AĞU 2026",
|
||||
timestamp: "23:45",
|
||||
mood: "night",
|
||||
moodLabel: "Gece Melankolisi",
|
||||
title: "Rust & Borrow Checker ile Gece Terapisi",
|
||||
content: "Rust'ta borrow checker ile kavga etmek bir nevi Zen meditasyonu gibi. Sizi acelecilikten arındırıyor ve acımasız bir hakikate zorluyor.",
|
||||
codeSnippet: `// Gece yarısı varoluşsal döngü
|
||||
fn main() -> Result<(), MentalState> {
|
||||
let mut coffee = Coffee::brew("Dark Roast")?;
|
||||
let mut energy = 100;
|
||||
|
||||
while energy > 0 {
|
||||
code_with_passion(&mut coffee);
|
||||
energy -= 10;
|
||||
}
|
||||
|
||||
Ok(Sleep::now())
|
||||
}`,
|
||||
codeLanguage: "rust",
|
||||
tags: ["rust", "yazılım", "gece", "kod"],
|
||||
likes: 19,
|
||||
marginNotes: ["derleyici haklıydı...", "03:12'de çalıştı!"],
|
||||
stampedText: "COMPILED",
|
||||
},
|
||||
{
|
||||
id: "post-5",
|
||||
type: "audio",
|
||||
date: "15 AĞU 2026",
|
||||
timestamp: "21:10",
|
||||
mood: "calm",
|
||||
moodLabel: "Sakin & Karalama",
|
||||
content: "Kadıköy sahilinde yürürken rüzgarın ve vapur düdüğünün arkasında aklıma gelen podcast fikri. Neden yapay zeka çağında el yazısı defterlere bu kadar aşığız?",
|
||||
audioTitle: "Gece Vapuru & Defter Romantizmi.m4a",
|
||||
audioDuration: "01:42",
|
||||
tags: ["ses-kaydı", "kadıköy", "nostalji", "vapur"],
|
||||
likes: 15,
|
||||
marginNotes: ["arka plandaki martı sesi gerçek"],
|
||||
},
|
||||
{
|
||||
id: "post-6",
|
||||
type: "sticky",
|
||||
date: "14 AĞU 2026",
|
||||
timestamp: "18:05",
|
||||
mood: "spark",
|
||||
moodLabel: "Fikir Patlaması",
|
||||
content: "Bugün sokaktaki bir tekir kedi bana öyle bir baktı ki, sanki dün gece geç saatte yazdığım CSS dosyasında 14 tane '!important' kullandığımı biliyordu.",
|
||||
tags: ["kediler", "css", "vicdan-azabı"],
|
||||
likes: 38,
|
||||
stampedText: "SUÇLU",
|
||||
marginNotes: ["(temizleyeceğim söz)"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
export type MoodType = "all" | "focus" | "night" | "spark" | "calm" | "visual";
|
||||
|
||||
export interface MoodConfig {
|
||||
id: MoodType;
|
||||
label: string;
|
||||
sublabel: string;
|
||||
iconName: string;
|
||||
color: string;
|
||||
bgColor: string;
|
||||
stampText: string;
|
||||
}
|
||||
|
||||
export type PostType = "notebook" | "sticky" | "polaroid" | "code" | "audio";
|
||||
|
||||
export interface JournalPost {
|
||||
id: string;
|
||||
type: PostType;
|
||||
date: string;
|
||||
timestamp: string;
|
||||
mood: MoodType;
|
||||
moodLabel: string;
|
||||
title?: string;
|
||||
content: string;
|
||||
marginNotes?: string[];
|
||||
tags: string[];
|
||||
likes: number;
|
||||
highlightWords?: string[];
|
||||
authorNote?: string;
|
||||
|
||||
// Specific properties
|
||||
imageUrl?: string;
|
||||
imageCaption?: string;
|
||||
codeSnippet?: string;
|
||||
codeLanguage?: string;
|
||||
audioDuration?: string;
|
||||
audioTitle?: string;
|
||||
stampedText?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user