mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 02:19:43 +00:00
feat(pairing): solo sealed answers that unlock on pairing
Unpaired users could only reach a waiting-room dead end and could not answer questions until a partner joined. Responses.couple_id is now nullable so a solo user answers immediately and banks "sealed answers" (couple_id IS NULL). Pairing claims both members' sealed answers and computes matches for every question both have now answered. Invite copy and the unpaired push nudge escalate with the sealed answer count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f22d4a3649
commit
09255da7db
26 files changed
+510
-75
No files matched your search
@@ -48,7 +48,7 @@ export const useSwipeScreen = () => {
|
||||
const [countdown, setCountdown] = useState<string>("");
|
||||
|
||||
const { enabledPackIds, ensureEnabledPacksLoaded, packs } = usePacksStore();
|
||||
const { user, partner, couple } = useAuthStore();
|
||||
const { user, partner, couple, sealedCount } = useAuthStore();
|
||||
|
||||
const filteredQuestions = filterSupportedQuestions(questions);
|
||||
|
||||
@@ -128,20 +128,15 @@ export const useSwipeScreen = () => {
|
||||
filtered = data;
|
||||
}
|
||||
|
||||
if (partner) {
|
||||
// The daily limit applies to pack browsing too, so it is read in both
|
||||
// modes; the answer gap is deliberately only enforced outside packs.
|
||||
if (packId) {
|
||||
setIsBlocked(false);
|
||||
setGapInfo(null);
|
||||
await checkDailyLimit();
|
||||
} else {
|
||||
await Promise.all([checkAnswerGap(), checkDailyLimit()]);
|
||||
}
|
||||
} else {
|
||||
// The daily limit applies solo too, since a sealed answer still counts
|
||||
// against it; the answer gap only makes sense once there is a partner
|
||||
// to be ahead of, and checkAnswerGap already no-ops without one.
|
||||
if (packId) {
|
||||
setIsBlocked(false);
|
||||
setGapInfo(null);
|
||||
setDailyLimitInfo(null);
|
||||
await checkDailyLimit();
|
||||
} else {
|
||||
await Promise.all([checkAnswerGap(), checkDailyLimit()]);
|
||||
}
|
||||
|
||||
if (currentRequestId === fetchRequestId.current) {
|
||||
@@ -156,7 +151,7 @@ export const useSwipeScreen = () => {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}, [packId, partner, checkAnswerGap, checkDailyLimit]);
|
||||
}, [packId, checkAnswerGap, checkDailyLimit]);
|
||||
|
||||
const fetchPending = useCallback(async () => {
|
||||
const currentRequestId = ++fetchRequestId.current;
|
||||
@@ -315,7 +310,7 @@ export const useSwipeScreen = () => {
|
||||
uploadResponseMedia,
|
||||
});
|
||||
|
||||
const data = await apiClient.post<{ match: unknown | null }>("/v1/responses", {
|
||||
const data = await apiClient.post<{ match: unknown | null; sealed_count?: number }>("/v1/responses", {
|
||||
question_id: questionId,
|
||||
answer,
|
||||
response_data: finalResponseData,
|
||||
@@ -323,6 +318,13 @@ export const useSwipeScreen = () => {
|
||||
|
||||
Events.questionAnswered(answer, filteredQuestions[currentIndex]?.pack_id);
|
||||
|
||||
// A solo answer has no partner to match against; the sealed count is
|
||||
// this answer's only visible feedback, so the banner updates instantly
|
||||
// instead of waiting for the couple state to refresh next time round.
|
||||
if (typeof data?.sealed_count === 'number') {
|
||||
useAuthStore.setState({ sealedCount: data.sealed_count });
|
||||
}
|
||||
|
||||
const hasMatch = data?.match != null;
|
||||
|
||||
if (hasMatch) {
|
||||
@@ -423,6 +425,7 @@ export const useSwipeScreen = () => {
|
||||
user,
|
||||
partner,
|
||||
couple,
|
||||
sealedCount,
|
||||
enabledPackIds,
|
||||
effectiveTotal,
|
||||
fetchQuestions,
|
||||
|
||||
Reference in new issue
Block a user