Compare commits
	
		
			2 Commits
		
	
	
		
			achalmers/
			...
			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
	