Add UI skeleton

This commit is contained in:
Christian Fehmer 2023-11-23 12:02:21 +01:00
parent 542dd90404
commit 2b2fedc128
No known key found for this signature in database
GPG key ID: 7294582286D5F1F4
14 changed files with 179 additions and 8 deletions

View file

@ -75,8 +75,8 @@ describe("store controller test", () => {
line_items: [{ price: "price_id", quantity: 1 }],
billing_address_collection: "auto",
success_url:
"http://localhost:3000/payment/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/payment/cancel",
"http://localhost:3000/store?action=success&session_id={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/store?action=cancel",
client_reference_id: uid,
mode: "subscription",
customer_email: "test@example.com",
@ -107,8 +107,8 @@ describe("store controller test", () => {
line_items: [{ price: "price_id", quantity: 1 }],
billing_address_collection: "auto",
success_url:
"http://localhost:3000/payment/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/payment/cancel",
"http://localhost:3000/store?action=success&session_id={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/store?action=cancel",
client_reference_id: uid,
mode: "payment",
customer_creation: "always",
@ -145,8 +145,8 @@ describe("store controller test", () => {
line_items: [{ price: "price_id", quantity: 1 }],
billing_address_collection: "auto",
success_url:
"http://localhost:3000/payment/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/payment/cancel",
"http://localhost:3000/store?action=success&session_id={CHECKOUT_SESSION_ID}",
cancel_url: "http://localhost:3000/store?action=cancel",
client_reference_id: uid,
mode: "subscription",
customer: "cust_1234",

View file

@ -27,8 +27,8 @@ export async function createCheckout(
},
],
billing_address_collection: "auto",
success_url: `${MY_DOMAIN}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${MY_DOMAIN}/payment/cancel`,
success_url: `${MY_DOMAIN}/store?action=success&session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${MY_DOMAIN}/store?action=cancel`,
client_reference_id: uid,
};

View file

@ -8,6 +8,7 @@ import Users from "./users";
import ApeKeys from "./ape-keys";
import Public from "./public";
import Configuration from "./configuration";
import Store from "./store";
export default {
Configs,
@ -20,4 +21,5 @@ export default {
Users,
ApeKeys,
Configuration,
Store,
};

View file

@ -0,0 +1,14 @@
const BASE_PATH = "/store";
export default class Store {
constructor(private httpClient: Ape.HttpClient) {
this.httpClient = httpClient;
}
async createCheckout(item: string): Ape.EndpointResponse {
const payload = {
items: [{ lookupKey: item }],
};
return await this.httpClient.post(`${BASE_PATH}/checkouts`, { payload });
}
}

View file

@ -20,6 +20,7 @@ const Ape = {
publicStats: new endpoints.Public(httpClient),
apeKeys: new endpoints.ApeKeys(httpClient),
configuration: new endpoints.Configuration(httpClient),
store: new endpoints.Store(httpClient),
};
export default Ape;

View file

@ -9,6 +9,7 @@ import * as PageLoading from "../pages/loading";
import * as PageProfile from "../pages/profile";
import * as PageProfileSearch from "../pages/profile-search";
import * as Page404 from "../pages/404";
import * as PageStore from "../pages/store";
import * as PageTransition from "../states/page-transition";
import * as AdController from "../controllers/ad-controller";
import * as Focus from "../test/focus";
@ -53,6 +54,7 @@ export async function change(
login: PageLogin.page,
profile: PageProfile.page,
profileSearch: PageProfileSearch.page,
store: PageStore.page,
404: Page404.page,
};

View file

@ -123,6 +123,12 @@ const routes: Route[] = [
});
},
},
{
path: "/store",
load: (): void => {
PageController.change("store");
},
},
];
export function navigate(

View file

@ -132,6 +132,7 @@ export async function initSnapshot(): Promise<
snap.maxStreak = userData?.streak?.maxLength ?? 0;
snap.filterPresets = userData.resultFilterPresets ?? [];
snap.isPremium = userData?.isPremium;
snap.premium = userData?.premium;
const hourOffset = userData?.streak?.hourOffset;
snap.streakHourOffset =

View file

@ -25,6 +25,7 @@ import "./controllers/input-controller";
import "./ready";
import "./controllers/route-controller";
import "./pages/about";
import "./pages/store";
import "./popups/pb-tables-popup";
import "./elements/scroll-to-top";
import "./popups/mobile-test-config-popup";

View file

@ -0,0 +1,96 @@
import Page from "./page";
import * as Skeleton from "../popups/skeleton";
import { Auth } from "../firebase";
import * as DB from "../db";
import Ape from "../ape";
function reset(): void {
$(".premiumDisabled").removeClass("hidden");
$(".premiumActive").addClass("hidden");
$(".premiumAvailable").addClass("hidden");
}
async function fill(): Promise<void> {
const user = Auth?.currentUser;
if (!user) return;
const data = DB.getSnapshot();
console.log("++++ fill", { user, data });
if (!data) return;
//TODO check backend config for user.premium.enabled
$(".premiumDisabled").addClass("hidden");
const urlParams = new URLSearchParams(window.location.search);
const action = urlParams.get("action");
if (action === "success") {
const sessionId = urlParams.get("session_id");
alert("complete purchase for sessionId " + sessionId);
//TODO: call backend POST /store/checkouts/${sessionId}
//TODO: on success reload user info
//simulate
data.isPremium = true;
data.premium = {
startTimestamp: 1701471599,
expirationTimestamp: 1704149999,
};
} else if (action === "cancel") {
alert("purchase cancelled.");
}
if (data.isPremium === true) {
let premiumEndDate = "";
if (data.premium?.expirationTimestamp) {
if (data.premium?.expirationTimestamp === -1) {
premiumEndDate = "the end of the universe";
$("#premium_sub_cancel").attr("disabled", "disabled");
} else {
premiumEndDate = new Date(
data.premium?.expirationTimestamp * 1000
).toDateString();
}
$("#premium_until").html(premiumEndDate);
}
$(".premiumActive").removeClass("hidden");
} else {
$(".premiumActive").addClass("hidden");
$(".premiumAvailable").removeClass("hidden");
}
}
$(".premium_sub").on("click", async (e) => {
const item = e.currentTarget.getAttribute("data-item") || "";
const response = await Ape.store.createCheckout(item);
if (response.status >= 300) {
alert("request failed: " + response.status + " " + response.message);
return;
}
const redirectUrl = response.data.redirectUrl;
window.location.href = redirectUrl;
});
export const page = new Page(
"store",
$(".page.pageStore"),
"/store",
async () => {
//
},
async () => {
reset();
Skeleton.remove("pageStore");
},
async () => {
Skeleton.append("pageStore", "main");
fill();
},
async () => {
//
}
);
Skeleton.save("pageStore");

View file

@ -14,6 +14,7 @@ declare namespace MonkeyTypes {
| "login"
| "profile"
| "profileSearch"
| "store"
| "404";
type Difficulty = "normal" | "expert" | "master";
@ -608,6 +609,7 @@ declare namespace MonkeyTypes {
streakHourOffset?: number;
lbOptOut?: boolean;
isPremium?: boolean;
premium?: PremiumInfo;
}
interface UserDetails {
@ -919,4 +921,8 @@ declare namespace MonkeyTypes {
histogramDataBucketSize: number;
historyStepSize: number;
}
interface PremiumInfo {
startTimestamp: number;
expirationTimestamp: number;
}
}

View file

@ -67,6 +67,10 @@
<i class="fas fa-fw fa-lock"></i>
<div class="text">Privacy</div>
</a>
<a href="/./store" class="textButton" target="_blank">
<i class="fas fa-fw fa-shopping-cart"></i>
<div class="text">Store</div>
</a>
</div>
<div class="right">
<button

View file

@ -0,0 +1,37 @@
<div class="page pageStore hidden" id="pageStore">
<div class="premiumActive hidden">
<h2>You are a premium user</h2>
<p>
premium active until
<span id="premium_until"></span>
.
</p>
<input type="button" id="premium_sub_cancel" value="Cancel subsciption." />
</div>
<div class="premiumAvailable hidden">
<h2>Subscripe to Prime Ape!</h2>
<p>Get cool features like:</p>
<ul>
<li>More historical test results.</li>
<li>Bragging rights</li>
</ul>
<input
type="button"
class="premium_sub"
data-item="prime_monthly"
value="Subscripe for 5$/month"
/>
<input
type="button"
class="premium_sub"
data-item="prime_yearly"
value="Subscripe for 39$/year"
/>
</div>
<div class="premiumDisabled">
Sorry, premium feature is currently not available.
</div>
</div>

View file

@ -39,6 +39,7 @@
compilation.assets["html/pages/account.html"].source() %> <%=
compilation.assets["html/pages/profile.html"].source() %> <%=
compilation.assets["html/pages/test.html"].source() %> <%=
compilation.assets["html/pages/store.html"].source() %> <%=
compilation.assets["html/pages/404.html"].source() %>
</main>