mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 02:19:43 +00:00
feat(onboarding): skippable avatar with first-match prompt and usage-reason pack picks
Lets new users skip the onboarding avatar step and instead defers the ask to a lightweight, dismissible prompt shown once after the couple's first match. Also personalizes the home screen's first pack recommendation using the usage_reason already collected during onboarding, which now round-trips through GET /v1/me. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f22d4a3649
commit
fd8981fa28
14 files changed
+567
-77
No files matched your search
@@ -5,6 +5,7 @@ import { useAuthStore, usePacksStore, useResponsesStore, useStreakStore } from "
|
||||
import { skipQuestion, getSkippedQuestionIds } from "../../../lib/skippedQuestions";
|
||||
import { ApiError, apiClient } from "../../../lib/apiClient";
|
||||
import { Events } from "../../../lib/analytics";
|
||||
import { hasSeenAvatarPrompt, markAvatarPromptSeen } from "../../../lib/avatarPromptSeen";
|
||||
import type { AnswerType } from "../../../types";
|
||||
import type { DailyLimitInfo, PackInfo, ResponseData } from "../types";
|
||||
import {
|
||||
@@ -45,7 +46,9 @@ export const useSwipeScreen = () => {
|
||||
const [showPaywall, setShowPaywall] = useState(false);
|
||||
const [packContext, setPackContext] = useState<{ name: string; icon: string } | null>(null);
|
||||
const [showConfetti, setShowConfetti] = useState(false);
|
||||
const [showAvatarPrompt, setShowAvatarPrompt] = useState(false);
|
||||
const [countdown, setCountdown] = useState<string>("");
|
||||
const hasEvaluatedAvatarPrompt = useRef(false);
|
||||
|
||||
const { enabledPackIds, ensureEnabledPacksLoaded, packs } = usePacksStore();
|
||||
const { user, partner, couple } = useAuthStore();
|
||||
@@ -387,9 +390,40 @@ export const useSwipeScreen = () => {
|
||||
}
|
||||
}, [checkAnswerGap, checkDailyLimit, currentIndex, dailyLimitInfo, filteredQuestions, mode, user?.id]);
|
||||
|
||||
const maybeShowAvatarPrompt = useCallback(async () => {
|
||||
const currentUser = useAuthStore.getState().user;
|
||||
if (!currentUser?.id || currentUser.avatar_url) return;
|
||||
|
||||
const alreadySeen = await hasSeenAvatarPrompt(currentUser.id);
|
||||
if (alreadySeen) return;
|
||||
|
||||
// Marked seen as soon as it is shown, so the prompt appears at most once
|
||||
// even if the user leaves the app before responding to it.
|
||||
await markAvatarPromptSeen(currentUser.id);
|
||||
setShowAvatarPrompt(true);
|
||||
Events.avatarPromptShown('first_match');
|
||||
}, []);
|
||||
|
||||
const handleConfettiComplete = useCallback(() => {
|
||||
setShowConfetti(false);
|
||||
setCurrentIndex(prev => prev + 1);
|
||||
|
||||
// Deferred avatar ask: offered once, right after the couple's first match
|
||||
// celebration finishes, never on top of it.
|
||||
if (!hasEvaluatedAvatarPrompt.current) {
|
||||
hasEvaluatedAvatarPrompt.current = true;
|
||||
void maybeShowAvatarPrompt();
|
||||
}
|
||||
}, [maybeShowAvatarPrompt]);
|
||||
|
||||
const handleAvatarPromptAddPhoto = useCallback(() => {
|
||||
setShowAvatarPrompt(false);
|
||||
Events.avatarPromptAccepted('first_match');
|
||||
}, []);
|
||||
|
||||
const handleAvatarPromptDismiss = useCallback(() => {
|
||||
setShowAvatarPrompt(false);
|
||||
Events.avatarPromptDismissed('first_match');
|
||||
}, []);
|
||||
|
||||
const getQuestionPackInfo = useCallback(
|
||||
@@ -419,6 +453,7 @@ export const useSwipeScreen = () => {
|
||||
showPaywall,
|
||||
packContext,
|
||||
showConfetti,
|
||||
showAvatarPrompt,
|
||||
countdown,
|
||||
user,
|
||||
partner,
|
||||
@@ -428,6 +463,8 @@ export const useSwipeScreen = () => {
|
||||
fetchQuestions,
|
||||
handleAnswer,
|
||||
handleConfettiComplete,
|
||||
handleAvatarPromptAddPhoto,
|
||||
handleAvatarPromptDismiss,
|
||||
getQuestionPackInfo,
|
||||
checkAnswerGap,
|
||||
setFeedbackQuestion,
|
||||
|
||||
Reference in new issue
Block a user