Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7757be9
feat: add layout_v2 feature flag and useLayoutVariant hook
rebelchris May 18, 2026
a777e31
refactor: simplify featureLayoutV2 to boolean flag
rebelchris May 18, 2026
0cf1f0e
feat: add v2 dual-sidebar layout behind featureLayoutV2 flag
rebelchris May 18, 2026
3fd9765
feat: pixel-match dual-sidebar v2 layout against designer mockup
rebelchris May 19, 2026
3187bc8
fix(v2): match designer panel — MagicIcon for For You, drop Game Center
rebelchris May 19, 2026
b15c4d4
fix(v2): widen sidebar item horizontal margin to match designer
rebelchris May 19, 2026
508a530
fix(v2): bordered page-header strip and grid inset for feed content
rebelchris May 19, 2026
bb6cf3a
fix(v2): compact ghost buttons + tighter feed/header insets
rebelchris May 19, 2026
c017a29
fix(v2): drop FeedPage 40px padding, restore grid inset, inline compa…
rebelchris May 19, 2026
d1dcb34
fix(v2): tighter header strip + smaller icons, snug card-to-strip gap
rebelchris May 19, 2026
4255f12
refactor(v2): align feed-container layout with designer's list-frame …
rebelchris May 19, 2026
19fefb6
fix(v2): drop feedHeading title in PageHeader to match mock
rebelchris May 19, 2026
8b6c09c
fix(v2): remove duplicate action strip — restyle isSearch branch instead
rebelchris May 19, 2026
8937a0a
fix(v2): tighter text buttons and snug grid top inset
rebelchris May 19, 2026
2f7f4f4
refactor(v2): drop button-style overrides, use Small + Tertiary natively
rebelchris May 19, 2026
7ed6d19
feat(v2): squads directory page-header strip
rebelchris May 19, 2026
ff70c54
fix(v2): NotificationsBell rail variant — match other rail icons
rebelchris May 19, 2026
470ad7e
feat(v2): port designer page-header pattern to remaining pages
rebelchris May 19, 2026
cdf36d9
fix(v2): full-width PageHeader on max-width pages + minimal game-cent…
rebelchris May 19, 2026
05cc552
feat(v2): port PageHeader pattern to bookmarks + settings AccountPage…
rebelchris May 19, 2026
c5b9aa3
fix(v2): unify bookmarks header — title + search + actions in one strip
rebelchris May 19, 2026
4ae42f8
fix(v2): show sidebar on /settings/* so the v2 rail appears
rebelchris May 19, 2026
4e20920
fix(v2): hide ProfileSettingsMenuDesktop — the v2 sidebar panel is th…
rebelchris May 19, 2026
cc79cfb
fix(v2): portal settings PageHeader to span full floating-card width
rebelchris May 19, 2026
831394b
feat(v2): settings/notifications — port FindSquad-style tabs into Pag…
rebelchris May 19, 2026
1b7f6e4
fix: some more changes to old pages
rebelchris May 19, 2026
f1a26b9
refactor(v2): isV2 is now laptop-gated — drop redundant isLaptop checks
rebelchris May 19, 2026
bad8b6a
feat(v2): route-loading progress bar at top of floating card
rebelchris May 19, 2026
87cc321
fix(v2): prettier + Button typecheck — unblock CI build
rebelchris May 19, 2026
c6dbbf3
chore: ping CI
rebelchris May 20, 2026
a510be5
Merge remote-tracking branch 'origin/main' into feat-layout-v2-flag
rebelchris May 20, 2026
8061937
feat(v2): render full Quest panel in Game Center sidebar
rebelchris May 20, 2026
a0990e8
Merge remote-tracking branch 'origin/main' into feat-layout-v2-flag
rebelchris May 21, 2026
8cd169d
fix: add top banner
rebelchris May 21, 2026
15e1ea1
fix: feedback and post page
rebelchris May 21, 2026
f4da259
Merge branch 'main' into feat-layout-v2-flag
rebelchris May 27, 2026
11fd28b
fix: Tsahi feedback round
rebelchris May 27, 2026
d9c20a8
fix: notification item
rebelchris May 27, 2026
28a23ad
feat: small improvements
rebelchris May 27, 2026
fa10be5
Merge remote-tracking branch 'origin/main' into feat-layout-v2-flag
rebelchris May 27, 2026
3015adf
fix: linting
rebelchris May 27, 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
70 changes: 70 additions & 0 deletions packages/extension/src/newtab/ExtensionSignInStrip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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 strip pinned to the top of the new tab for logged-out users so
// the auth CTAs stay reachable while scrolling. Shares the feed's
// primary background so it reads as part of the same surface.
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>
);
};
217 changes: 217 additions & 0 deletions packages/extension/src/newtab/ExtensionTopBanners.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import type { ReactElement } from 'react';
import React, { useRef } from 'react';
import classNames from 'classnames';
import { TopHero } from '@dailydotdev/shared/src/components/marketing/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/useThemedAsset';
import ReadingReminderCatLaptop from '@dailydotdev/shared/src/components/marketing/banners/ReadingReminderCatLaptop';
import {
cloudinaryShortcutsIconsGmail,
cloudinaryShortcutsIconsOpenai,
cloudinaryShortcutsIconsReddit,
uploadCvBgMobile,
} from '@dailydotdev/shared/src/lib/image';

// Bare-illustration frame matched across the three top cards so they
// line up vertically. Slightly wider than tall to give the CV cluster
// horizontal room without cropping.
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"
style={{
backgroundImage: `url(${uploadCvBgMobile})`,
backgroundPosition: 'center top',
backgroundSize: 'auto 220%',
}}
/>
</div>
);

// Compact cat illustration scaled to match the CV / Shortcuts frames so
// the three cards in the row share the same height.
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();
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();

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 evaluate the reminder regardless of viewport
// (the webapp-only `requireMobile` heuristic would hide it on desktop
// new tabs, which is where the extension lives).
const reminder = useReadingReminderHero({ requireMobile: false });
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);
// 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>
</>
);
};
8 changes: 8 additions & 0 deletions packages/extension/src/newtab/MainFeedPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { isFocusActiveAt } from '@dailydotdev/shared/src/features/customizeNewTa
import { normaliseNewTabMode } from '@dailydotdev/shared/src/features/customizeNewTab/lib/newTabMode';
import { DndBanner } from '@dailydotdev/shared/src/components/DndBanner';
import ShortcutLinks from './ShortcutLinks/ShortcutLinks';
import { ExtensionTopBanners } from './ExtensionTopBanners';
import { ExtensionSignInStrip } from './ExtensionSignInStrip';
import { CompanionPopupButton } from '../companion/CompanionPopupButton';
import { useCompanionSettings } from '../companion/useCompanionSettings';
import { getDefaultLink } from './dnd';
Expand Down Expand Up @@ -198,6 +200,12 @@ const MainFeedPageInner = ({
additionalButtons={
!loadingUser && !optOutCompanion && <CompanionPopupButton />
}
topBanner={
<>
<ExtensionSignInStrip />
<ExtensionTopBanners />
</>
}
>
<FeedLayoutProvider>
<MainFeedLayout
Expand Down
Loading
Loading