mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
feat(landing): signed-out cloud workspaces sign-in card (V3-08)
In builds that offer Orca Cloud, when signed out (never, or the session ended) with projects and no cloud list, Home shows a quiet 'Cloud workspaces · Sign in to see your team's cloud workspaces' card where the list would be. It stands in for the trial card while signed out. The base landing (wordmark, no-project Home) is unchanged pending FR1. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { LandingCloudCard } from './landing/LandingCloudCard'
|
||||
import { LandingCloudSignInCard, useSignedOutOfOrcaCloud } from './landing/LandingCloudSignInCard'
|
||||
import {
|
||||
CloudWorkspacesHome,
|
||||
useCloudWorkspacesPresent
|
||||
@@ -225,6 +226,10 @@ export default function Landing(): React.JSX.Element {
|
||||
const cloudListPresent = useCloudWorkspacesPresent()
|
||||
const repos = useAppStore((s) => s.repos)
|
||||
const openModal = useAppStore((s) => s.openModal)
|
||||
const signedOutOfCloud = useSignedOutOfOrcaCloud()
|
||||
const reconnectRequired = useAppStore(
|
||||
(s) => s.orcaProfileAuthStatus?.state === 'reconnect-required'
|
||||
)
|
||||
|
||||
const createActionLabel =
|
||||
repos.length > 0 && repos.every((repo) => isGitRepoKind(repo))
|
||||
@@ -298,7 +303,12 @@ export default function Landing(): React.JSX.Element {
|
||||
onCreate={() => openModal('new-workspace-composer', { telemetrySource: 'unknown' })}
|
||||
/>
|
||||
|
||||
{hasProjects ? <LandingCloudCard cloudWorkspacesPresent={cloudListPresent} /> : null}
|
||||
{/* V3-08: signed out, the team's cloud workspaces can't be listed; say so where they'd be. */}
|
||||
{hasProjects && signedOutOfCloud && !cloudListPresent ? (
|
||||
<LandingCloudSignInCard reconnect={reconnectRequired} />
|
||||
) : hasProjects ? (
|
||||
<LandingCloudCard cloudWorkspacesPresent={cloudListPresent} />
|
||||
) : null}
|
||||
|
||||
{!cloudListPresent && <LandingShortcutList shortcuts={shortcuts} />}
|
||||
</div>
|
||||
|
||||
@@ -65,6 +65,13 @@ export function cloudWorkspacesAvailable(): Promise<boolean> {
|
||||
return availability
|
||||
}
|
||||
|
||||
/** Fixtures only: forget the cached answer so the next probe reads the stubbed bridge. */
|
||||
export function resetCloudWorkspacesAvailabilityForFixtures(): void {
|
||||
availability = null
|
||||
availableValue = null
|
||||
availabilityListeners.forEach((listener) => listener())
|
||||
}
|
||||
|
||||
/** null until the first availability answer arrives. */
|
||||
export function useCloudWorkspacesAvailable(): boolean | null {
|
||||
return useSyncExternalStore(
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// V3-08: Home signed out of Orca. Cloud builds with projects show the "Cloud workspaces · Sign in"
|
||||
// card where the list would be; without projects, and in local builds, Home is unchanged.
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { OrcaProfileAuthStatus } from '../../../../shared/orca-profiles'
|
||||
import { defineFixture } from '../../component-fixtures/component-fixture'
|
||||
import { resetCloudWorkspacesAvailabilityForFixtures } from '../cloud-workspaces/use-cloud-workspaces'
|
||||
import { makeRepo } from '../worktree-jump/worktree-jump-palette-test-fixtures'
|
||||
import Landing from '../Landing'
|
||||
|
||||
const repo: Repo = { ...makeRepo(), displayName: 'orca' }
|
||||
const auth = (
|
||||
configured: boolean,
|
||||
state: OrcaProfileAuthStatus['state']
|
||||
): OrcaProfileAuthStatus => ({ activeProfileId: 'default', configured, state, persistence: 'none' })
|
||||
const signedOut = auth(true, 'local')
|
||||
const sessionEnded = auth(true, 'reconnect-required')
|
||||
const localBuild = auth(false, 'unconfigured')
|
||||
|
||||
const build = (cloud: boolean) => (): void => {
|
||||
window.api.cloudWorkspaces = { ...window.api.cloudWorkspaces, available: async () => cloud }
|
||||
resetCloudWorkspacesAvailabilityForFixtures()
|
||||
}
|
||||
// A healthy machine, so the shots show only Home's cloud area.
|
||||
const healthy = {
|
||||
preflightStatus: {
|
||||
git: { installed: true },
|
||||
gh: { installed: true, authenticated: true },
|
||||
glab: { installed: true, authenticated: true }
|
||||
},
|
||||
refreshPreflightStatus: async () => {}
|
||||
}
|
||||
|
||||
const home = (): React.JSX.Element => (
|
||||
<div className="relative" style={{ height: 620 }}>
|
||||
<Landing />
|
||||
</div>
|
||||
)
|
||||
|
||||
/** Cloud build, signed out, has projects: the sign-in card. */
|
||||
export const cloudSignedOutWithProjects = defineFixture({
|
||||
width: 760,
|
||||
setup: build(true),
|
||||
store: { ...healthy, repos: [repo], orcaProfileAuthStatus: signedOut },
|
||||
render: home
|
||||
})
|
||||
|
||||
/** Cloud build, the session ended: same card, "Sign in again". */
|
||||
export const cloudSessionEndedWithProjects = defineFixture({
|
||||
width: 760,
|
||||
setup: build(true),
|
||||
store: { ...healthy, repos: [repo], orcaProfileAuthStatus: sessionEnded },
|
||||
render: home
|
||||
})
|
||||
|
||||
/** Cloud build, signed out, no projects: unchanged (the welcome screen is V3-05, not built). */
|
||||
export const cloudSignedOutNoProjects = defineFixture({
|
||||
width: 760,
|
||||
setup: build(true),
|
||||
store: { ...healthy, repos: [], orcaProfileAuthStatus: signedOut },
|
||||
render: home
|
||||
})
|
||||
|
||||
/** Local build with projects: unchanged, no cloud card. */
|
||||
export const localBuildWithProjects = defineFixture({
|
||||
width: 760,
|
||||
setup: build(false),
|
||||
store: { ...healthy, repos: [repo], orcaProfileAuthStatus: localBuild },
|
||||
render: home
|
||||
})
|
||||
|
||||
/** Local build, no projects: unchanged. */
|
||||
export const localBuildNoProjects = defineFixture({
|
||||
width: 760,
|
||||
setup: build(false),
|
||||
store: { ...healthy, repos: [], orcaProfileAuthStatus: localBuild },
|
||||
render: home
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { OrcaProfileAuthStatus } from '../../../../shared/orca-profiles'
|
||||
import { isSignedOutOfOrcaCloud } from './LandingCloudSignInCard'
|
||||
|
||||
const auth = (
|
||||
configured: boolean,
|
||||
state: OrcaProfileAuthStatus['state']
|
||||
): OrcaProfileAuthStatus => ({ activeProfileId: 'default', configured, state, persistence: 'none' })
|
||||
|
||||
describe('isSignedOutOfOrcaCloud', () => {
|
||||
it('holds in a cloud build when never signed in or the session ended', () => {
|
||||
expect(isSignedOutOfOrcaCloud(true, auth(true, 'local'))).toBe(true)
|
||||
expect(isSignedOutOfOrcaCloud(true, auth(true, 'reconnect-required'))).toBe(true)
|
||||
})
|
||||
|
||||
it('does not hold when signed in', () => {
|
||||
expect(isSignedOutOfOrcaCloud(true, auth(true, 'connected'))).toBe(false)
|
||||
})
|
||||
|
||||
it('does not hold in a local build or before either answer arrives', () => {
|
||||
expect(isSignedOutOfOrcaCloud(false, auth(true, 'local'))).toBe(false)
|
||||
expect(isSignedOutOfOrcaCloud(null, auth(true, 'local'))).toBe(false)
|
||||
expect(isSignedOutOfOrcaCloud(true, auth(false, 'unconfigured'))).toBe(false)
|
||||
expect(isSignedOutOfOrcaCloud(true, null)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
// V3-08: signed out of Orca in a build that offers Orca Cloud, Home says where the team's cloud
|
||||
// workspaces went and how to see them, in the place the cloud list would be. Local builds and
|
||||
// signed-in people never see it. While it shows, it stands in for the trial card (one cloud card).
|
||||
import React from 'react'
|
||||
import { Cloud } from 'lucide-react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useCloudWorkspacesAvailable } from '@/components/cloud-workspaces/use-cloud-workspaces'
|
||||
import type { OrcaProfileAuthStatus } from '../../../../shared/orca-profiles'
|
||||
import { OrcaSignInButton, OrcaSignInWaitNotice } from '../OrcaSignInProgress'
|
||||
|
||||
/** Signed out of an Orca account this build can sign in to: never signed in, or the session ended. */
|
||||
export function isSignedOutOfOrcaCloud(
|
||||
cloudAvailable: boolean | null,
|
||||
status: OrcaProfileAuthStatus | null | undefined
|
||||
): boolean {
|
||||
return (
|
||||
cloudAvailable === true &&
|
||||
status?.configured === true &&
|
||||
(status.state === 'local' || status.state === 'reconnect-required')
|
||||
)
|
||||
}
|
||||
|
||||
export function useSignedOutOfOrcaCloud(): boolean {
|
||||
const available = useCloudWorkspacesAvailable()
|
||||
const status = useAppStore((s) => s.orcaProfileAuthStatus)
|
||||
return isSignedOutOfOrcaCloud(available, status)
|
||||
}
|
||||
|
||||
/** Render only when `useSignedOutOfOrcaCloud()` holds (Landing decides; fixtures render it directly). */
|
||||
export function LandingCloudSignInCard({
|
||||
reconnect
|
||||
}: {
|
||||
/** The session ended (`reconnect-required`), as opposed to never signed in. */
|
||||
reconnect: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<section
|
||||
aria-labelledby="landing-cloud-sign-in-title"
|
||||
data-landing-cloud-sign-in="true"
|
||||
className="w-full rounded-lg border border-border bg-card px-4 py-3 text-left"
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<Cloud className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden />
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 id="landing-cloud-sign-in-title" className="text-sm font-medium text-foreground">
|
||||
{t('components.landing.cloudSignIn.title', 'Cloud workspaces')}
|
||||
</h2>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
||||
{t(
|
||||
'components.landing.cloudSignIn.body',
|
||||
'Sign in to see your team’s cloud workspaces.'
|
||||
)}
|
||||
</p>
|
||||
<div className="mt-3 flex flex-col items-start gap-2">
|
||||
<OrcaSignInButton size="sm" variant="outline">
|
||||
{reconnect
|
||||
? t('components.landing.cloudSignIn.again', 'Sign in again')
|
||||
: t('components.landing.cloudSignIn.action', 'Sign in')}
|
||||
</OrcaSignInButton>
|
||||
<OrcaSignInWaitNotice />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -24311,6 +24311,12 @@
|
||||
"planBody": "Agents keep running while your laptop sleeps.",
|
||||
"trialBody": "Agents keep running while your laptop sleeps. No card needed.",
|
||||
"try": "Try Orca Cloud"
|
||||
},
|
||||
"cloudSignIn": {
|
||||
"title": "Cloud workspaces",
|
||||
"body": "Sign in to see your team’s cloud workspaces.",
|
||||
"again": "Sign in again",
|
||||
"action": "Sign in"
|
||||
}
|
||||
},
|
||||
"cloudWorkspaces": {
|
||||
|
||||
Reference in New Issue
Block a user