Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function SiteSyncDescription( { children }: PropsWithChildren ) {
return (
<div className="p-8 flex justify-center gap-12">
<div className="flex flex-col max-w-sm">
<div className="a8c-subtitle text-pretty">{ __( 'Sign in to get started' ) }</div>
<div className="a8c-subtitle text-pretty">{ __( 'Log in to get started' ) }</div>
<div className="max-w-[40ch] text-frame-text-secondary a8c-body mt-2">
{ __( 'Connect your WordPress.com account to access your sites.' ) }
</div>
Expand Down
123 changes: 113 additions & 10 deletions apps/ui/src/components/agentic-signin-banner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import { useNavigate } from '@tanstack/react-router';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/ui';
import { useEffect, useRef } from 'react';
import { Button, Tooltip } from '@wordpress/ui';
import { clsx } from 'clsx';
import { useEffect, useRef, useState } from 'react';
import { DotGrid } from '@/components/dot-grid';
import { ProgressiveBlur } from '@/components/progressive-blur';
import { useAgenticFeatures } from '@/data/queries/use-agentic-features';
import { useLogin } from '@/data/queries/use-auth-user';
import { useSidebarCollapsed } from '@/hooks/use-sidebar-collapsed';
import { EmptyBackground } from '@/ui-classic/components/session-view/empty-background';
import styles from './style.module.css';
import type { AgenticFeatureReason } from '@/data/queries/use-agentic-features';

const TAGLINE_INTERVAL_MS = 4000;

// The illustrated, collapsible sign-in band pinned to the bottom of the site overview.
export function AgenticSigninBanner() {
const { enabled, reason } = useAgenticFeatures();
const login = useLogin();
const navigate = useNavigate();
const sidebarCollapsed = useSidebarCollapsed();
const [ minimized, setMinimized ] = useState( false );
const [ taglineIndex, setTaglineIndex ] = useState( 0 );

const taglines = [
__( 'Let Studio code it for you' ),
__( 'Share a preview online' ),
__( 'Push to your live site' ),
__( 'Build a theme or plugin' ),
];
const taglineCount = taglines.length;

// `authenticate()` resolves when the browser opens, not when OAuth
// completes, so a finished login only surfaces here as a signed-out →
Expand All @@ -23,29 +43,112 @@ export function AgenticSigninBanner() {
}
}, [ enabled, reason, navigate ] );

// Rotate the tagline while collapsed.
useEffect( () => {
if ( ! minimized ) {
return;
}
const id = window.setInterval( () => {
setTaglineIndex( ( index ) => ( index + 1 ) % taglineCount );
}, TAGLINE_INTERVAL_MS );
return () => window.clearInterval( id );
}, [ minimized, taglineCount ] );

if ( reason !== 'signed-out' ) {
return null;
}

return <SigninNotice />;
const loginButton = (
<Tooltip.Root>
<Tooltip.Trigger
render={
<Button
type="button"
variant="solid"
tone="brand"
size="small"
loading={ login.isPending }
onClick={ () => login.mutate() }
>
{ minimized ? __( 'Log in' ) : __( 'Log in with WordPress.com' ) }
</Button>
}
/>
<Tooltip.Popup positioner={ <Tooltip.Positioner side="top" /> }>
{ minimized ? __( 'Log in to WordPress.com' ) : __( 'Opens your browser to log in' ) }
</Tooltip.Popup>
</Tooltip.Root>
);

// Hidden: a rotating tagline next to the log-in button, pinned clear of the
// preview toggle and above the footer blur.
if ( minimized ) {
return (
<section
className={ clsx( styles.root, styles.minimized ) }
aria-label={ __( 'Log in to Studio' ) }
>
<span key={ taglineIndex } className={ styles.tagline } aria-hidden="true">
{ taglines[ taglineIndex ] }
</span>
{ loginButton }
</section>
);
}

return (
<section
className={ clsx( styles.root, sidebarCollapsed && styles.collapsed ) }
aria-label={ __( 'Log in to Studio' ) }
>
<DotGrid className={ styles.grid } opacity={ 0.35 } />
<ProgressiveBlur direction="down" fadeToSurface className={ styles.topBlur } />
<div className={ styles.lockup }>
<div className={ styles.mark } aria-hidden="true">
<EmptyBackground />
</div>
<div className={ styles.copy }>
<h2 className={ styles.heading }>{ __( 'Let Studio code it for you' ) }</h2>
<p className={ styles.subline }>
{ __(
'An AI powered WordPress expert that can build a site, theme, or plugin, and help you share and publish.'
) }
</p>
<div className={ styles.actions }>
{ loginButton }
<Button
type="button"
variant="minimal"
tone="neutral"
size="small"
onClick={ () => setMinimized( true ) }
>
{ __( 'Hide' ) }
</Button>
</div>
</div>
</div>
</section>
);
}

// The banner without the route-aware behaviour, for surfaces that must stay
// put once the user signs in (e.g. Settings).
// A compact inline notice (no route-aware redirect) for surfaces that keep the
// prompt in the content flow and must stay put once the user signs in — e.g. the
// Settings Usage panel.
export function SigninNotice() {
const login = useLogin();

return (
<section className={ styles.root } aria-label={ __( 'Sign in to Studio' ) }>
<div className={ styles.text }>
<h2 className={ styles.heading }>{ __( 'Sign in to do more with Studio' ) }</h2>
<ul className={ styles.benefits }>
<section className={ styles.notice } aria-label={ __( 'Log in to Studio' ) }>
<div className={ styles.noticeText }>
<h2 className={ styles.noticeHeading }>{ __( 'Log in to do more with Studio' ) }</h2>
<ul className={ styles.noticeBenefits }>
<li>{ __( 'Chat with a WordPress expert that builds and edits your site for you' ) }</li>
<li>{ __( 'Share your work instantly with preview links' ) }</li>
<li>{ __( "Publish to a real WordPress.com site when you're ready" ) }</li>
</ul>
</div>
<div className={ styles.actions }>
<div className={ styles.noticeActions }>
<Button
type="button"
variant="solid"
Expand Down
Loading
Loading