feat: Souq native integration + auth simplification + CE-wide enhancements

Souq Marketplace:
- Native souq pages: browse+filters, create listing, detail+seller profile, orders+chat
- New API: reviews (POST), sellers/[id], upload (multipart), polar-checkout webhook
- Listings API enhanced: minPrice, maxPrice, sortBy, deliveryDays filters
- Seller workflow: start order, mark delivered, upload files, confirm delivery
- Wallet: 5 Polar.sh FLH tiers, production checkout, mock fallback, success banner
- Fix: order redirect /souq/history → /souq/orders

Auth System:
- Unified auth page, removed OAuth provider routing (2 files deleted)
- Deleted oauth.ts (319 lines) — simplified auth chain
- Streamlined login/register API routes

Prisma Schema (+179 lines):
- New models: Listing, Purchase, Review, Group/GroupMember
- Gamification: DailyStreak, XpTransaction, Achievement, UserLevel
- Learning: LearnCourse/Module/Enrollment/Certificate
- Notifications, Referrals, Dhikr, PremiumBenefits

Deleted legacy code:
- src/app/api/marketplace/* (3 files) — replaced by /api/souq/*
- src/app/api/files/[listingId]/route.ts — replaced by /api/souq/upload
- src/app/api/seller/[sellerId]/route.ts — replaced by /api/souq/sellers/[id]

Infrastructure:
- Dockerfile: standalone output, Prisma runtime migration
- docker-compose.yml: Polar env vars, healthcheck fix
- docker-compose.staging.yml: staging on port 4014
- .gitea/workflows/ci.yml: CI pipeline
This commit is contained in:
root
2026-06-24 07:02:03 +02:00
parent 913fa94fb9
commit cfff74e2db
81 changed files with 12132 additions and 2101 deletions
+36 -5
View File
@@ -28,9 +28,11 @@ interface CashoutEntry {
}
const TOP_UP_OPTIONS = [
{ flh: 500, usd: 4.99, bonus: "" },
{ flh: 1100, usd: 9.99, bonus: "+10% bonus" },
{ flh: 3000, usd: 24.99, bonus: "+20% bonus" },
{ flh: 500, usd: 0.99, bonus: "", bestValue: false },
{ flh: 1000, usd: 0.99, bonus: "", bestValue: true },
{ flh: 5000, usd: 4.99, bonus: "+500 bonus", bestValue: false },
{ flh: 10000, usd: 9.99, bonus: "+2,000 bonus", bestValue: false },
{ flh: 50000, usd: 49.99, bonus: "+15,000 bonus", bestValue: false },
];
const EARNING_TIPS = [
@@ -54,6 +56,7 @@ export default function WalletPage() {
text: string;
} | null>(null);
const [topupLoading, setTopupLoading] = useState<number | null>(null);
const [topupSuccess, setTopupSuccess] = useState(false);
useEffect(() => {
if (!loading && !token) router.push("/auth");
@@ -65,6 +68,21 @@ export default function WalletPage() {
}
}, [token]);
// Check for ?topup=success from Polar.sh or mock redirect
useEffect(() => {
const params = new URLSearchParams(window.location.search);
if (params.get("topup") === "success") {
setTopupSuccess(true);
fetchWallet();
// Clean URL param without reload
const url = new URL(window.location.href);
url.searchParams.delete("topup");
window.history.replaceState({}, "", url.toString());
const timer = setTimeout(() => setTopupSuccess(false), 6000);
return () => clearTimeout(timer);
}
}, []);
const fetchWallet = async () => {
try {
const res = await fetch("/mobile/api/wallet", {
@@ -235,6 +253,14 @@ export default function WalletPage() {
<ErrorFeedback error={cashoutMessage.text} kind="default" onRetry={() => setCashoutMessage(null)} context="wallet" />
)}
{/* Top-up Success Banner */}
{topupSuccess && (
<div className="flex items-center gap-2 p-3 rounded-xl text-sm bg-emerald-900/20 border border-emerald-800/40 text-emerald-400">
<Check size={16} />
Top-up successful! FLH has been added to your balance.
</div>
)}
{/* Top-Up Section */}
<div>
<h2 className="text-sm font-semibold text-white mb-3 flex items-center gap-2">
@@ -247,8 +273,13 @@ export default function WalletPage() {
key={opt.flh}
onClick={() => handleTopUp(opt.flh)}
disabled={topupLoading === opt.flh}
className="bg-[#111118] border border-gray-800/60 rounded-2xl p-5 text-center active:scale-95 transition hover:border-[#D4AF37]/30 disabled:opacity-60 min-h-[100px]"
className="bg-[#111118] border border-gray-800/60 rounded-2xl p-4 text-center active:scale-95 transition hover:border-[#D4AF37]/30 disabled:opacity-60 relative"
>
{opt.bestValue && (
<span className="absolute -top-2.5 inset-x-0 mx-auto w-fit px-2 py-0.5 bg-[#D4AF37] text-[#0a0a0f] text-[10px] font-bold rounded-full uppercase tracking-wider">
Best Value
</span>
)}
{topupLoading === opt.flh ? (
<Loader2 size={20} className="animate-spin mx-auto text-[#D4AF37]" />
) : (
@@ -260,7 +291,7 @@ export default function WalletPage() {
${opt.usd.toFixed(2)}
</p>
{opt.bonus && (
<p className="text-xs text-emerald-400 mt-1 font-medium">
<p className="text-[11px] text-emerald-400 mt-1.5 font-semibold">
{opt.bonus}
</p>
)}