Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0e422f7
feat: introduce dual desktop sidebar
tsahimatsliah May 11, 2026
741e26b
refactor(sidebar): polish dual sidebar, drop nested boxes
tsahimatsliah May 11, 2026
e9b0d72
refactor(sidebar): swap surface tones, surface profile identity
tsahimatsliah May 11, 2026
3fc335e
fix(sidebar): restore layout behavior after color swap
tsahimatsliah May 11, 2026
f864b91
fix(sidebar): restore exact layout and swap only container colors
tsahimatsliah May 11, 2026
27a69f7
fix(sidebar): complete color swap by recoloring active rail state
tsahimatsliah May 11, 2026
82d5fb9
style(sidebar): unify sidebar and feed bg on primary background
tsahimatsliah May 11, 2026
b3bd362
style(sidebar): set sidebar surface to surface-float for contrast
tsahimatsliah May 11, 2026
0dd9b97
style(sidebar): polish dual sidebar layout
tsahimatsliah May 12, 2026
41c9ab8
feat(sidebar): dedicated settings and game center panels
tsahimatsliah May 12, 2026
976247f
feat(sidebar): add dedicated Discover rail panel
tsahimatsliah May 12, 2026
8b9bef1
feat(sidebar): add profile rail shortcut
tsahimatsliah May 12, 2026
cc19653
feat(sidebar): track recently visited feeds in Home panel
tsahimatsliah May 12, 2026
6d22c70
fix(feed): style cards-layout actions strip as a header
tsahimatsliah May 12, 2026
db33029
refactor(sidebar): unify utility panel header
tsahimatsliah May 12, 2026
5d27887
style(sidebar): place profile rail link below Game Center
tsahimatsliah May 12, 2026
8ceec0a
fix(settings): persist client-only sidebar flags locally
tsahimatsliah May 12, 2026
6e911b9
refactor(profile-menu): move profile entries into sidebar Profile panel
tsahimatsliah May 12, 2026
4f0d3a7
style(feed): tighten list-frame header and search controls
tsahimatsliah May 12, 2026
f8a6098
fix(sidebar): resolve CI lint, build, and test failures
tsahimatsliah May 12, 2026
28c4a69
chore: re-trigger CI
tsahimatsliah May 12, 2026
404228b
feat(sidebar): polish dual-sidebar UX
tsahimatsliah May 12, 2026
e2754f7
style(sidebar): match rail hover panel to support dropdown
tsahimatsliah May 12, 2026
2526de6
style(sidebar): tighten rail hover panel to match Support dropdown
tsahimatsliah May 12, 2026
fb20955
feat(sidebar): rail icon click navigates to category landing page
tsahimatsliah May 12, 2026
1424a13
style(sidebar): match rail hover panel rows to Support exactly
tsahimatsliah May 12, 2026
a5c3722
fix(sidebar): squads rail click is instant + first item highlights
tsahimatsliah May 12, 2026
dd5907c
feat(layout): unify page header strip across feed pages
tsahimatsliah May 13, 2026
6032002
feat(sidebar): WIP profile stats, recent pages, and rail polish
tsahimatsliah May 13, 2026
acbab32
feat(layout): extend PageHeader to more sidebar destinations
tsahimatsliah May 13, 2026
514e441
fix(sidebar): daily.dev logo + snug tooltips on the rail top
tsahimatsliah May 13, 2026
558150a
feat(layout): finish PageHeader rollout and stabilize settings header
tsahimatsliah May 13, 2026
cde9cfd
feat(sidebar): animated collapse toggle slides between rail and panel
tsahimatsliah May 13, 2026
042586e
fix(settings): page header reflects current page + notifications tabs
tsahimatsliah May 13, 2026
15d93f9
fix(shared): unblock CI for staging deploy
tsahimatsliah May 13, 2026
3becd25
feat(extension): logged-out parity, top hero cards, profile widget po…
tsahimatsliah May 14, 2026
f08b007
feat(sidebar): right-align achievements + tighten stats strip
tsahimatsliah May 14, 2026
57648b9
feat(sidebar): refine profile widget hover, alignment, and streak pop…
tsahimatsliah May 14, 2026
f05cecf
fix(sidebar): rebalance profile widget vertical rhythm
tsahimatsliah May 14, 2026
624b41a
fix(sidebar): scope hover underline to display name and bump stats→Ne…
tsahimatsliah May 14, 2026
7745791
chore(settings): remove duplicate Hot Takes entry from Gamification
tsahimatsliah May 14, 2026
cf8af4b
fix(ci): resolve lint, strict, and test regressions on dual-sidebar b…
tsahimatsliah May 14, 2026
5e50d9f
fix(sidebar): keep streak popup below button instead of overlapping
tsahimatsliah May 14, 2026
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
12 changes: 7 additions & 5 deletions packages/extension/src/newtab/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { ExtensionContextProvider } from '../contexts/ExtensionContext';
import CustomRouter from '../lib/CustomRouter';
import { version } from '../../package.json';
import MainFeedPage from './MainFeedPage';
import HijackingLoginStrip from './HijackingLoginStrip';
import { BootDataProvider } from '../../../shared/src/contexts/BootProvider';
import { getContentScriptPermissionAndRegister } from '../lib/extensionScripts';
import { useContentScriptStatus } from '../../../shared/src/hooks';
Expand Down Expand Up @@ -67,7 +66,7 @@ const feedErrorFallback: ReactElement = (
</div>
);

