feat: mobile auth flow

This commit is contained in:
Arik Chakma
2025-09-23 22:57:51 +06:00
parent c3e3d52832
commit 362281427d
6 changed files with 67 additions and 7 deletions

View File

@@ -9,4 +9,6 @@ PUBLIC_STRIPE_INDIVIDUAL_YEARLY_PRICE_ID=
PUBLIC_STRIPE_INDIVIDUAL_MONTHLY_PRICE_AMOUNT=10
PUBLIC_STRIPE_INDIVIDUAL_YEARLY_PRICE_AMOUNT=100
ROADMAP_API_KEY=
ROADMAP_API_KEY=
PUBLIC_MOBILE_APP_SCHEMA=mobileroadmap://

View File

@@ -9,7 +9,12 @@ import { cn } from '../../lib/classname.ts';
import { httpGet } from '../../lib/http';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
import { getLastPath, triggerUtmRegistration, urlToId } from '../../lib/browser.ts';
import {
getLastPath,
triggerUtmRegistration,
urlToId,
} from '../../lib/browser.ts';
import { getPlatformFromState, redirectToMobileApp } from '../../lib/auth.ts';
type GitHubButtonProps = {
isDisabled?: boolean;
@@ -38,6 +43,13 @@ export function GitHubButton(props: GitHubButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
const platform = getPlatformFromState(state);
if (platform === 'mobile') {
redirectToMobileApp(urlParams);
return;
}
const lastPageBeforeGithub = localStorage.getItem(GITHUB_LAST_PAGE);
httpGet<{ token: string; isNewUser: boolean }>(
@@ -162,7 +174,7 @@ export function GitHubButton(props: GitHubButtonProps) {
Continue with GitHub
</button>
{error && (
<p className="mb-2 mt-1 text-sm font-medium text-red-600">{error}</p>
<p className="mt-1 mb-2 text-sm font-medium text-red-600">{error}</p>
)}
</>
);

View File

@@ -5,8 +5,13 @@ import { COURSE_PURCHASE_PARAM } from '../../lib/jwt';
import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
import { triggerUtmRegistration, urlToId, getLastPath } from '../../lib/browser.ts';
import {
triggerUtmRegistration,
urlToId,
getLastPath,
} from '../../lib/browser.ts';
import { cn } from '../../lib/classname.ts';
import { getPlatformFromState, redirectToMobileApp } from '../../lib/auth.ts';
type GoogleButtonProps = {
isDisabled?: boolean;
@@ -35,6 +40,13 @@ export function GoogleButton(props: GoogleButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
const platform = getPlatformFromState(state);
if (platform === 'mobile') {
redirectToMobileApp(urlParams);
return;
}
const lastPageBeforeGoogle = localStorage.getItem(GOOGLE_LAST_PAGE);
httpGet<{ token: string; isNewUser: boolean }>(
@@ -161,7 +173,7 @@ export function GoogleButton(props: GoogleButtonProps) {
Continue with Google
</button>
{error && (
<p className="mb-2 mt-1 text-sm font-medium text-red-600">{error}</p>
<p className="mt-1 mb-2 text-sm font-medium text-red-600">{error}</p>
)}
</>
);

View File

@@ -9,7 +9,12 @@ import { httpGet } from '../../lib/http';
import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
import { getLastPath, triggerUtmRegistration, urlToId } from '../../lib/browser.ts';
import {
getLastPath,
triggerUtmRegistration,
urlToId,
} from '../../lib/browser.ts';
import { getPlatformFromState, redirectToMobileApp } from '../../lib/auth.ts';
type LinkedInButtonProps = {
isDisabled?: boolean;
@@ -38,6 +43,13 @@ export function LinkedInButton(props: LinkedInButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
const platform = getPlatformFromState(state);
if (platform === 'mobile') {
redirectToMobileApp(urlParams);
return;
}
const lastPageBeforeLinkedIn = localStorage.getItem(LINKEDIN_LAST_PAGE);
httpGet<{ token: string; isNewUser: boolean }>(
@@ -166,7 +178,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
Continue with LinkedIn
</button>
{error && (
<p className="mb-2 mt-1 text-sm font-medium text-red-600">{error}</p>
<p className="mt-1 mb-2 text-sm font-medium text-red-600">{error}</p>
)}
</>
);

1
src/env.d.ts vendored
View File

@@ -8,6 +8,7 @@ interface ImportMetaEnv {
PUBLIC_AVATAR_BASE_URL: string;
PUBLIC_EDITOR_APP_URL: string;
PUBLIC_COURSE_APP_URL: string;
PUBLIC_MOBILE_APP_SCHEMA: string;
}
interface ImportMeta {

View File

@@ -9,3 +9,24 @@ export function logout() {
// Reloading will automatically redirect the user if required
window.location.href = '/';
}
export type AllowedPlatform = 'mobile' | 'web';
export function getPlatformFromState(state: string): AllowedPlatform {
const [platform, _] = state.split(':');
if (platform === 'mobile') {
return 'mobile';
}
return 'web';
}
export function redirectToMobileApp(searchParams: URLSearchParams) {
const state = searchParams.get('state');
if (state) {
searchParams.set('state', state.replace('mobile:', ''));
}
const deepLinkUrl = `${import.meta.env.PUBLIC_MOBILE_APP_SCHEMA}?${String(searchParams)}`;
window.location.href = deepLinkUrl;
}