Files
OpenFrontIO/src/client/Cosmetics.ts
T
evanpelle 35ad6f3abf create account on purchase (#1966)
## Description:

When purchasing an item, user will be logged in as their email
automatically.

* Users can be logged in either via discord or email (the top right
button has an email or discord icon depending on which is logged in
* Created AccountModal to show current login and has option to log in
via Discord or send recovery email
* Created TokenLoginModal which is triggered during account recovery or
after purchase
* Update DiscordUserSchema to 
* Removed choco pattern key listeners, they were causing NPEs when empty
input was provided on forms

<img width="408" height="479" alt="Screenshot 2025-08-29 at 5 35 31 PM"
src="https://github.com/user-attachments/assets/a2be5556-b534-4279-931b-799d8ece122c"
/>
support email or discord identity
<img width="801" height="351" alt="Screenshot 2025-08-29 at 5 38 59 PM"
src="https://github.com/user-attachments/assets/9d18ef8f-a6f8-4c22-b583-c31d9b176467"
/>
<img width="97" height="83" alt="Screenshot 2025-08-29 at 5 39 51 PM"
src="https://github.com/user-attachments/assets/994d7ade-fa02-4adb-a6f8-e929af4089b2"
/>
<img width="102" height="83" alt="Screenshot 2025-08-29 at 5 40 03 PM"
src="https://github.com/user-attachments/assets/f829dd49-996b-479d-9b75-d81092e31da4"
/>
<img width="59" height="43" alt="Screenshot 2025-08-29 at 5 40 19 PM"
src="https://github.com/user-attachments/assets/aacf39e7-2528-463b-95cb-a58bc8c2194b"
/>


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
2025-08-31 19:09:38 -07:00

92 lines
2.7 KiB
TypeScript

import { UserMeResponse } from "../core/ApiSchemas";
import { Cosmetics, CosmeticsSchema, Pattern } from "../core/CosmeticSchemas";
import { getApiBase, getAuthHeader } from "./jwt";
import { getPersistentID } from "./Main";
export async function fetchPatterns(
userMe: UserMeResponse | null,
): Promise<Map<string, Pattern>> {
const cosmetics = await getCosmetics();
if (cosmetics === undefined) {
return new Map();
}
const patterns: Map<string, Pattern> = new Map();
const playerFlares = new Set(userMe?.player?.flares ?? []);
const hasAllPatterns = playerFlares.has("pattern:*");
for (const name in cosmetics.patterns) {
const patternData = cosmetics.patterns[name];
const hasAccess = hasAllPatterns || playerFlares.has(`pattern:${name}`);
if (hasAccess) {
// Remove product info because player already has access.
patternData.product = null;
patterns.set(name, patternData);
} else if (patternData.product !== null) {
// Player doesn't have access, but product is available for purchase.
patterns.set(name, patternData);
}
// If player doesn't have access and product is null, don't show it.
}
return patterns;
}
export async function handlePurchase(pattern: Pattern) {
if (pattern.product === null) {
alert("This pattern is not available for purchase.");
return;
}
const response = await fetch(
`${getApiBase()}/stripe/create-checkout-session`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
authorization: getAuthHeader(),
"X-Persistent-Id": getPersistentID(),
},
body: JSON.stringify({
priceId: pattern.product.priceId,
hostname: window.location.origin,
}),
},
);
if (!response.ok) {
console.error(
`Error purchasing pattern:${response.status} ${response.statusText}`,
);
if (response.status === 401) {
alert("You are not logged in. Please log in to purchase a pattern.");
} else {
alert("Something went wrong. Please try again later.");
}
return;
}
const { url } = await response.json();
// Redirect to Stripe checkout
window.location.href = url;
}
export async function getCosmetics(): Promise<Cosmetics | undefined> {
try {
const response = await fetch(`${getApiBase()}/cosmetics.json`);
if (!response.ok) {
console.error(`HTTP error! status: ${response.status}`);
return;
}
const result = CosmeticsSchema.safeParse(await response.json());
if (!result.success) {
console.error(`Invalid cosmetics: ${result.error.message}`);
return;
}
return result.data;
} catch (error) {
console.error("Error getting cosmetics:", error);
}
}