mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-20 07:16:17 +08:00
Add UI skeleton
This commit is contained in:
parent
542dd90404
commit
2b2fedc128
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
14
frontend/src/ts/ape/endpoints/store.ts
Normal file
14
frontend/src/ts/ape/endpoints/store.ts
Normal 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 });
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -123,6 +123,12 @@ const routes: Route[] = [
|
|||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/store",
|
||||
load: (): void => {
|
||||
PageController.change("store");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function navigate(
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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";
|
||||
|
|
96
frontend/src/ts/pages/store.ts
Normal file
96
frontend/src/ts/pages/store.ts
Normal 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");
|
6
frontend/src/ts/types/types.d.ts
vendored
6
frontend/src/ts/types/types.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
37
frontend/static/html/pages/store.html
Normal file
37
frontend/static/html/pages/store.html
Normal 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>
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue