diff --git a/src/components/Navigation/AccountDropdown.tsx b/src/components/Navigation/AccountDropdown.tsx
index 07119ea99..7e24b0587 100644
--- a/src/components/Navigation/AccountDropdown.tsx
+++ b/src/components/Navigation/AccountDropdown.tsx
@@ -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,66 +94,80 @@ export function AccountDropdown() {
).length;
return (
-
- {isOnboardingModalOpen && onboardingConfig && (
-
{
- setIsOnboardingModalOpen(false);
- }}
- onIgnoreTask={(taskId, status) => {
- loadOnboardingConfig().finally(() => {});
- }}
- />
- )}
- {isCreatingRoadmap && (
- {
- setIsCreatingRoadmap(false);
+ <>
+ {shouldShowOnboardingStatus && !isOnboardingModalOpen && (
+ {
+ loadOnboardingConfig().then(() => {
+ setIsOnboardingModalOpen(true);
+ });
}}
/>
)}
-
+
+ {isOnboardingModalOpen && onboardingConfig && (
+
{
+ setIsOnboardingModalOpen(false);
+ }}
+ onIgnoreTask={(taskId, status) => {
+ loadOnboardingConfig().finally(() => {});
+ }}
+ />
+ )}
+ {isCreatingRoadmap && (
+ {
+ setIsCreatingRoadmap(false);
+ }}
+ />
+ )}
- {showDropdown && (
- {
+ setIsTeamsOpen(false);
+ setShowDropdown(!showDropdown);
+ }}
>
- {isTeamsOpen ? (
-
- ) : (
-
{
- setIsCreatingRoadmap(true);
- setShowDropdown(false);
- }}
- setIsTeamsOpen={setIsTeamsOpen}
- onOnboardingClick={() => {
- setIsOnboardingModalOpen(true);
- setShowDropdown(false);
- }}
- shouldShowOnboardingStatus={shouldShowOnboardingStatus}
- isConfigLoading={isConfigLoading}
- onboardingConfigCount={onboardingCount}
- doneConfigCount={onboardingDoneCount}
- />
+
+ Account / Teams
+
+
+ {shouldShowOnboardingStatus && !showDropdown && (
+
)}
-
- )}
-
+
+
+ {showDropdown && (
+
+ {isTeamsOpen ? (
+
+ ) : (
+
{
+ setIsCreatingRoadmap(true);
+ setShowDropdown(false);
+ }}
+ setIsTeamsOpen={setIsTeamsOpen}
+ onOnboardingClick={() => {
+ setIsOnboardingModalOpen(true);
+ setShowDropdown(false);
+ }}
+ shouldShowOnboardingStatus={shouldShowOnboardingStatus}
+ isConfigLoading={isConfigLoading}
+ onboardingConfigCount={onboardingCount}
+ doneConfigCount={onboardingDoneCount}
+ />
+ )}
+
+ )}
+
+ >
);
}
diff --git a/src/components/Navigation/AccountDropdownList.tsx b/src/components/Navigation/AccountDropdownList.tsx
index 79deae41d..8b3dbe1c9 100644
--- a/src/components/Navigation/AccountDropdownList.tsx
+++ b/src/components/Navigation/AccountDropdownList.tsx
@@ -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}
diff --git a/src/components/OnboardingNudge.tsx b/src/components/OnboardingNudge.tsx
new file mode 100644
index 000000000..bb2710a82
--- /dev/null
+++ b/src/components/OnboardingNudge.tsx
@@ -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 (
+
+
+ Welcome! Please take a moment to{' '}
+
+
+
+
+ );
+}
diff --git a/src/hooks/use-scroll-position.ts b/src/hooks/use-scroll-position.ts
new file mode 100644
index 000000000..861e28c3c
--- /dev/null
+++ b/src/hooks/use-scroll-position.ts
@@ -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;
+}
diff --git a/src/styles/global.css b/src/styles/global.css
index 7f84672c5..7f5977e73 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -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,