feat: rebrand WinningHunter as AdSeeQ

This commit is contained in:
2026-08-10 16:47:29 +03:00
parent 5046d00817
commit 0db82654ba
17 changed files with 87 additions and 22 deletions
+2 -2
View File
@@ -1,11 +1,11 @@
NEXT_PUBLIC_APP_URL=https://your-domain.com
NEXT_PUBLIC_APP_URL=https://adseeq.com
DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/winninghunter?schema=public"
AUTH_COOKIE_NAME=wh_session
AUTH_SESSION_DAYS=30
ADMIN_EMAILS="admin@winninghunter.local"
# Google OAuth (https://console.cloud.google.com/apis/credentials)
# Authorized redirect URI: {NEXT_PUBLIC_APP_URL}/api/auth/google/callback
# Authorized redirect URI: https://adseeq.com/api/auth/google/callback
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
+4 -4
View File
@@ -1,6 +1,6 @@
# WinningHunter MVP
# AdSeeQ
WinningHunter klonu için çalışan MVP:
Meta reklamları, TikTok Shop ürünleri, trendler ve rakip markalar için çalışan reklam zekâsı platformu:
- Next.js 16 + React 19
- Prisma + PostgreSQL
@@ -61,11 +61,11 @@ docker compose up -d --build
1. Yeni proje oluştur.
2. Git repo veya ZIP kaynak olarak bu klasörü bağla.
3. Build type: Dockerfile.
4. Domain: `winninghunter.your-domain.com`.
4. Domain: `adseeq.com`.
5. Env:
```env
NEXT_PUBLIC_APP_URL=https://winninghunter.your-domain.com
NEXT_PUBLIC_APP_URL=https://adseeq.com
DATABASE_URL=postgresql://...
AUTH_COOKIE_NAME=wh_session
AUTH_SESSION_DAYS=30
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "winninghunter",
"name": "adseeq",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "winninghunter",
"name": "adseeq",
"version": "0.1.0",
"dependencies": {
"@prisma/client": "5.22.0",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "winninghunter",
"name": "adseeq",
"version": "0.1.0",
"private": true,
"scripts": {
+3 -3
View File
@@ -3,12 +3,12 @@ set -eu
app_url="${NEXT_PUBLIC_APP_URL:?NEXT_PUBLIC_APP_URL is required}"
if curl --fail --silent --show-error --max-time 10 "${app_url%/}/api/health" >/dev/null; then
echo "WinningHunter healthcheck OK"
echo "AdSeeQ healthcheck OK"
exit 0
fi
if [ -n "${ALERT_WEBHOOK_URL:-}" ]; then
curl --fail --silent --show-error --max-time 10 -X POST -H 'content-type: application/json' --data '{"text":"WinningHunter healthcheck failed"}' "$ALERT_WEBHOOK_URL" >/dev/null || true
curl --fail --silent --show-error --max-time 10 -X POST -H 'content-type: application/json' --data '{"text":"AdSeeQ healthcheck failed"}' "$ALERT_WEBHOOK_URL" >/dev/null || true
fi
echo "WinningHunter healthcheck FAILED" >&2
echo "AdSeeQ healthcheck FAILED" >&2
exit 1
+1 -1
View File
@@ -25,7 +25,7 @@ export default async function AdminOverviewPage() {
return (
<div>
<div className="mb-6 flex flex-wrap items-end justify-between gap-3">
<div><div className="text-sm font-black uppercase tracking-[0.2em] text-violet-600">Kontrol Merkezi</div><h1 className="mt-1 text-3xl font-black">WinningHunter Admin</h1><p className="mt-1 text-slate-500">Kullanıcı, gelir, kredi ve operasyon görünümü.</p></div>
<div><div className="text-sm font-black uppercase tracking-[0.2em] text-violet-600">Kontrol Merkezi</div><h1 className="mt-1 text-3xl font-black">AdSeeQ Admin</h1><p className="mt-1 text-slate-500">Kullanıcı, gelir, kredi ve operasyon görünümü.</p></div>
<div className="rounded-2xl bg-emerald-50 px-4 py-2 text-sm font-bold text-emerald-700">Sistem aktif</div>
</div>
<AdminNav />
+2 -1
View File
@@ -1,6 +1,7 @@
import Link from "next/link";
import { requireUser } from "@/lib/auth/current-user";
import { DashboardMobileNav } from "@/components/dashboard-mobile-nav";
import { BrandLogo } from "@/components/brand-logo";
const nav = [
["Ads", "/dashboard/ads"],
@@ -20,7 +21,7 @@ export default async function DashboardLayout({ children }: { children: React.Re
<div className="min-h-screen bg-slate-50">
<header className="sticky top-0 z-30 border-b border-slate-200 bg-white/90 backdrop-blur">
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-4">
<Link href="/dashboard/ads" className="text-xl font-black">WinningHunter<span className="text-violet-700">.AI</span></Link>
<Link href="/dashboard/ads"><BrandLogo markClassName="h-8 w-8" /></Link>
<DashboardMobileNav isAdmin={user.role === "ADMIN"} />
<nav className="hidden gap-1 lg:flex">
{nav.map(([label, href]) => (
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="18" fill="#6d28d9"/>
<circle cx="29" cy="29" r="16" fill="none" stroke="#fff" stroke-width="7"/>
<path d="M41 41 54 54" fill="none" stroke="#fff" stroke-linecap="round" stroke-width="7"/>
<path d="m21 29 5 5 11-11" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/>
</svg>

After

Width:  |  Height:  |  Size: 416 B

+25 -2
View File
@@ -1,9 +1,32 @@
import type { Metadata } from "next";
import "./globals.css";
const appUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
export const metadata: Metadata = {
title: "WinningHunter MVP",
description: "Dropshipping reklam ve mağaza istihbaratı MVP"
metadataBase: new URL(appUrl),
applicationName: "AdSeeQ",
title: {
default: "AdSeeQ — Reklam ve Trend Zekâsı",
template: "%s | AdSeeQ"
},
description: "Meta reklamlarını, TikTok Shop ürünlerini, yükselen trendleri ve rakip markaları yapay zekâ ile tek panelden keşfedin.",
keywords: ["reklam kütüphanesi", "Meta Ads", "TikTok Shop", "trend analizi", "marka takibi", "reklam zekâsı"],
alternates: { canonical: "/" },
openGraph: {
title: "AdSeeQ — Kazanan reklamları ve trendleri keşfedin",
description: "Meta Ads, TikTok Shop, Magic AI Trends ve Brand Tracker tek panelde.",
url: "/",
siteName: "AdSeeQ",
locale: "tr_TR",
type: "website"
},
twitter: {
card: "summary",
title: "AdSeeQ — Reklam ve Trend Zekâsı",
description: "Kazanan reklamları, ürünleri ve rakip marka hareketlerini keşfedin."
},
robots: { index: true, follow: true }
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
+2
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { BrandLogo } from "@/components/brand-logo";
function GoogleIcon() {
return (
@@ -36,6 +37,7 @@ export default function LoginPage() {
return (
<main className="grid min-h-screen place-items-center bg-slate-50 px-4">
<form onSubmit={submit} className="w-full max-w-md rounded-3xl bg-white p-8 shadow-soft">
<a href="/" className="mb-7 inline-flex"><BrandLogo /></a>
<h1 className="text-3xl font-black">Giriş yap</h1>
<p className="mt-2 text-sm text-slate-500">Hesabınızla veya Google üzerinden güvenli giriş yapın.</p>
+4 -3
View File
@@ -1,10 +1,11 @@
import { LinkButton } from "@/components/ui/button";
import { BrandLogo } from "@/components/brand-logo";
export default function LandingPage() {
return (
<main className="min-h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,#ddd6fe,transparent_35%),#f8fafc]">
<nav className="mx-auto flex max-w-7xl items-center justify-between px-6 py-6">
<div className="text-xl font-black tracking-tight">WinningHunter<span className="text-violet-700">.AI</span></div>
<BrandLogo />
<div className="flex gap-3">
<LinkButton href="/login" className="bg-white text-slate-900 hover:bg-slate-100">Giriş</LinkButton>
<LinkButton href="/pricing">Planlar</LinkButton>
@@ -13,13 +14,13 @@ export default function LandingPage() {
<section className="mx-auto grid max-w-7xl gap-10 px-6 py-20 lg:grid-cols-[1.05fr_0.95fr] lg:items-center">
<div>
<div className="mb-5 inline-flex rounded-full border border-violet-200 bg-white/70 px-4 py-2 text-sm font-semibold text-violet-800">
Meta Ads + Shopify Store Intelligence MVP
Meta Ads + TikTok Shop + AI Trend Intelligence
</div>
<h1 className="max-w-4xl text-5xl font-black leading-tight tracking-tight text-slate-950 md:text-7xl">
Kazanan reklamları ve mağazaları dakikalar içinde keşfet.
</h1>
<p className="mt-6 max-w-2xl text-lg leading-8 text-slate-600">
Dropshipping ve e-ticaret için reklam kütüphanesi, mağaza keşfi, kaydetme, takip ve kota tabanlı üyelik altyapısı tek panelde.
Meta reklam kütüphanesi, TikTok Shop ürün keşfi, Magic AI trend analizi ve rakip marka takibi tek panelde.
</p>
<div className="mt-8 flex flex-wrap gap-3">
<LinkButton href="/register" className="px-6 py-3">Ücretsiz başla</LinkButton>
+2
View File
@@ -2,6 +2,7 @@ import { prisma } from "@/lib/db";
import { LinkButton } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { CheckoutButton } from "@/components/checkout-button";
import { BrandLogo } from "@/components/brand-logo";
export default async function PricingPage() {
const plans = await prisma.plan.findMany({ orderBy: { sortOrder: "asc" } }).catch(() => []);
@@ -15,6 +16,7 @@ export default async function PricingPage() {
return (
<main className="min-h-screen bg-slate-50 px-6 py-12">
<div className="mx-auto max-w-6xl">
<a href="/" className="mb-10 inline-flex"><BrandLogo /></a>
<div className="mb-10 text-center">
<h1 className="text-4xl font-black">Fiyatlandırma</h1>
<p className="mt-3 text-slate-600">Kota tabanlı freemium model: arama, takip ve API kredileri planlara göre açılır.</p>
+2
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { BrandLogo } from "@/components/brand-logo";
function GoogleIcon() {
return (
@@ -33,6 +34,7 @@ export default function RegisterPage() {
return (
<main className="grid min-h-screen place-items-center bg-slate-50 px-4">
<form onSubmit={submit} className="w-full max-w-md rounded-3xl bg-white p-8 shadow-soft">
<a href="/" className="mb-7 inline-flex"><BrandLogo /></a>
<h1 className="text-3xl font-black">Ücretsiz başla</h1>
<label className="mt-6 block text-sm font-bold">Ad</label>
+1 -1
View File
@@ -43,7 +43,7 @@ export function ApifyEmptySearch({ query, planLimit }: { query: string; planLimi
<div className="rounded-3xl border border-dashed border-violet-200 bg-violet-50/60 p-8 text-center md:col-span-2 xl:col-span-3">
<h2 className="text-xl font-black">{query} için kayıtlı reklam bulunamadı</h2>
<p className="mx-auto mt-2 max-w-2xl text-sm text-slate-600">
Bu arama önce WinningHunter veritabanını kontrol eder. Yeni Meta reklamlarını Apify üzerinden getirip aynı aramaya ekleyebilirsiniz.
Bu arama önce AdSeeQ veritabanını kontrol eder. Yeni Meta reklamlarını Apify üzerinden getirip aynı aramaya ekleyebilirsiniz.
</p>
<div className="mx-auto mt-5 flex max-w-sm gap-2">
<label className="sr-only" htmlFor="apify-result-count">Getirilecek reklam adedi</label>
+28
View File
@@ -0,0 +1,28 @@
type BrandLogoProps = {
className?: string;
markClassName?: string;
showWordmark?: boolean;
};
export function BrandLogo({
className = "",
markClassName = "h-9 w-9",
showWordmark = true
}: BrandLogoProps) {
return (
<span className={`inline-flex items-center gap-2.5 ${className}`} aria-label="AdSeeQ">
<span className={`inline-flex shrink-0 items-center justify-center rounded-xl bg-violet-700 text-white shadow-sm ${markClassName}`}>
<svg viewBox="0 0 32 32" className="h-[72%] w-[72%]" aria-hidden="true">
<circle cx="14.5" cy="14.5" r="8" fill="none" stroke="currentColor" strokeWidth="3.4" />
<path d="M20.4 20.4 27 27" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="3.4" />
<path d="M10.5 14.7 13.2 17.4 18.8 11.8" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.5" />
</svg>
</span>
{showWordmark && (
<span className="text-xl font-black tracking-[-0.035em] text-slate-950">
AdSee<span className="text-violet-700">Q</span>
</span>
)}
</span>
);
}
+1 -1
View File
@@ -56,7 +56,7 @@ export function StoreTrackerManager({ tracked, limit }: { tracked: TrackedStoreI
<input value={domain} onChange={(event) => setDomain(event.target.value)} required placeholder="ornek-magaza.com" className="min-w-0 flex-1 rounded-2xl border border-slate-200 px-4 py-3" />
<button disabled={busy !== null} className="rounded-2xl bg-slate-950 px-5 py-3 font-bold text-white disabled:opacity-50">{busy === "add" ? "Ekleniyor…" : "Mağazayı takip et"}</button>
</form>
<div className="mb-5 flex flex-wrap items-center justify-between gap-2 text-sm"><span className="text-slate-500">Yalnızca WinningHunter veritabanında bulunan domainler eklenebilir.</span><span className="rounded-full bg-violet-50 px-3 py-1 font-bold text-violet-700">{tracked.length} / {limit === null ? "∞" : limit}</span></div>
<div className="mb-5 flex flex-wrap items-center justify-between gap-2 text-sm"><span className="text-slate-500">Yalnızca AdSeeQ veritabanında bulunan domainler eklenebilir.</span><span className="rounded-full bg-violet-50 px-3 py-1 font-bold text-violet-700">{tracked.length} / {limit === null ? "∞" : limit}</span></div>
{message && <div className={`mb-4 rounded-xl p-3 text-sm font-semibold ${error ? "bg-rose-50 text-rose-700" : "bg-emerald-50 text-emerald-700"}`}>{message}</div>}
<div className="space-y-3">
{tracked.map((item) => <div key={item.id} className="flex flex-col gap-3 rounded-2xl bg-slate-50 p-4 sm:flex-row sm:items-center sm:justify-between"><a href={`/dashboard/stores/${item.store.id}`} className="min-w-0"><div className="truncate font-black">{item.store.name}</div><div className="truncate text-sm text-slate-500">{item.store.domain} · {item.store.products.map((product) => product.title).join(", ") || "Bestseller yok"} · {item.store.estRevenue30dMax?.toLocaleString() || "—"} est.</div></a><button type="button" disabled={busy !== null} onClick={() => removeStore(item.id)} className="shrink-0 rounded-xl border border-rose-200 px-4 py-2 text-sm font-bold text-rose-700 disabled:opacity-50">{busy === item.id ? "Çıkarılıyor…" : "Takibi bırak"}</button></div>)}
+1 -1
View File
@@ -26,7 +26,7 @@ async function sendAlertEmail(email: string, title: string, brand: string) {
const key = process.env.RESEND_API_KEY?.trim();
const from = process.env.ALERT_FROM_EMAIL?.trim();
if (!key || !from) return false;
const response = await fetch("https://api.resend.com/emails", { method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json" }, body: JSON.stringify({ from, to: [email], subject: `${brand}: yeni reklam`, html: `<p><strong>${brand}</strong> için yeni reklam bulundu.</p><p>${title}</p><p><a href="${process.env.NEXT_PUBLIC_APP_URL || ""}/dashboard/brand-tracker">WinningHunter'da görüntüle</a></p>` }) });
const response = await fetch("https://api.resend.com/emails", { method: "POST", headers: { authorization: `Bearer ${key}`, "content-type": "application/json" }, body: JSON.stringify({ from, to: [email], subject: `${brand}: yeni reklam`, html: `<p><strong>${brand}</strong> için yeni reklam bulundu.</p><p>${title}</p><p><a href="${process.env.NEXT_PUBLIC_APP_URL || ""}/dashboard/brand-tracker">AdSeeQ'da görüntüle</a></p>` }) });
return response.ok;
}