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
+5 -107
View File
@@ -9,44 +9,10 @@ import ErrorFeedback from "@/components/ErrorFeedback";
type AuthMode = "login" | "register";
type PageState = "select" | "email-form";
/** Inline Google SVG logo */
function GoogleLogo({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none">
<path
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"
fill="#4285F4"
/>
<path
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
fill="#34A853"
/>
<path
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
fill="#FBBC05"
/>
<path
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
fill="#EA4335"
/>
</svg>
);
}
/** Inline GitHub SVG logo */
function GitHubLogo({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
</svg>
);
}
function AuthPageInner() {
const { user, login, register, oauthLogin, loading: authLoading } = useAuth();
const { user, login, register, loading: authLoading } = useAuth();
const router = useRouter();
const searchParams = useSearchParams();
const refCode = searchParams.get("ref");
// Redirect to main page if already logged in (e.g. after OAuth popup completes)
useEffect(() => {
@@ -61,11 +27,6 @@ function AuthPageInner() {
const [error, setError] = useState("");
const [submitting, setSubmitting] = useState(false);
const handleProviderClick = (provider: string) => {
setError("");
oauthLogin(provider);
};
const handleEmailClick = () => {
setError("");
setPageState("email-form");
@@ -98,7 +59,7 @@ function AuthPageInner() {
if (mode === "login") {
await login(email.trim(), password);
} else {
await register(email.trim(), name.trim(), password, refCode || undefined);
await register(email.trim(), name.trim(), password);
}
router.push("/");
} catch (err: unknown) {
@@ -120,45 +81,14 @@ function AuthPageInner() {
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-[#D4AF37]/20 to-[#D4AF37]/5 border border-[#D4AF37]/30 flex items-center justify-center mx-auto mb-4">
<span className="text-3xl"></span>
</div>
<h1 className="text-2xl font-bold text-white">Welcome to Falah</h1>
<h1 className="text-2xl font-bold text-white">Sign in with Ummah ID</h1>
<p className="text-sm text-gray-500 mt-1">
Your Islamic lifestyle companion
Your Ummah ID works across all Falah apps
</p>
</div>
{/* SSO Buttons */}
{/* Sign-in Methods */}
<div className="space-y-3">
{/* Google */}
<button
onClick={() => handleProviderClick("google")}
disabled={authLoading}
className="w-full flex items-center gap-4 px-5 py-4 rounded-2xl bg-[#111118] border border-gray-800/60 active:bg-[#1a1a24] disabled:opacity-50 transition-all touch-manipulation"
>
<GoogleLogo className="w-6 h-6 shrink-0" />
<span className="text-sm font-medium text-white">
Continue with Google
</span>
</button>
{/* GitHub */}
<button
onClick={() => handleProviderClick("github")}
disabled={authLoading}
className="w-full flex items-center gap-4 px-5 py-4 rounded-2xl bg-[#111118] border border-gray-800/60 active:bg-[#1a1a24] disabled:opacity-50 transition-all touch-manipulation"
>
<GitHubLogo className="w-6 h-6 shrink-0 text-white" />
<span className="text-sm font-medium text-white">
Continue with GitHub
</span>
</button>
{/* Divider */}
<div className="flex items-center gap-3 py-2">
<div className="flex-1 h-px bg-gray-800/60" />
<span className="text-xs text-gray-600">or</span>
<div className="flex-1 h-px bg-gray-800/60" />
</div>
{/* Email */}
<button
onClick={handleEmailClick}
@@ -173,21 +103,6 @@ function AuthPageInner() {
</span>
</button>
</div>
{/* Demo Credentials */}
<div className="mt-8 rounded-2xl bg-[#111118] border border-gray-800/60 p-4 text-center">
<p className="text-xs text-gray-600 uppercase tracking-wider mb-1.5">
Demo Account
</p>
<p className="text-xs text-gray-500">
Email:{" "}
<span className="text-gray-300 font-mono">demo@falahos.my</span>
</p>
<p className="text-xs text-gray-500">
Password:{" "}
<span className="text-gray-300 font-mono">password123</span>
</p>
</div>
</div>
</div>
);
@@ -345,26 +260,9 @@ function AuthPageInner() {
<p className="text-xs text-gray-500">
Get <span className="text-[#D4AF37] font-semibold">5,000 FLH</span>{" "}
free on sign up + 7-day premium trial
{refCode && (
<>
{" "}+{" "}
<span className="text-emerald-400 font-semibold">1,000 FLH</span>{" "}
referral bonus
</>
)}
</p>
</div>
)}
{/* Demo credentials (subtle) */}
<div className="mt-6 rounded-xl bg-[#111118]/60 border border-gray-800/40 p-3 text-center">
<p className="text-xs text-gray-600">
Demo:{" "}
<span className="text-gray-500 font-mono">demo@falahos.my</span>{" "}
<span className="text-gray-600">/</span>{" "}
<span className="text-gray-500 font-mono">password123</span>
</p>
</div>
</div>
</div>
);