feat(market): add Minha Intenções tab with panel, hook, and badge

- Introduced "Minhas Intenções" tab visible only to logged-in users.
- Integrated MyPurchaseIntentionsPanel and useMyPurchaseIntentions hook.
- Added tab badge showing count of pending intentions.
- Extended session API to include user’s main character for downstream features.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-07 17:25:51 -03:00
1 parent f35fee944d
commit 7f42def478
55 files changed
+4906 -95

No files matched your search

+28 -6
View File
@@ -6,6 +6,7 @@ import {
createPurchaseIntention,
getPurchaseIntentionsByListing,
hasPendingIntention,
getPendingIntentionByBuyerAndListing,
} from "@/lib/prisma/queries/purchase-intentions";
import { createNotification } from "@/lib/prisma/queries/notifications";
import { trackEvent } from "@/lib/prisma/queries/telemetry";
@@ -94,12 +95,33 @@ export async function POST(
}
// Verificar se já existe uma intenção pendente
const alreadyHasPending = await hasPendingIntention(listingId, auth.userId);
if (alreadyHasPending) {
return NextResponse.json(
{ success: false, error: "Você já possui uma intenção de compra pendente para este anúncio" },
{ status: 400 }
);
const existingIntention = await getPendingIntentionByBuyerAndListing(listingId, auth.userId);
if (existingIntention) {
// Se já existe e ainda pode trocar o método
if (existingIntention.payment_method_change_count < 1) {
// Permite atualizar o método via endpoint específico
return NextResponse.json(
{
success: false,
error: "Você já possui uma intenção de compra pendente para este anúncio",
existing_intention_id: existingIntention.intention_id,
can_change_payment_method: true,
payment_method_change_count: existingIntention.payment_method_change_count,
message: "Use o endpoint PATCH /api/purchase-intentions/[intentionId]/payment-method para trocar o método de pagamento",
},
{ status: 400 }
);
} else {
// Já atingiu o limite de trocas
return NextResponse.json(
{
success: false,
error: "Você já possui uma intenção de compra pendente para este anúncio e atingiu o limite de trocas de método de pagamento",
existing_intention_id: existingIntention.intention_id,
},
{ status: 400 }
);
}
}
// Criar intenção de compra
+28 -6
View File
@@ -6,6 +6,7 @@ import { createNotification } from "@/lib/prisma/queries/notifications";
import {
createPurchaseIntention,
hasPendingIntention,
getPendingIntentionByBuyerAndListing,
} from "@/lib/prisma/queries/purchase-intentions";
import { trackEvent } from "@/lib/prisma/queries/telemetry";
@@ -98,12 +99,33 @@ export async function POST(
}
// Verificar se já existe uma intenção pendente
const alreadyHasPending = await hasPendingIntention(listingId, auth.userId);
if (alreadyHasPending) {
return NextResponse.json(
{ success: false, error: "Você já possui uma intenção de compra pendente para este anúncio" },
{ status: 400 }
);
const existingIntention = await getPendingIntentionByBuyerAndListing(listingId, auth.userId);
if (existingIntention) {
// Se já existe e ainda pode trocar o método
if (existingIntention.payment_method_change_count < 1) {
// Permite atualizar o método via endpoint específico
return NextResponse.json(
{
success: false,
error: "Você já possui uma intenção de compra pendente para este anúncio",
existing_intention_id: existingIntention.intention_id,
can_change_payment_method: true,
payment_method_change_count: existingIntention.payment_method_change_count,
message: "Use o endpoint PATCH /api/purchase-intentions/[intentionId]/payment-method para trocar o método de pagamento",
},
{ status: 400 }
);
} else {
// Já atingiu o limite de trocas
return NextResponse.json(
{
success: false,
error: "Você já possui uma intenção de compra pendente para este anúncio e atingiu o limite de trocas de método de pagamento",
existing_intention_id: existingIntention.intention_id,
},
{ status: 400 }
);
}
}
// Criar intenção de compra ao invés de mudar status diretamente
+11 -1
View File
@@ -6,6 +6,7 @@ import {
updateListing,
deleteListing,
} from "@/lib/prisma/queries/listings";
import { hasPendingIntention } from "@/lib/prisma/queries/purchase-intentions";
import { updateListingSchema } from "@/lib/validation-schemas";
/**
@@ -17,6 +18,7 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const auth = await optionalAuth(request);
const { id } = await params;
const listingId = parseInt(id, 10);
@@ -42,10 +44,18 @@ export async function GET(
);
}
const hasPendingForUser =
auth && auth.userId
? await hasPendingIntention(listingId, auth.userId)
: false;
return NextResponse.json(
{
success: true,
listing,
listing: {
...listing,
has_pending_intention_for_current_user: hasPendingForUser,
},
},
{ status: 200 }
);
+19 -1
View File
@@ -13,6 +13,7 @@ import {
getActiveListings,
} from "@/lib/prisma/queries/listings";
import { isServerActive } from "@/lib/prisma/queries/servers";
import { getPendingIntentionListingIdsForBuyer } from "@/lib/prisma/queries/purchase-intentions";
/**
* GET /api/listings
@@ -20,6 +21,7 @@ import { isServerActive } from "@/lib/prisma/queries/servers";
*/
export async function GET(request: NextRequest) {
try {
const auth = await optionalAuth(request);
const { searchParams } = new URL(request.url);
const filters = {
@@ -65,10 +67,26 @@ export async function GET(request: NextRequest) {
? await getListings(filters)
: await getActiveListings(filters);
let listingsResponse = listingsData.listings;
if (auth && listingsResponse.length > 0) {
const listingIds = listingsResponse.map((listing) => listing.listing_id);
const pendingListingIds = await getPendingIntentionListingIdsForBuyer(
auth.userId,
listingIds
);
const pendingSet = new Set(pendingListingIds);
listingsResponse = listingsResponse.map((listing) => ({
...listing,
has_pending_intention_for_current_user: pendingSet.has(listing.listing_id),
}));
}
return NextResponse.json(
{
success: true,
listings: listingsData.listings,
listings: listingsResponse,
pagination: {
page: filters.page,
pageSize: filters.pageSize,