Polish milestone dialog and one-time delivery
This commit is contained in:
@@ -16,8 +16,9 @@ async function ownedMilestone(id: string, userId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function clientState(milestone: { brandStatus: string; brandPostUrl: string | null; brandPostError: string | null; selfSharedAt: Date | null }) {
|
||||
function clientState(milestone: { status: string; brandStatus: string; brandPostUrl: string | null; brandPostError: string | null; selfSharedAt: Date | null }) {
|
||||
return {
|
||||
promptStatus: milestone.status,
|
||||
brandStatus: milestone.brandStatus,
|
||||
brandPostUrl: milestone.brandPostUrl,
|
||||
brandPostError: milestone.brandPostError,
|
||||
@@ -86,7 +87,10 @@ export async function PATCH(request: NextRequest, { params }: { params: { id: st
|
||||
// much less disruptive in an X compose window than a full UUID.
|
||||
const updated = await db.socialMilestone.update({
|
||||
where: { id: milestone.id },
|
||||
data: { selfSharedAt: now, publicShareApprovedAt: now, shareToken: token, cardData: card, language },
|
||||
data: {
|
||||
status: 'self_shared', respondedAt: milestone.respondedAt || now,
|
||||
selfSharedAt: now, publicShareApprovedAt: now, shareToken: token, cardData: card, language,
|
||||
},
|
||||
});
|
||||
return NextResponse.json({ ok: true, shareToken: token, shareUrl, shareVersion: now.getTime(), milestone: clientState(updated) });
|
||||
}
|
||||
|
||||
@@ -19,28 +19,18 @@ export async function GET(request: NextRequest) {
|
||||
if (!user || user.socialPromptOptOut) return NextResponse.json({ milestone: null });
|
||||
|
||||
const milestone = await db.socialMilestone.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
OR: [
|
||||
{ status: { in: ['detected', 'shown'] } },
|
||||
{ status: 'approved', brandStatus: { in: ['approved', 'processing', 'failed'] } },
|
||||
],
|
||||
},
|
||||
// `shown` is a durable delivery receipt: one milestone may auto-open only
|
||||
// once, even across refreshes, tabs and later dashboard visits.
|
||||
where: { userId, status: 'detected' },
|
||||
orderBy: { detectedAt: 'asc' },
|
||||
include: { qr: { select: { id: true, title: true, createdAt: true } } },
|
||||
});
|
||||
if (!milestone) return NextResponse.json({ milestone: null });
|
||||
|
||||
if (milestone.status === 'detected') {
|
||||
await db.socialMilestone.update({ where: { id: milestone.id }, data: { status: 'shown', shownAt: new Date() } });
|
||||
}
|
||||
const threshold = milestoneThreshold(milestone.kind);
|
||||
if (!threshold) return NextResponse.json({ milestone: null });
|
||||
const locale = socialLocale(request.nextUrl.searchParams.get('locale'));
|
||||
const shareToken = milestone.shareToken || randomBytes(9).toString('base64url');
|
||||
if (!milestone.shareToken) {
|
||||
await db.socialMilestone.update({ where: { id: milestone.id }, data: { shareToken } });
|
||||
}
|
||||
const shareUrl = `${getWwwOrigin()}/s/m/${shareToken}`;
|
||||
const card = await ensureSocialMilestoneCard({
|
||||
milestoneId: milestone.id,
|
||||
@@ -50,15 +40,21 @@ export async function GET(request: NextRequest) {
|
||||
language: locale,
|
||||
qr: milestone.qr,
|
||||
primaryUseCase: user.primaryUseCase,
|
||||
refresh: ['detected', 'shown'].includes(milestone.status),
|
||||
refresh: true,
|
||||
snapshotAt: new Date(),
|
||||
});
|
||||
const delivered = await db.socialMilestone.updateMany({
|
||||
where: { id: milestone.id, status: 'detected' },
|
||||
data: { status: 'shown', shownAt: new Date(), shareToken },
|
||||
});
|
||||
if (!delivered.count) return NextResponse.json({ milestone: null });
|
||||
|
||||
return NextResponse.json({
|
||||
milestone: {
|
||||
id: milestone.id, qrTitle: milestone.qr.title, threshold,
|
||||
defaultXHandle: user.xHandle,
|
||||
brandStatus: milestone.brandStatus,
|
||||
promptStatus: 'shown',
|
||||
brandPostUrl: milestone.brandPostUrl,
|
||||
brandPostError: milestone.brandPostError,
|
||||
language: locale,
|
||||
|
||||
Reference in New Issue
Block a user