mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-12 17:51:53 +08:00
feat: mobile auth flow
This commit is contained in:
@@ -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://
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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
1
src/env.d.ts
vendored
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user