Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 39 additions & 24 deletions apps/web/app/(app)/onboarding/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ export default function OnboardingPage() {
const fileRef = useRef<HTMLInputElement>(null)
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null)
const skippingRef = useRef(false)
const [isSkipping, setIsSkipping] = useState(false)
const [spotlightCategory, setSpotlightCategory] =
useState<SpotlightCategoryId>("productivity")

Expand Down Expand Up @@ -617,13 +618,15 @@ export default function OnboardingPage() {
const handleSkip = useCallback(async () => {
if (skippingRef.current) return
skippingRef.current = true
setIsSkipping(true)
try {
await ensureOrg()
const pendingPath = consumePendingConnectUrl()
router.push(pendingPath ?? "/")
} catch (err) {
console.error(err)
skippingRef.current = false
setIsSkipping(false)
}
}, [ensureOrg, router])

Expand Down Expand Up @@ -798,9 +801,22 @@ export default function OnboardingPage() {
<button
type="button"
onClick={handleSkip}
className="text-[#525966] text-sm hover:text-white transition-colors cursor-pointer"
disabled={isSkipping}
className={cn(
"text-sm transition-colors cursor-pointer flex items-center gap-1.5",
isSkipping
? "text-[#525966] cursor-not-allowed"
: "text-[#525966] hover:text-white",
)}
>
Skip for now →
{isSkipping ? (
<>
<Loader2 className="size-3.5 animate-spin" />
Skipping…
</>
) : (
"Skip for now →"
)}
</button>
</div>

Expand Down Expand Up @@ -870,34 +886,36 @@ export default function OnboardingPage() {
/>

<AnimatePresence>
{isCheckingAccount && (
{hasDetectedAccount && detected !== "resume" && (
<motion.span
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.15 }}
className="absolute right-3 flex items-center pointer-events-none"
className="absolute right-1 flex size-8 items-center justify-center"
>
<Loader2 className="size-4 animate-spin text-[#6BB0FF]" />
{isCheckingAccount ? (
<Loader2 className="size-4 animate-spin text-[#6BB0FF]" />
) : canSubmit ? (
<motion.button
type="button"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
onClick={() =>
handleSubmit(detected as "x" | "linkedin")
}
className="rounded-xl size-8 flex items-center justify-center border-[0.5px] border-[#161F2C] hover:scale-[0.95] active:scale-[0.95] transition-transform cursor-pointer"
style={{
background:
"linear-gradient(180deg, #0D121A -26.14%, #000 100%)",
}}
>
<SubmitArrow />
</motion.button>
) : null}
</motion.span>
)}
</AnimatePresence>

{canSubmit && (
<motion.button
type="button"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
onClick={() => handleSubmit(detected as "x" | "linkedin")}
className="absolute right-1 rounded-xl size-8 flex items-center justify-center border-[0.5px] border-[#161F2C] hover:scale-[0.95] active:scale-[0.95] transition-transform cursor-pointer"
style={{
background:
"linear-gradient(180deg, #0D121A -26.14%, #000 100%)",
}}
>
<SubmitArrow />
</motion.button>
)}
</div>

<AnimatePresence>
Expand Down Expand Up @@ -929,9 +947,6 @@ export default function OnboardingPage() {
{currentAccountLookup?.status === "error" && (
<AlertCircle className="size-3.5 shrink-0" />
)}
{isCheckingAccount && (
<Loader2 className="size-3.5 shrink-0 animate-spin" />
)}
<span>
{currentAccountLookup?.message ??
SOURCE_LABEL[detected as "x" | "linkedin"]}
Expand Down
5 changes: 3 additions & 2 deletions apps/web/app/(app)/settings/integrations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ export default function SettingsIntegrationsPage() {
const searchParams = useSearchParams()

useEffect(() => {
const qs = searchParams.toString()
router.replace(`/settings${qs ? `?${qs}` : ""}#integrations`)
const params = new URLSearchParams(searchParams.toString())
params.set("view", "integrations")
router.replace(`/?${params.toString()}`)
}, [router, searchParams])

return null
Expand Down
173 changes: 95 additions & 78 deletions apps/web/app/(app)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,92 +298,99 @@ export default function SettingsPage() {
<span className="text-white/55 font-medium shrink-0">Settings</span>
</nav>
<div className="flex items-center gap-2 sm:gap-3 shrink-0">
{!isMobile && (
<Popover
open={orgSwitcherOpen && canSwitchOrg}
onOpenChange={(open) => {
if (canSwitchOrg) setOrgSwitcherOpen(open)
}}
>
<PopoverTrigger asChild>
<button
type="button"
disabled={!canSwitchOrg}
{!isMobile &&
(canSwitchOrg ? (
<Popover open={orgSwitcherOpen} onOpenChange={setOrgSwitcherOpen}>
<PopoverTrigger asChild>
<button
type="button"
className={cn(
"group flex items-center gap-2 rounded-full pl-1.5 pr-2.5 py-1.5 transition-colors",
"bg-white/[0.03] border border-white/[0.06]",
"cursor-pointer hover:bg-white/[0.06]",
dmSansClassName(),
)}
>
<span className="flex size-6 shrink-0 items-center justify-center rounded-full bg-white/[0.05] text-white/55">
<Building2 className="size-[13px]" />
</span>
<span className="max-w-[160px] text-left text-[13px] font-medium text-white truncate leading-none">
{org?.name ?? "Personal"}
</span>
<OrgPlanBadge plan={activeOrgPlan} />
<ChevronsUpDown className="size-3.5 shrink-0 text-white/40" />
</button>
</PopoverTrigger>
<PopoverContent
align="end"
side="bottom"
sideOffset={8}
className={cn(
"group flex items-center gap-2 rounded-full pl-1.5 pr-2.5 py-1.5 transition-colors",
"bg-white/[0.03] border border-white/[0.06]",
canSwitchOrg
? "cursor-pointer hover:bg-white/[0.06]"
: "cursor-default",
"w-[260px] max-h-80 overflow-y-auto p-1.5",
"bg-[#14161A] border-white/10 rounded-[14px]",
"shadow-[0px_8px_28px_rgba(0,0,0,0.5)]",
dmSansClassName(),
)}
>
<span className="flex size-6 shrink-0 items-center justify-center rounded-full bg-white/[0.05] text-white/55">
<Building2 className="size-[13px]" />
</span>
<span className="max-w-[160px] text-left text-[13px] font-medium text-white truncate leading-none">
{org?.name ?? "Personal"}
</span>
<OrgPlanBadge plan={activeOrgPlan} />
{canSwitchOrg && (
<ChevronsUpDown className="size-3.5 shrink-0 text-white/40" />
)}
</button>
</PopoverTrigger>
<PopoverContent
align="end"
side="bottom"
sideOffset={8}
{[...(organizations ?? [])]
.sort((a, b) => a.name.localeCompare(b.name))
.map((organization) => {
const isCurrent = organization.id === org?.id
const isSwitching = switchingOrgId === organization.id
const plan = resolveOrgPlan(
organization.id,
isCurrent,
currentPlan,
planByOrgId,
)
return (
<button
key={organization.id}
type="button"
disabled={isCurrent || isSwitching}
onClick={() =>
handleOrgSwitch(organization.slug, organization.id)
}
className={cn(
"w-full flex items-center gap-2.5 rounded-[10px] px-3 py-2 text-left transition-colors",
isCurrent
? "bg-white/5"
: "hover:bg-white/5 cursor-pointer",
"disabled:cursor-default",
)}
>
<Building2 className="size-4 shrink-0 text-white/40" />
<span className="min-w-0 flex-1 truncate text-[13.5px] text-white">
{organization.name}
</span>
{isSwitching ? (
<LoaderIcon className="size-4 shrink-0 animate-spin text-[#4BA0FA]" />
) : isCurrent ? (
<Check className="size-4 shrink-0 text-[#4BA0FA]" />
) : null}
<OrgPlanBadge plan={plan} />
</button>
)
})}
</PopoverContent>
</Popover>
) : (
<div
className={cn(
"w-[260px] max-h-80 overflow-y-auto p-1.5",
"bg-[#14161A] border-white/10 rounded-[14px]",
"shadow-[0px_8px_28px_rgba(0,0,0,0.5)]",
"flex items-center gap-2 rounded-full pl-1.5 pr-2.5 py-1.5",
"bg-white/[0.03] border border-white/[0.06]",
dmSansClassName(),
)}
>
{[...(organizations ?? [])]
.sort((a, b) => a.name.localeCompare(b.name))
.map((organization) => {
const isCurrent = organization.id === org?.id
const isSwitching = switchingOrgId === organization.id
const plan = resolveOrgPlan(
organization.id,
isCurrent,
currentPlan,
planByOrgId,
)
return (
<button
key={organization.id}
type="button"
disabled={isCurrent || isSwitching}
onClick={() =>
handleOrgSwitch(organization.slug, organization.id)
}
className={cn(
"w-full flex items-center gap-2.5 rounded-[10px] px-3 py-2 text-left transition-colors",
isCurrent
? "bg-white/5"
: "hover:bg-white/5 cursor-pointer",
"disabled:cursor-default",
)}
>
<Building2 className="size-4 shrink-0 text-white/40" />
<span className="min-w-0 flex-1 truncate text-[13.5px] text-white">
{organization.name}
</span>
{isSwitching ? (
<LoaderIcon className="size-4 shrink-0 animate-spin text-[#4BA0FA]" />
) : isCurrent ? (
<Check className="size-4 shrink-0 text-[#4BA0FA]" />
) : null}
<OrgPlanBadge plan={plan} />
</button>
)
})}
</PopoverContent>
</Popover>
)}
<span className="flex size-6 shrink-0 items-center justify-center rounded-full bg-white/[0.05] text-white/55">
<Building2 className="size-[13px]" />
</span>
<span className="max-w-[160px] text-left text-[13px] font-medium text-white truncate leading-none">
{org?.name ?? "Personal"}
</span>
<OrgPlanBadge plan={activeOrgPlan} />
</div>
))}
<UserProfileMenu />
</div>
</header>
Expand Down Expand Up @@ -639,6 +646,11 @@ export default function SettingsPage() {
resetConfirmation !== confirmText ||
resetOrganization.isPending
}
title={
!confirmText || resetConfirmation !== confirmText
? `Type "${confirmText || "your name"}" exactly to enable`
: undefined
}
onClick={() =>
resetOrganization.mutate(
{ confirmation: confirmText },
Expand Down Expand Up @@ -719,6 +731,11 @@ export default function SettingsPage() {
deleteEmailConfirm !== user?.email ||
deleteUserAccount.isPending
}
title={
deleteEmailConfirm !== user?.email
? `Type ${user?.email ?? "your email"} exactly to confirm`
: undefined
}
onClick={handleDeleteAccount}
className="relative flex items-center gap-1.5 px-4 py-2 rounded-full text-sm font-medium cursor-pointer transition-opacity bg-[#290F0A] text-[#C73B1B] disabled:opacity-40 disabled:cursor-not-allowed hover:opacity-90"
>
Expand Down
Loading
Loading