mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 22:35:24 +00:00
02dc5fc153
## Description: Hash the pattern + is for sale and store it in local storage. then check if the new cosmetics hash is different from the one we last saw. If it's different, add a glowing red dot on the store. After user clicks it, then update the hash and remove the dot. <img width="1030" height="143" alt="Screenshot 2026-01-29 at 3 54 46 PM" src="https://github.com/user-attachments/assets/e5727764-40e4-45e1-b651-65816e657067" /> ## 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
124 lines
3.3 KiB
TypeScript
124 lines
3.3 KiB
TypeScript
import { UserMeResponse } from "../core/ApiSchemas";
|
|
import {
|
|
ColorPalette,
|
|
Cosmetics,
|
|
CosmeticsSchema,
|
|
Pattern,
|
|
} from "../core/CosmeticSchemas";
|
|
import { createCheckoutSession, getApiBase } from "./Api";
|
|
|
|
export async function handlePurchase(
|
|
pattern: Pattern,
|
|
colorPalette: ColorPalette | null,
|
|
) {
|
|
if (pattern.product === null) {
|
|
alert("This pattern is not available for purchase.");
|
|
return;
|
|
}
|
|
|
|
const url = await createCheckoutSession(
|
|
pattern.product.priceId,
|
|
colorPalette?.name ?? null,
|
|
);
|
|
if (url === false) {
|
|
alert("Failed to create checkout session.");
|
|
return;
|
|
}
|
|
|
|
// Redirect to Stripe checkout
|
|
window.location.href = url;
|
|
}
|
|
|
|
let __cosmetics: Promise<Cosmetics | null> | null = null;
|
|
let __cosmeticsHash: string | null = null;
|
|
|
|
function simpleHash(str: string): string {
|
|
let hash = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
const char = str.charCodeAt(i);
|
|
hash = (hash << 5) - hash + char;
|
|
hash = hash & hash;
|
|
}
|
|
return hash.toString(36);
|
|
}
|
|
|
|
export async function fetchCosmetics(): Promise<Cosmetics | null> {
|
|
if (__cosmetics !== null) {
|
|
return __cosmetics;
|
|
}
|
|
__cosmetics = (async () => {
|
|
try {
|
|
const response = await fetch(`${getApiBase()}/cosmetics.json`);
|
|
if (!response.ok) {
|
|
console.error(`HTTP error! status: ${response.status}`);
|
|
return null;
|
|
}
|
|
const result = CosmeticsSchema.safeParse(await response.json());
|
|
if (!result.success) {
|
|
console.error(`Invalid cosmetics: ${result.error.message}`);
|
|
return null;
|
|
}
|
|
const patternKeys = Object.keys(result.data.patterns).sort();
|
|
const hashInput = patternKeys
|
|
.map((k) => k + (result.data.patterns[k].product ? "sale" : ""))
|
|
.join(",");
|
|
__cosmeticsHash = simpleHash(hashInput);
|
|
return result.data;
|
|
} catch (error) {
|
|
console.error("Error getting cosmetics:", error);
|
|
return null;
|
|
}
|
|
})();
|
|
return __cosmetics;
|
|
}
|
|
|
|
export async function getCosmeticsHash(): Promise<string | null> {
|
|
await fetchCosmetics();
|
|
return __cosmeticsHash;
|
|
}
|
|
|
|
export function patternRelationship(
|
|
pattern: Pattern,
|
|
colorPalette: { name: string; isArchived?: boolean } | null,
|
|
userMeResponse: UserMeResponse | false,
|
|
affiliateCode: string | null,
|
|
): "owned" | "purchasable" | "blocked" {
|
|
const flares =
|
|
userMeResponse === false ? [] : (userMeResponse.player.flares ?? []);
|
|
if (flares.includes("pattern:*")) {
|
|
return "owned";
|
|
}
|
|
|
|
if (colorPalette === null) {
|
|
// For backwards compatibility only show non-colored patterns if they are owned.
|
|
if (flares.includes(`pattern:${pattern.name}`)) {
|
|
return "owned";
|
|
}
|
|
return "blocked";
|
|
}
|
|
|
|
const requiredFlare = `pattern:${pattern.name}:${colorPalette.name}`;
|
|
|
|
if (flares.includes(requiredFlare)) {
|
|
return "owned";
|
|
}
|
|
|
|
if (pattern.product === null) {
|
|
// We don't own it and it's not for sale, so don't show it.
|
|
return "blocked";
|
|
}
|
|
|
|
if (colorPalette?.isArchived) {
|
|
// We don't own the color palette, and it's archived, so don't show it.
|
|
return "blocked";
|
|
}
|
|
|
|
if (affiliateCode !== pattern.affiliateCode) {
|
|
// Pattern is for sale, but it's not the right store to show it on.
|
|
return "blocked";
|
|
}
|
|
|
|
// Patterns is for sale, and it's the right store to show it on.
|
|
return "purchasable";
|
|
}
|