Compare commits
2 Commits
nadro/adho
...
kurt-web-a
Author | SHA1 | Date | |
---|---|---|---|
c42903d2e8 | |||
d98669fb8a |
14
src/App.tsx
14
src/App.tsx
@ -32,7 +32,12 @@ import {
|
||||
} from '@src/lib/singletons'
|
||||
import { maybeWriteToDisk } from '@src/lib/telemetry'
|
||||
import type { IndexLoaderData } from '@src/lib/types'
|
||||
import { engineStreamActor, useSettings, useToken } from '@src/lib/singletons'
|
||||
import {
|
||||
engineStreamActor,
|
||||
useSettings,
|
||||
useToken,
|
||||
useAuthState,
|
||||
} from '@src/lib/singletons'
|
||||
import { EngineStreamTransition } from '@src/machines/engineStreamMachine'
|
||||
import { BillingTransition } from '@src/machines/billingMachine'
|
||||
import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'
|
||||
@ -90,6 +95,7 @@ export function App() {
|
||||
|
||||
const settings = useSettings()
|
||||
const authToken = useToken()
|
||||
const authState = useAuthState()
|
||||
|
||||
useHotkeys('backspace', (e) => {
|
||||
e.preventDefault()
|
||||
@ -133,6 +139,7 @@ export function App() {
|
||||
settings.app.onboardingStatus.default
|
||||
const needsOnboarded =
|
||||
!isDesktop() &&
|
||||
authState.matches('loggedIn') &&
|
||||
searchParams.size === 0 &&
|
||||
needsToOnboard(location, onboardingStatus)
|
||||
|
||||
@ -152,12 +159,13 @@ export function App() {
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [settings.app.onboardingStatus])
|
||||
}, [settings.app.onboardingStatus, authState])
|
||||
|
||||
useEffect(() => {
|
||||
const needsDownloadAppToast =
|
||||
!isDesktop() &&
|
||||
!isPlaywright() &&
|
||||
authState.matches('loggedIn') &&
|
||||
searchParams.size === 0 &&
|
||||
!settings.app.dismissWebBanner.current
|
||||
if (needsDownloadAppToast) {
|
||||
@ -186,7 +194,7 @@ export function App() {
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
}, [authState])
|
||||
|
||||
useEffect(() => {
|
||||
const needsWasmInitFailedToast = !isDesktop() && kclManager.wasmInitFailed
|
||||
|
@ -14,12 +14,53 @@ import { Themes, getSystemTheme } from '@src/lib/theme'
|
||||
import { reportRejection } from '@src/lib/trap'
|
||||
import { toSync } from '@src/lib/utils'
|
||||
import { authActor, useSettings } from '@src/lib/singletons'
|
||||
import { APP_VERSION, generateSignInUrl } from '@src/routes/utils'
|
||||
import { APP_VERSION } from '@src/routes/utils'
|
||||
|
||||
const subtleBorder =
|
||||
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
|
||||
const cardArea = `${subtleBorder} rounded-lg px-6 py-3 text-chalkboard-70 dark:text-chalkboard-30`
|
||||
|
||||
// OAuth provider types - matching the API types
|
||||
type AccountProvider = 'github' | 'google' | 'apple' | 'microsoft' | 'discord'
|
||||
|
||||
// OAuth client info type to match API response
|
||||
interface OAuth2ClientInfo {
|
||||
url?: string
|
||||
}
|
||||
|
||||
async function handleOAuthSignin(
|
||||
provider: AccountProvider,
|
||||
callback_url: string
|
||||
) {
|
||||
try {
|
||||
const endpoint =
|
||||
VITE_KC_API_BASE_URL +
|
||||
'/oauth2/provider/' +
|
||||
provider +
|
||||
'/consent?callback_url=' +
|
||||
encodeURIComponent(callback_url)
|
||||
|
||||
// This will get our auth URL and state.
|
||||
const resp = await fetch(endpoint, {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
if (!resp.ok) {
|
||||
toast.error('Login is unavailable.')
|
||||
return
|
||||
}
|
||||
|
||||
const info: OAuth2ClientInfo = await resp.json()
|
||||
|
||||
// If there is a url, redirect to it.
|
||||
if (info && info.url && info.url.length > 0) {
|
||||
window.location.href = info.url
|
||||
}
|
||||
} catch {
|
||||
toast.error('Login is unavailable.')
|
||||
}
|
||||
}
|
||||
|
||||
const SignIn = () => {
|
||||
// Only create the native file menus on desktop
|
||||
if (isDesktop()) {
|
||||
@ -35,9 +76,14 @@ const SignIn = () => {
|
||||
const {
|
||||
app: { theme },
|
||||
} = useSettings()
|
||||
const signInUrl = generateSignInUrl()
|
||||
const kclSampleUrl = `${VITE_KC_SITE_BASE_URL}/docs/kcl-samples/car-wheel-assembly`
|
||||
|
||||
// OAuth callback URL for webapp
|
||||
const webappCallbackUrl =
|
||||
typeof window !== 'undefined'
|
||||
? window.location.href.replace('signin', '')
|
||||
: ''
|
||||
|
||||
const getThemeText = useCallback(
|
||||
(shouldContrast = true) =>
|
||||
theme.current === Themes.Light ||
|
||||
@ -55,7 +101,7 @@ const SignIn = () => {
|
||||
// We want to invoke our command to login via device auth.
|
||||
const userCodeToDisplay = await window.electron
|
||||
.startDeviceFlow(VITE_KC_API_BASE_URL + location.search)
|
||||
.catch(reportError)
|
||||
.catch(reportRejection)
|
||||
if (!userCodeToDisplay) {
|
||||
console.error('No user code received while trying to log in')
|
||||
toast.error('Error while trying to log in')
|
||||
@ -64,7 +110,9 @@ const SignIn = () => {
|
||||
setUserCode(userCodeToDisplay)
|
||||
|
||||
// Now that we have the user code, we can kick off the final login step.
|
||||
const token = await window.electron.loginWithDeviceFlow().catch(reportError)
|
||||
const token = await window.electron
|
||||
.loginWithDeviceFlow()
|
||||
.catch(reportRejection)
|
||||
if (!token) {
|
||||
console.error('No token received while trying to log in')
|
||||
toast.error('Error while trying to log in')
|
||||
@ -78,6 +126,16 @@ const SignIn = () => {
|
||||
setUserCode('')
|
||||
}
|
||||
|
||||
const handleOAuthClick = (provider: AccountProvider) => {
|
||||
return (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.preventDefault()
|
||||
handleOAuthSignin(provider, webappCallbackUrl).catch(reportRejection)
|
||||
}
|
||||
}
|
||||
|
||||
// OAuth button styling
|
||||
const oauthButtonClasses = `w-full flex items-center justify-center gap-3 px-4 py-3 rounded-lg border ${subtleBorder} bg-chalkboard-10 dark:bg-chalkboard-90 hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 text-chalkboard-90 dark:text-chalkboard-10 transition-colors`
|
||||
|
||||
return (
|
||||
<main
|
||||
className="bg-primary h-screen grid place-items-stretch m-0 p-2"
|
||||
@ -167,24 +225,63 @@ const SignIn = () => {
|
||||
) : (
|
||||
<>
|
||||
<div className="flex md:hidden flex-col gap-2">
|
||||
<p className="text-base text-primary">
|
||||
<p className="text-base text-primary mb-4">
|
||||
This app is really best used on a desktop. We're working on
|
||||
simple touch controls for mobile, but in the meantime please
|
||||
visit using a larger device.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
onClick={openExternalBrowserIfDesktop(signInUrl)}
|
||||
to={signInUrl}
|
||||
className={
|
||||
'w-fit m-0 mt-8 hidden md:flex gap-4 items-center px-3 py-1 ' +
|
||||
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
|
||||
}
|
||||
data-testid="sign-in-button"
|
||||
>
|
||||
Sign in to get started
|
||||
<CustomIcon name="arrowRight" className="w-6 h-6" />
|
||||
</Link>
|
||||
<div className="hidden md:block mt-8">
|
||||
<h2 className="text-xl mb-4 text-chalkboard-90 dark:text-chalkboard-10">
|
||||
Sign in to get started
|
||||
</h2>
|
||||
<p className="text-sm mb-6 text-chalkboard-70 dark:text-chalkboard-30">
|
||||
No password setup necessary. When you sign into Zoo for the
|
||||
first time we create your account automatically.
|
||||
</p>
|
||||
<div className="flex flex-col gap-3 max-w-sm">
|
||||
<button
|
||||
onClick={handleOAuthClick('github')}
|
||||
className={oauthButtonClasses}
|
||||
data-testid="github-signin-button"
|
||||
>
|
||||
<CustomIcon name="code" className="w-5 h-5" />
|
||||
<span>Continue with GitHub</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleOAuthClick('google')}
|
||||
className={oauthButtonClasses}
|
||||
data-testid="google-signin-button"
|
||||
>
|
||||
<CustomIcon name="search" className="w-5 h-5" />
|
||||
<span>Continue with Google</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleOAuthClick('apple')}
|
||||
className={oauthButtonClasses}
|
||||
data-testid="apple-signin-button"
|
||||
>
|
||||
<CustomIcon name="star" className="w-5 h-5" />
|
||||
<span>Continue with Apple</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleOAuthClick('microsoft')}
|
||||
className={oauthButtonClasses}
|
||||
data-testid="microsoft-signin-button"
|
||||
>
|
||||
<CustomIcon name="settings" className="w-5 h-5" />
|
||||
<span>Continue with Microsoft</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={handleOAuthClick('discord')}
|
||||
className={oauthButtonClasses}
|
||||
data-testid="discord-signin-button"
|
||||
>
|
||||
<CustomIcon name="chat" className="w-5 h-5" />
|
||||
<span>Continue with Discord</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user