Add progress nudge on roadmap

pull/5740/head
Kamran Ahmed 5 months ago
parent 9895956531
commit 6728010173
  1. 19
      src/components/Navigation/AccountDropdown.tsx
  2. 2
      src/components/Navigation/AccountDropdownList.tsx
  3. 69
      src/components/OnboardingNudge.tsx
  4. 22
      src/hooks/use-scroll-position.ts
  5. 2
      src/styles/global.css

@ -10,6 +10,7 @@ import { httpGet } from '../../lib/http.ts';
import { useToast } from '../../hooks/use-toast.ts';
import type { UserDocument } from '../../api/user.ts';
import { NotificationIndicator } from './NotificationIndicator.tsx';
import { OnboardingNudge } from '../OnboardingNudge.tsx';
export type OnboardingConfig = Pick<
UserDocument,
@ -24,7 +25,7 @@ export function AccountDropdown() {
const [isTeamsOpen, setIsTeamsOpen] = useState(false);
const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
const [isConfigLoading, setIsConfigLoading] = useState(true);
const [isConfigLoading, setIsConfigLoading] = useState(false);
const [isOnboardingModalOpen, setIsOnboardingModalOpen] = useState(false);
const [onboardingConfig, setOnboardingConfig] = useState<
OnboardingConfig | undefined
@ -93,6 +94,17 @@ export function AccountDropdown() {
).length;
return (
<>
{shouldShowOnboardingStatus && !isOnboardingModalOpen && (
<OnboardingNudge
onStartOnboarding={() => {
loadOnboardingConfig().then(() => {
setIsOnboardingModalOpen(true);
});
}}
/>
)}
<div className="relative z-[90] animate-fade-in">
{isOnboardingModalOpen && onboardingConfig && (
<OnboardingModal
@ -124,7 +136,9 @@ export function AccountDropdown() {
Account&nbsp;<span className="text-gray-300">/</span>&nbsp;Teams
</span>
<ChevronDown className="h-4 w-4 shrink-0 stroke-[2.5px]" />
{shouldShowOnboardingStatus && !showDropdown && <NotificationIndicator />}
{shouldShowOnboardingStatus && !showDropdown && (
<NotificationIndicator />
)}
</button>
{showDropdown && (
@ -154,5 +168,6 @@ export function AccountDropdown() {
</div>
)}
</div>
</>
);
}

@ -45,7 +45,7 @@ export function AccountDropdownList(props: AccountDropdownListProps) {
className={cn(
'flex h-9 w-full items-center rounded py-1 pl-3 pr-2 text-sm font-medium text-slate-100 hover:opacity-80',
isConfigLoading
? 'striped-loader-lighter flex border-slate-800 opacity-70'
? 'striped-loader-darker flex border-slate-800 opacity-70'
: 'border-slate-600 bg-slate-700',
)}
onClick={onOnboardingClick}

@ -0,0 +1,69 @@
import { cn } from '../lib/classname.ts';
import { memo, useEffect, useState } from 'react';
import { useScrollPosition } from '../hooks/use-scroll-position.ts';
import { X } from 'lucide-react';
type OnboardingNudgeProps = {
onStartOnboarding: () => void;
};
const NUDGE_ONBOARDING_KEY = 'should_nudge_onboarding';
export function OnboardingNudge(props: OnboardingNudgeProps) {
const { onStartOnboarding } = props;
const [isLoading, setIsLoading] = useState(false);
const { y: scrollY } = useScrollPosition();
useEffect(() => {
if (localStorage.getItem(NUDGE_ONBOARDING_KEY) === null) {
localStorage.setItem(NUDGE_ONBOARDING_KEY, 'true');
}
}, []);
if (localStorage.getItem(NUDGE_ONBOARDING_KEY) !== 'true') {
return null;
}
if (scrollY < 100) {
return null;
}
return (
<div
className={cn(
'fixed left-0 right-0 top-0 z-[91] flex w-full items-center justify-center bg-yellow-300 py-1.5',
{
'striped-loader': isLoading,
},
)}
>
<p className="text-base font-semibold text-yellow-950">
Welcome! Please take a moment to{' '}
<button
type="button"
onClick={() => {
setIsLoading(true);
localStorage.setItem(NUDGE_ONBOARDING_KEY, 'false');
onStartOnboarding();
}}
className="underline"
>
complete onboarding
</button>
<button
type="button"
className="relative top-[3px] ml-1 px-1 py-1 text-yellow-600 hover:text-yellow-950"
onClick={(e) => {
e.stopPropagation();
localStorage.setItem(NUDGE_ONBOARDING_KEY, 'false');
setIsLoading(true);
}}
>
<X className="h-4 w-4" strokeWidth={3} />
</button>
</p>
</div>
);
}

@ -0,0 +1,22 @@
import { useEffect, useState } from 'react';
export function useScrollPosition() {
const [scrollPosition, setScrollPosition] = useState<{
x: number;
y: number;
}>({
x: 0,
y: 0,
});
useEffect(() => {
const handleScroll = () => {
setScrollPosition({ x: window.scrollX, y: window.scrollY });
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return scrollPosition;
}

@ -84,7 +84,7 @@ a > code:before {
animation: barberpole 15s linear infinite;
}
.striped-loader-lighter {
.striped-loader-darker {
background-image: repeating-linear-gradient(
-45deg,
transparent,

Loading…
Cancel
Save