Skip to content

Commit 783031d

Browse files
committed
feat: enhance Discord connection flow with mobile support
1 parent 17f537a commit 783031d

3 files changed

Lines changed: 60 additions & 3 deletions

File tree

app/student/discord/setup/page.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Button } from "@betterinternship/components";
99
import { Card } from "@/components/ui/card";
1010
import { Loader } from "@/components/ui/loader";
1111
import { DiscordService } from "@/lib/api/discord.api";
12+
import { toDiscordAppAuthorizationUrl } from "@/lib/discord/mobile-oauth";
1213
import { JobService, UserService } from "@/lib/api/services";
1314
import { useAuthContext } from "@/lib/ctx-auth";
1415
import useModalRegistry from "@/components/modals/modal-registry";
@@ -92,8 +93,33 @@ function DiscordSetupContent() {
9293
void apply();
9394
}, [resumeReady, status.data?.linked]);
9495

95-
const connect = () => {
96-
window.location.assign(DiscordService.authorizationUrl(jobId));
96+
const connect = async () => {
97+
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
98+
if (!isMobile) {
99+
window.location.assign(DiscordService.authorizationUrl(jobId));
100+
return;
101+
}
102+
103+
try {
104+
const result = await DiscordService.getMobileAuthorizationUrl(jobId);
105+
if (!result.success || !result.authorizationUrl) {
106+
throw new Error(result.message || "Could not start Discord connection.");
107+
}
108+
109+
const webAuthorizationUrl = result.authorizationUrl;
110+
const fallback = window.setTimeout(() => {
111+
window.location.assign(webAuthorizationUrl);
112+
}, 1500);
113+
114+
window.addEventListener("pagehide", () => window.clearTimeout(fallback), {
115+
once: true,
116+
});
117+
window.location.assign(toDiscordAppAuthorizationUrl(webAuthorizationUrl));
118+
} catch {
119+
// The regular OAuth page is the supported fallback when the app cannot
120+
// be opened or a device blocks custom URL schemes.
121+
window.location.assign(DiscordService.authorizationUrl(jobId));
122+
}
97123
};
98124

99125
const setDefaultResume = async (resumeId: string) => {
@@ -197,7 +223,7 @@ function DiscordSetupContent() {
197223
)}
198224

199225
{!status.data?.linked && resumeReady && (
200-
<Button className="w-full" onClick={connect}>
226+
<Button className="w-full" onClick={() => void connect()}>
201227
Connect Discord and Apply
202228
</Button>
203229
)}

lib/api/discord.api.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export type DiscordLinkStatusResponse = FetchResponse & {
55
linked?: boolean;
66
};
77

8+
export type DiscordAuthorizationUrlResponse = FetchResponse & {
9+
authorizationUrl?: string;
10+
};
11+
812
export const DiscordService = {
913
getLinkStatus() {
1014
return APIClient.get<DiscordLinkStatusResponse>(
@@ -31,4 +35,13 @@ export const DiscordService = {
3135
.p({ job_id: jobId })
3236
.build();
3337
},
38+
39+
getMobileAuthorizationUrl(jobId?: string) {
40+
return APIClient.get<DiscordAuthorizationUrlResponse>(
41+
APIRouteBuilder("integrations")
42+
.r("discord", "oauth", "start")
43+
.p({ job_id: jobId, mobile: true })
44+
.build(),
45+
);
46+
},
3447
};

lib/discord/mobile-oauth.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Converts Discord's browser OAuth URL into the desktop/mobile client scheme.
3+
* The URL is produced by our API, which is also where the signed OAuth state is
4+
* created; this function only changes the launch target.
5+
*/
6+
export const toDiscordAppAuthorizationUrl = (authorizationUrl: string) => {
7+
const url = new URL(authorizationUrl);
8+
9+
if (
10+
url.protocol !== "https:" ||
11+
url.hostname !== "discord.com" ||
12+
url.pathname !== "/oauth2/authorize"
13+
) {
14+
throw new Error("Invalid Discord authorization URL.");
15+
}
16+
17+
return `discord://-${url.pathname}${url.search}${url.hash}`;
18+
};

0 commit comments

Comments
 (0)