Skip to content

Commit aea14a6

Browse files
committed
auth
1 parent 2b606f2 commit aea14a6

File tree

15 files changed

+500
-502
lines changed

15 files changed

+500
-502
lines changed

resources/lang/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@
704704
"colors": "Colors",
705705
"purchase": "Purchase",
706706
"show_only_owned": "My Skins",
707+
"not_logged_in": "Not logged in",
707708
"blocked": {
708709
"login": "You must be logged in to access this skin.",
709710
"purchase": "Purchase this skin to unlock it."

src/client/AccountModal.ts

Lines changed: 78 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,14 @@ import {
55
PlayerStatsTree,
66
UserMeResponse,
77
} from "../core/ApiSchemas";
8+
import { fetchPlayerById, getUserMe } from "./Api";
9+
import { discordLogin, logOut, sendMagicLink } from "./Auth";
810
import "./components/baseComponents/stats/DiscordUserHeader";
911
import "./components/baseComponents/stats/GameList";
1012
import "./components/baseComponents/stats/PlayerStatsTable";
1113
import "./components/baseComponents/stats/PlayerStatsTree";
1214
import "./components/Difficulties";
1315
import "./components/PatternButton";
14-
import {
15-
discordLogin,
16-
fetchPlayerById,
17-
getApiBase,
18-
getUserMe,
19-
logOut,
20-
} from "./jwt";
2116
import { isInIframe, translateText } from "./Utils";
2217

2318
@customElement("account-modal")
@@ -30,10 +25,7 @@ export class AccountModal extends LitElement {
3025
@state() private email: string = "";
3126
@state() private isLoadingUser: boolean = false;
3227

33-
private loggedInEmail: string | null = null;
34-
private loggedInDiscord: string | null = null;
3528
private userMeResponse: UserMeResponse | null = null;
36-
private playerId: string | null = null;
3729
private statsTree: PlayerStatsTree | null = null;
3830
private recentGames: PlayerGame[] = [];
3931

@@ -44,8 +36,7 @@ export class AccountModal extends LitElement {
4436
const customEvent = event as CustomEvent;
4537
if (customEvent.detail) {
4638
this.userMeResponse = customEvent.detail as UserMeResponse;
47-
this.playerId = this.userMeResponse?.player?.publicId;
48-
if (this.playerId === undefined) {
39+
if (this.userMeResponse?.player?.publicId === undefined) {
4940
this.statsTree = null;
5041
this.recentGames = [];
5142
}
@@ -67,82 +58,90 @@ export class AccountModal extends LitElement {
6758
id="account-modal"
6859
title="${translateText("account_modal.title") || "Account"}"
6960
>
70-
${this.renderInner()}
61+
${this.isLoadingUser
62+
? html`
63+
<div
64+
class="flex flex-col items-center justify-center p-6 text-white"
65+
>
66+
<p class="mb-2">
67+
${translateText("account_modal.fetching_account")}
68+
</p>
69+
<div
70+
class="w-6 h-6 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"
71+
></div>
72+
</div>
73+
`
74+
: this.renderInner()}
7175
</o-modal>
7276
`;
7377
}
7478

7579
private renderInner() {
76-
if (this.isLoadingUser) {
77-
return html`
78-
<div class="flex flex-col items-center justify-center p-6 text-white">
79-
<p class="mb-2">${translateText("account_modal.fetching_account")}</p>
80-
<div
81-
class="w-6 h-6 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"
82-
></div>
83-
</div>
84-
`;
85-
}
86-
if (this.loggedInDiscord) {
87-
return this.renderLoggedInDiscord();
88-
} else if (this.loggedInEmail) {
89-
return this.renderLoggedInEmail();
80+
if (this.userMeResponse?.user) {
81+
return this.renderAccountInfo();
9082
} else {
9183
return this.renderLoginOptions();
9284
}
9385
}
9486

95-
private viewGame(gameId: string): void {
96-
this.close();
97-
const path = location.pathname;
98-
const { search } = location;
99-
const hash = `#join=${encodeURIComponent(gameId)}`;
100-
const newUrl = `${path}${search}${hash}`;
101-
102-
history.pushState({ join: gameId }, "", newUrl);
103-
window.dispatchEvent(new HashChangeEvent("hashchange"));
104-
}
105-
106-
private renderLoggedInDiscord() {
87+
private renderAccountInfo() {
10788
return html`
10889
<div class="p-6">
109-
<div class="mb-4 text-center">
110-
<p class="text-white mb-4">
111-
Logged in with Discord as ${this.loggedInDiscord}
90+
<div class="mb-4">
91+
<p class="text-white mb-4 text-center">
92+
Player ID: ${this.userMeResponse?.player?.publicId}
11293
</p>
113-
${this.logoutButton()}
94+
</div>
95+
<div class="mb-4 text-center">
96+
<p class="text-white mb-4">${this.renderLoggedInAs()}</p>
11497
</div>
11598
<div class="flex flex-col items-center mt-2 mb-4">
11699
<discord-user-header
117100
.data=${this.userMeResponse?.user?.discord ?? null}
118101
></discord-user-header>
119-
<player-stats-tree-view
120-
.statsTree=${this.statsTree}
121-
></player-stats-tree-view>
122-
<hr class="w-2/3 border-gray-600 my-2" />
123-
<game-list
124-
.games=${this.recentGames}
125-
.onViewGame=${(id: string) => this.viewGame(id)}
126-
></game-list>
127102
</div>
103+
${this.renderPlayerStats()}
128104
</div>
129105
`;
130106
}
131107

132-
private renderLoggedInEmail(): TemplateResult {
108+
private renderLoggedInAs(): TemplateResult {
109+
const me = this.userMeResponse?.user;
110+
if (me?.discord) {
111+
return html`<p>Logged in as ${me.discord.global_name}</p>
112+
${this.renderLogoutButton()}`;
113+
} else if (me?.email) {
114+
return html`<p>Logged in as ${me.email}</p>
115+
${this.renderLogoutButton()}`;
116+
}
117+
return this.renderLoginOptions();
118+
}
119+
120+
private renderPlayerStats(): TemplateResult {
133121
return html`
134-
<div class="p-6">
135-
<div class="mb-4">
136-
<p class="text-white text-center mb-4">
137-
Logged in as ${this.loggedInEmail}
138-
</p>
139-
</div>
140-
${this.logoutButton()}
141-
</div>
122+
<player-stats-tree-view
123+
.statsTree=${this.statsTree}
124+
></player-stats-tree-view>
125+
<hr class="w-2/3 border-gray-600 my-2" />
126+
<game-list
127+
.games=${this.recentGames}
128+
.onViewGame=${(id: string) => this.viewGame(id)}
129+
></game-list>
142130
`;
143131
}
144132

145-
private logoutButton(): TemplateResult {
133+
private viewGame(gameId: string): void {
134+
this.close();
135+
const path = location.pathname;
136+
const { search } = location;
137+
const hash = `#join=${encodeURIComponent(gameId)}`;
138+
const newUrl = `${path}${search}${hash}`;
139+
140+
history.pushState({ join: gameId }, "", newUrl);
141+
window.dispatchEvent(new HashChangeEvent("hashchange"));
142+
}
143+
144+
private renderLogoutButton(): TemplateResult {
146145
return html`
147146
<button
148147
@click="${this.handleLogout}"
@@ -157,10 +156,6 @@ export class AccountModal extends LitElement {
157156
return html`
158157
<div class="p-6">
159158
<div class="mb-6">
160-
<h3 class="text-lg font-medium text-white mb-4 text-center">
161-
Choose your login method
162-
</h3>
163-
164159
<!-- Discord Login Button -->
165160
<div class="mb-6">
166161
<button
@@ -195,7 +190,6 @@ export class AccountModal extends LitElement {
195190
for="email"
196191
class="block text-sm font-medium text-white mb-2"
197192
>
198-
Recover account by email
199193
</label>
200194
<input
201195
type="email"
@@ -225,6 +219,12 @@ export class AccountModal extends LitElement {
225219
</button>
226220
</div>
227221
</div>
222+
<button
223+
@click="${this.handleLogout}"
224+
class="px-3 py-1 text-xs font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors duration-200"
225+
>
226+
Clear
227+
</button>
228228
`;
229229
}
230230

@@ -239,37 +239,15 @@ export class AccountModal extends LitElement {
239239
return;
240240
}
241241

242-
try {
243-
const apiBase = getApiBase();
244-
const response = await fetch(`${apiBase}/magic-link`, {
245-
method: "POST",
246-
headers: {
247-
"Content-Type": "application/json",
248-
},
249-
body: JSON.stringify({
250-
redirectDomain: window.location.origin,
242+
const success = await sendMagicLink(this.email);
243+
if (success) {
244+
alert(
245+
translateText("account_modal.recovery_email_sent", {
251246
email: this.email,
252247
}),
253-
});
254-
255-
if (response.ok) {
256-
alert(
257-
translateText("account_modal.recovery_email_sent", {
258-
email: this.email,
259-
}),
260-
);
261-
this.close();
262-
} else {
263-
console.error(
264-
"Failed to send recovery email:",
265-
response.status,
266-
response.statusText,
267-
);
268-
alert("Failed to send recovery email. Please try again.");
269-
}
270-
} catch (error) {
271-
console.error("Error sending recovery email:", error);
272-
alert("Error sending recovery email. Please try again.");
248+
);
249+
} else {
250+
alert("Failed to send recovery email");
273251
}
274252
}
275253

@@ -284,14 +262,10 @@ export class AccountModal extends LitElement {
284262
void getUserMe()
285263
.then((userMe) => {
286264
if (userMe) {
287-
this.loggedInEmail = userMe.user.email ?? null;
288-
this.loggedInDiscord = userMe.user.discord?.global_name ?? null;
289-
if (this.playerId) {
290-
this.loadFromApi(this.playerId);
265+
this.userMeResponse = userMe;
266+
if (this.userMeResponse?.player?.publicId) {
267+
this.loadPlayerProfile(this.userMeResponse.player.publicId);
291268
}
292-
} else {
293-
this.loggedInEmail = null;
294-
this.loggedInDiscord = null;
295269
}
296270
this.isLoadingUser = false;
297271
this.requestUpdate();
@@ -315,9 +289,9 @@ export class AccountModal extends LitElement {
315289
window.location.reload();
316290
}
317291

318-
private async loadFromApi(playerId: string): Promise<void> {
292+
private async loadPlayerProfile(publicId: string): Promise<void> {
319293
try {
320-
const data = await fetchPlayerById(playerId);
294+
const data = await fetchPlayerById(publicId);
321295
if (!data) {
322296
this.requestUpdate();
323297
return;

0 commit comments

Comments
 (0)