Fix onboarding bottom button not staying pinned, and swipe card crash
- OnboardingScreen used min-h-dvh instead of h-dvh, so the scrollable content area never actually clipped/scrolled internally; the page just grew and the primary CTA button scrolled away with it instead of staying visible. Switch to h-dvh and give each step's button a sticky footer (StepFooterButton). - SwipeCard crashed with "Cannot read properties of null" when a candidate had no photos, because the backend could return null instead of an empty array; guard with optional chaining.
This commit is contained in:
@@ -30,8 +30,8 @@ export function OnboardingScreen({
|
||||
const total = ONBOARDING_STEPS.length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 min-h-dvh">
|
||||
<div className="flex items-center gap-3 px-5 pt-[calc(env(safe-area-inset-top)+16px)] pb-2">
|
||||
<div className="flex flex-col h-dvh">
|
||||
<div className="shrink-0 flex items-center gap-3 px-5 pt-[calc(env(safe-area-inset-top)+16px)] pb-2">
|
||||
<button
|
||||
onClick={onBack ?? (() => router.back())}
|
||||
className="w-9 h-9 rounded-full flex items-center justify-center hover:bg-(--color-bg-secondary) shrink-0"
|
||||
@@ -65,7 +65,7 @@ export function OnboardingScreen({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-2">
|
||||
<div className="shrink-0 flex flex-col gap-2 px-6 pt-6 pb-2">
|
||||
<h1 className="font-[var(--font-display)] font-extrabold text-[24px] text-(--color-content-primary) leading-snug">
|
||||
{title} <span>{emoji}</span>
|
||||
</h1>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { ButtonHTMLAttributes } from "react";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
|
||||
interface StepFooterButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
// دکمهی اصلی هر مرحلهی onboarding؛ چون داخل ناحیهی overflow-y-auto صفحه است،
|
||||
// sticky bottom-0 نسبت به همون ناحیه چسبیده میمونه، نه اینکه ته لیست اسکرول بشه.
|
||||
export function StepFooterButton({
|
||||
children,
|
||||
isLoading,
|
||||
disabled,
|
||||
...props
|
||||
}: StepFooterButtonProps) {
|
||||
return (
|
||||
<div className="sticky bottom-0 -mx-6 px-6 pt-4 pb-[calc(env(safe-area-inset-bottom)+16px)] bg-(--color-bg-primary) border-t border-(--color-border-subtle)">
|
||||
<Button size="lg" isBlock isLoading={isLoading} disabled={disabled} {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { StepFooterButton } from "@/components/onboarding/StepFooterButton";
|
||||
import { TextField } from "@/components/ui/TextField";
|
||||
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -79,9 +79,9 @@ export function BasicInfoStep({
|
||||
|
||||
<ErrorBanner message={error} />
|
||||
|
||||
<Button size="lg" isBlock disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
<StepFooterButton disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
بریم مرحله بعد
|
||||
</Button>
|
||||
</StepFooterButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { StepFooterButton } from "@/components/onboarding/StepFooterButton";
|
||||
import { TextField } from "@/components/ui/TextField";
|
||||
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
||||
import { submitBodyInfo, FullProfile } from "@/lib/api";
|
||||
@@ -58,9 +58,9 @@ export function BodyInfoStep({
|
||||
onChange={(e) => setWeight(e.target.value)}
|
||||
/>
|
||||
<ErrorBanner message={error} />
|
||||
<Button size="lg" isBlock disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
<StepFooterButton disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
بریم مرحله بعد
|
||||
</Button>
|
||||
</StepFooterButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { StepFooterButton } from "@/components/onboarding/StepFooterButton";
|
||||
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
||||
import { FullScreenLoader } from "@/components/ui/FullScreenLoader";
|
||||
import { OptionPicker } from "@/components/ui/OptionPicker";
|
||||
@@ -84,9 +84,9 @@ export function EducationStep({
|
||||
|
||||
<ErrorBanner message={error} />
|
||||
|
||||
<Button size="lg" isBlock disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
<StepFooterButton disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
بریم مرحله بعد
|
||||
</Button>
|
||||
</StepFooterButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { StepFooterButton } from "@/components/onboarding/StepFooterButton";
|
||||
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
||||
import { FullScreenLoader } from "@/components/ui/FullScreenLoader";
|
||||
import { OptionPicker } from "@/components/ui/OptionPicker";
|
||||
@@ -83,9 +83,9 @@ export function GenericOptionStep({
|
||||
max={max}
|
||||
/>
|
||||
<ErrorBanner message={error} />
|
||||
<Button size="lg" isBlock disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
<StepFooterButton disabled={!isValid} isLoading={loading} onClick={handleSubmit}>
|
||||
بریم مرحله بعد
|
||||
</Button>
|
||||
</StepFooterButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { Plus, X } from "lucide-react";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { StepFooterButton } from "@/components/onboarding/StepFooterButton";
|
||||
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
||||
import { confirmPhotosStep, uploadPhoto, deletePhoto, photoUrl, FullProfile, Photo } from "@/lib/api";
|
||||
import { formatApiError } from "@/lib/hooks";
|
||||
@@ -102,9 +102,9 @@ export function PhotosStep({
|
||||
|
||||
<ErrorBanner message={error} />
|
||||
|
||||
<Button size="lg" isBlock disabled={photos.length === 0} isLoading={loading} onClick={handleNext}>
|
||||
<StepFooterButton disabled={photos.length === 0} isLoading={loading} onClick={handleNext}>
|
||||
بریم مرحله بعد
|
||||
</Button>
|
||||
</StepFooterButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/Button";
|
||||
import { StepFooterButton } from "@/components/onboarding/StepFooterButton";
|
||||
import { ErrorBanner } from "@/components/ui/ErrorBanner";
|
||||
import { FullScreenLoader } from "@/components/ui/FullScreenLoader";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -139,9 +139,9 @@ export function ProfileQuestionsStep({
|
||||
|
||||
<ErrorBanner message={error} />
|
||||
|
||||
<Button size="lg" isBlock disabled={!allAnswered} isLoading={loading} onClick={handleSubmit}>
|
||||
<StepFooterButton disabled={!allAnswered} isLoading={loading} onClick={handleSubmit}>
|
||||
بریم مرحله بعد
|
||||
</Button>
|
||||
</StepFooterButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user