function HijackingPage({
function OnboardingHijackPage({
onPageChanged,
}: {
onPageChanged: (page: string) => void;
Expand All @@ -87,7 +86,6 @@ function HijackingPage({
onPageChanged={onPageChanged}
initialPage="/"
shouldInitializeCurrentPage={false}
shortcuts={<HijackingLoginStrip />}
/>
);
}
Expand All @@ -108,8 +106,12 @@ function InternalApp(): ReactElement {
const { growthbook } = useGrowthBookContext();
const isPageReady =
(growthbook?.ready && router?.isReady && isAuthReady) || isTesting;
// Logged-out users now stay on the regular MainFeedPage (with the
// sticky sign-in strip up top + the public Popular feed). Only users
// who have signed up but haven't finished onboarding still get the
// onboarding hijack so we keep nudging them through the flow.
const shouldRedirectOnboarding =
isPageReady && (!user || !isOnboardingComplete) && !isTesting;
isPageReady && !!user && !isOnboardingComplete && !isTesting;

useCheckLocation();
useCheckCoresRole();
Expand Down Expand Up @@ -150,7 +152,7 @@ function InternalApp(): ReactElement {
return (
<ErrorBoundary feature="extension-feed" fallback={feedErrorFallback}>
<DndContextProvider>
<HijackingPage onPageChanged={onPageChanged} />
<OnboardingHijackPage onPageChanged={onPageChanged} />
</DndContextProvider>
</ErrorBoundary>
);
Expand Down
72 changes: 72 additions & 0 deletions packages/extension/src/newtab/ExtensionSignInStrip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { ReactElement } from 'react';
import React from 'react';
import {
Button,
ButtonSize,
ButtonVariant,
} from '@dailydotdev/shared/src/components/buttons/Button';
import {
Typography,
TypographyType,
} from '@dailydotdev/shared/src/components/typography/Typography';
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
import { AuthTriggers } from '@dailydotdev/shared/src/lib/auth';

// Sticky banner pinned to the top of the new tab for logged-out users.
// Sits above shortcuts and any other top-page modules so the auth CTAs
// are the first thing the user sees and remain reachable while
// scrolling. Shares the feed's primary background so it reads as part
// of the same surface (no glass / blur / shadow).
export const ExtensionSignInStrip = (): ReactElement | null => {
const { isAuthReady, isLoggedIn, showLogin } = useAuthContext();

if (!isAuthReady || isLoggedIn) {
return null;
}

const onLogIn = () =>
showLogin({
trigger: AuthTriggers.MainButton,
options: { isLogin: true },
});
const onSignUp = () =>
showLogin({
trigger: AuthTriggers.MainButton,
options: { isLogin: false },
});

return (
<section
aria-label="Sign in to daily.dev"
className="sticky top-0 z-3 mx-4 mb-3 laptop:mx-0"
>
<div className="flex items-center justify-between gap-4 rounded-12 border border-border-subtlest-quaternary bg-background-default px-6 py-4">
<Typography
type={TypographyType.Body}
bold
className="min-w-0 flex-1 text-text-primary"
>
Sign in to personalize your feed and save what matters.
</Typography>
<div className="flex shrink-0 items-center gap-2">
<Button
type="button"
variant={ButtonVariant.Secondary}
size={ButtonSize.Small}
onClick={onLogIn}
>
Log in
</Button>
<Button
type="button"
variant={ButtonVariant.Primary}
size={ButtonSize.Small}
onClick={onSignUp}
>
Sign up
</Button>
</div>
</div>
</section>
);
};
236 changes: 236 additions & 0 deletions packages/extension/src/newtab/ExtensionTopBanners.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import type { ReactElement } from 'react';
import React, { useRef } from 'react';
import classNames from 'classnames';
import { TopHero } from '@dailydotdev/shared/src/components/banners/HeroBottomBanner';
import { useReadingReminderHero } from '@dailydotdev/shared/src/hooks/notifications/useReadingReminderHero';
import {
fileValidation,
useUploadCv,
} from '@dailydotdev/shared/src/features/profile/hooks/useUploadCv';
import { useLazyModal } from '@dailydotdev/shared/src/hooks/useLazyModal';
import { LazyModal } from '@dailydotdev/shared/src/components/modals/common/types';
import { useSettingsContext } from '@dailydotdev/shared/src/contexts/SettingsContext';
import { useActions } from '@dailydotdev/shared/src/hooks';
import { ActionType } from '@dailydotdev/shared/src/graphql/actions';
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
import { useIsShortcutsHubEnabled } from '@dailydotdev/shared/src/features/shortcuts/hooks/useIsShortcutsHubEnabled';
import { useShortcutLinks } from '@dailydotdev/shared/src/features/shortcuts/hooks/useShortcutLinks';
import { useThemedAsset } from '@dailydotdev/shared/src/hooks/utils';
import ReadingReminderCatLaptop from '@dailydotdev/shared/src/components/banners/ReadingReminderCatLaptop';
import {
cloudinaryShortcutsIconsGmail,
cloudinaryShortcutsIconsOpenai,
cloudinaryShortcutsIconsReddit,
uploadCvBgMobile,
} from '@dailydotdev/shared/src/lib/image';

// Bare-illustration frame — slightly wider than tall so the CV cluster
// (rendered as a background image below) has horizontal room without
// cropping the calculator/laptop edges. `self-center` plus `items-center
// justify-center` keeps the other cards' illustrations centered too.
const illustrationFrameClass =
'!m-0 flex h-24 w-32 shrink-0 items-center justify-center self-center tablet:h-28 tablet:w-36';

const CvIllustration = (): ReactElement => (
<div
className={classNames(illustrationFrameClass, 'overflow-hidden')}
aria-hidden
>
<span
className="block size-full bg-no-repeat"
// The source art (712×860) clusters all of the subjects in the
// upper ~46% of the canvas with a large transparent lower half.
// We scale the background to ~220% of the frame height so the
// cluster's vertical midpoint (~23% of the source) lands near the
// frame's vertical center, then anchor the image to the top so
// the empty lower half is cropped out instead of being shown as a
// blank stripe at the bottom of the card.
style={{
backgroundImage: `url(${uploadCvBgMobile})`,
backgroundPosition: 'center top',
backgroundSize: 'auto 220%',
}}
/>
</div>
);

// Compact cat illustration matched to `illustrationFrameClass` so the
// reminder card lines up height-wise with the CV / shortcuts cards in
// the extension top row (the `TopHero` default cat is sized for the
// taller webapp variant).
const CompactReminderCat = (): ReactElement => (
<ReadingReminderCatLaptop className="!m-0 h-24 w-28 shrink-0 self-center rounded-12 object-contain tablet:h-28 tablet:w-32" />
);

const ShortcutsIllustration = (): ReactElement => {
const { githubShortcut } = useThemedAsset();
// Cluster of four site icons echoes the in-feed onboarding row but
// compressed into the illustration frame so the cards line up.
const icons = [
{ src: cloudinaryShortcutsIconsGmail, rotate: '-rotate-12' },
{ src: githubShortcut, rotate: 'rotate-0' },
{ src: cloudinaryShortcutsIconsReddit, rotate: 'rotate-12' },
{ src: cloudinaryShortcutsIconsOpenai, rotate: '-rotate-6' },
];

return (
<div className={illustrationFrameClass} aria-hidden>
<div className="grid grid-cols-2 gap-1.5">
{icons.map(({ src, rotate }) => (
<div
key={src}
className={classNames(
'flex size-9 items-center justify-center rounded-full bg-background-default shadow-2',
rotate,
)}
>
<img
src={src}
alt=""
loading="lazy"
className="size-5 object-contain"
/>
</div>
))}
</div>
</div>
);
};

type UseShortcutsOnboardingResult = {
shouldShow: boolean;
onAddClick: () => void;
};

const useShortcutsOnboarding = (): UseShortcutsOnboardingResult => {
const hubEnabled = useIsShortcutsHubEnabled();
const { showTopSites, toggleShowTopSites } = useSettingsContext();
const { openModal } = useLazyModal();
const { completeAction, checkHasCompleted } = useActions();
const { shortcutLinks } = useShortcutLinks();

// Once the user has at least one shortcut pinned (custom or top-site),
// the actual shortcuts row renders above these cards and the
// onboarding tile becomes redundant. Drop it so the remaining hero
// cards expand to fill the row.
const hasShortcuts = (shortcutLinks?.length ?? 0) > 0;
const shouldShow = !hasShortcuts;

const completeFirstSession = () => {
if (!checkHasCompleted(ActionType.FirstShortcutsSession)) {
completeAction(ActionType.FirstShortcutsSession);
}
};

const onAddClick = () => {
completeFirstSession();
if (!showTopSites) {
toggleShowTopSites();
}
openModal({
type: hubEnabled ? LazyModal.ShortcutsManage : LazyModal.CustomLinks,
});
};

return { shouldShow, onAddClick };
};

export const ExtensionTopBanners = (): ReactElement | null => {
// The extension's top hero row is the only place this card appears
// on the new tab, so we bypass the temporal throttling that the
// webapp homepage uses to limit how often the reminder is shown.
const reminder = useReadingReminderHero({
requireMobile: false,
bypassThrottling: true,
});
const { isLoggedIn, isAuthReady } = useAuthContext();
const { onUpload, shouldShow: shouldShowCv } = useUploadCv();
const { completeAction } = useActions();
const fileInputRef = useRef<HTMLInputElement>(null);
const shortcuts = useShortcutsOnboarding();

// Logged-out users get the dedicated sticky sign-in strip rendered
// higher up in `MainFeedPage`. This component is logged-in cards only.
if (!isAuthReady || !isLoggedIn) {
return null;
}

const cards: ReactElement[] = [];

if (reminder.shouldShow) {
cards.push(
<TopHero
key="reminder"
subtitle="Turn on your daily reading reminder and never miss a learning day."
illustration={<CompactReminderCat />}
onCtaClick={() => {
reminder.onEnable();
}}
onClose={() => {
reminder.onDismiss();
}}
/>,
);
}

if (shouldShowCv) {
cards.push(
<TopHero
key="cv"
subtitle="Upload your CV and let your next job quietly come to you."
ctaLabel="Upload CV"
illustration={<CvIllustration />}
onCtaClick={() => fileInputRef.current?.click()}
onClose={() => completeAction(ActionType.ClosedProfileBanner)}
/>,
);
}

if (shortcuts.shouldShow) {
cards.push(
<TopHero
key="shortcuts"
subtitle="Pin the sites you visit most, right from your new tab."
ctaLabel="Add shortcuts"
illustration={<ShortcutsIllustration />}
onCtaClick={shortcuts.onAddClick}
/>,
);
}

if (cards.length === 0) {
return null;
}

return (
<>
<input
ref={fileInputRef}
type="file"
accept={fileValidation.acceptedExtensions
.map((ext) => `.${ext}`)
.join(',')}
className="hidden"
onChange={(event) => {
const file = event.target.files?.[0];
if (!file) {
return;
}
onUpload(file);
// Allow the user to re-pick the same file after an error.
// eslint-disable-next-line no-param-reassign
event.target.value = '';
}}
/>
<div
className={classNames(
'mx-4 mb-3 grid grid-cols-1 gap-3 laptop:mx-0',
cards.length === 2 && 'tablet:grid-cols-2',
cards.length === 3 && 'tablet:grid-cols-2 laptop:grid-cols-3',
)}
>
{cards}
</div>
</>
);
};
Loading
Loading