Skip to content

Commit a51bde1

Browse files
committed
next/store/course: rename type to source, to be later able to distinquish where to route back
1 parent 3a8af73 commit a51bde1

File tree

18 files changed

+169
-123
lines changed

18 files changed

+169
-123
lines changed

src/.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"Bash(pnpm build:*)",
66
"Bash(git add:*)",
77
"Bash(git commit:*)",
8-
"Bash(prettier -w:*)"
8+
"Bash(prettier -w:*)",
9+
"WebFetch(domain:docs.anthropic.com)",
10+
"Bash(pnpm ts-build:*)",
11+
"Bash(find:*)"
912
],
1013
"deny": []
1114
}

src/CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4343

4444
### Development
4545

46-
- After code changes, run `pretter -w [filename]` to ensure consistent styling
46+
- **IMPORTANT**: Always run `prettier -w [filename]` immediately after editing any .ts, .tsx, .md, or .json file to ensure consistent styling
4747
- After TypeScript or `*.tsx` changes, run `pnpm build` in the relevant package directory
4848

4949
## Architecture Overview
@@ -87,6 +87,7 @@ CoCalc is organized as a monorepo with key packages:
8787
- **Database Queries**: Structured query system with typed interfaces
8888
- **Event Emitters**: Inter-service communication within backend
8989
- **REST-like APIs**: Some HTTP endpoints for specific operations
90+
- **API Schema**: API endpoints in `packages/next/pages/api/v2/` use Zod schemas in `packages/next/lib/api/schema/` for validation. These schemas must be kept in harmony with the TypeScript types sent from frontend applications using `apiPost` (in `packages/next/lib/api/post.ts`) or `api` (in `packages/frontend/client/api.ts`). When adding new fields to API requests, both the frontend types and the API schema validation must be updated.
9091

9192
### Key Technologies
9293

src/packages/next/components/store/add-box.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
Add a cash voucher to your shopping cart.
88
*/
99
import { useState, type JSX } from "react";
10+
import { Alert, Button, Spin } from "antd";
11+
1012
import { CostInputPeriod } from "@cocalc/util/licenses/purchase/types";
1113
import { round2up } from "@cocalc/util/misc";
1214
import { money } from "@cocalc/util/licenses/purchase/utils";
13-
import { Alert, Button, Spin } from "antd";
1415
import { addToCart } from "./add-to-cart";
1516
import { DisplayCost } from "./site-license-cost";
1617
import { periodicCost } from "@cocalc/util/licenses/purchase/compute-cost";
1718
import { decimalDivide } from "@cocalc/util/stripe/calc";
18-
import { LicenseType } from "./types";
19+
20+
import type { LicenseSource } from "@cocalc/util/upgrades/shopping";
1921

2022
export const ADD_STYLE = {
2123
display: "inline-block",
@@ -38,7 +40,7 @@ interface Props {
3840
dedicatedItem?: boolean;
3941
disabled?: boolean;
4042
noAccount: boolean;
41-
type: LicenseType;
43+
source: LicenseSource;
4244
}
4345

4446
export function AddBox({
@@ -50,7 +52,7 @@ export function AddBox({
5052
dedicatedItem = false,
5153
noAccount,
5254
disabled = false,
53-
type,
55+
source,
5456
}: Props) {
5557
if (cost?.input.type == "cash-voucher") {
5658
return null;
@@ -80,7 +82,7 @@ export function AddBox({
8082
message={
8183
<>
8284
{money(round2up(costPer))}{" "}
83-
<b>per {type === "course" ? "student" : "project"}</b>{" "}
85+
<b>per {source === "course" ? "student" : "project"}</b>{" "}
8486
{!!cost.period && cost.period != "range" ? cost.period : ""}
8587
</>
8688
}

src/packages/next/components/store/cart.tsx

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,27 @@ shopping cart experience, so most likely to feel familiar to users and easy
1111
to use.
1212
*/
1313

14+
import { Alert, Button, Checkbox, Popconfirm, Table } from "antd";
15+
import { useRouter } from "next/router";
16+
import { useEffect, useMemo, useState, type JSX } from "react";
17+
1418
import { Icon } from "@cocalc/frontend/components/icon";
19+
import type {
20+
ProductDescription,
21+
ProductType,
22+
} from "@cocalc/util/db-schema/shopping-cart-items";
1523
import { describeQuotaFromInfo } from "@cocalc/util/licenses/describe-quota";
1624
import { CostInputPeriod } from "@cocalc/util/licenses/purchase/types";
25+
import { computeCost } from "@cocalc/util/licenses/store/compute-cost";
1726
import { capitalize, isValidUUID } from "@cocalc/util/misc";
18-
import { Alert, Button, Checkbox, Popconfirm, Table } from "antd";
1927
import A from "components/misc/A";
2028
import Loading from "components/share/loading";
2129
import SiteName from "components/share/site-name";
2230
import apiPost from "lib/api/post";
2331
import useAPI from "lib/hooks/api";
2432
import useIsMounted from "lib/hooks/mounted";
25-
import { useRouter } from "next/router";
26-
import { useEffect, useMemo, useState, type JSX } from "react";
27-
import { computeCost } from "@cocalc/util/licenses/store/compute-cost";
2833
import OtherItems from "./other-items";
2934
import { describeItem, describePeriod, DisplayCost } from "./site-license-cost";
30-
import type {
31-
ProductDescription,
32-
ProductType,
33-
} from "@cocalc/util/db-schema/shopping-cart-items";
3435

3536
export default function ShoppingCart() {
3637
const isMounted = useIsMounted();
@@ -353,9 +354,9 @@ export function DescriptionColumn(props: DCProps) {
353354
const router = useRouter();
354355
const { id, description, style, readOnly } = props;
355356
if (
356-
description.type == "disk" ||
357-
description.type == "vm" ||
358-
description.type == "quota"
357+
description.type === "disk" ||
358+
description.type === "vm" ||
359+
description.type === "quota"
359360
) {
360361
return <DescriptionColumnSiteLicense {...props} />;
361362
} else if (description.type == "cash-voucher") {
@@ -390,9 +391,9 @@ function DescriptionColumnSiteLicense(props: DCProps) {
390391
const { id, cost, description, compact, project_id, readOnly } = props;
391392
if (
392393
!(
393-
description.type == "disk" ||
394-
description.type == "vm" ||
395-
description.type == "quota"
394+
description.type === "disk" ||
395+
description.type === "vm" ||
396+
description.type === "quota"
396397
)
397398
) {
398399
throw Error("BUG -- incorrect typing");
@@ -403,7 +404,7 @@ function DescriptionColumnSiteLicense(props: DCProps) {
403404
return <pre>{JSON.stringify(description, undefined, 2)}</pre>;
404405
}
405406
const { input } = cost;
406-
if (input.type == "cash-voucher") {
407+
if (input.type === "cash-voucher") {
407408
throw Error("incorrect typing");
408409
}
409410

@@ -423,7 +424,7 @@ function DescriptionColumnSiteLicense(props: DCProps) {
423424
}
424425

425426
function editableQuota() {
426-
if (input.type == "cash-voucher") return null;
427+
if (input.type === "cash-voucher") return null;
427428
return (
428429
<div>
429430
<div>{describeQuotaFromInfo(input)}</div>
@@ -433,9 +434,14 @@ function DescriptionColumnSiteLicense(props: DCProps) {
433434
}
434435

435436
// this could rely an the "type" field, but we rather check the data directly
436-
function editPage(): "site-license" | "vouchers" {
437-
if (input.type == "cash-voucher") {
437+
function editPage(): "site-license" | "vouchers" | "course" {
438+
if (input.type === "cash-voucher") {
438439
return "vouchers";
440+
} else if (
441+
description.type === "quota" &&
442+
description.source === "course"
443+
) {
444+
return "course";
439445
}
440446
return "site-license";
441447
}
@@ -451,7 +457,7 @@ function DescriptionColumnSiteLicense(props: DCProps) {
451457
<div style={DESCRIPTION_STYLE}>
452458
<div style={{ marginBottom: "8px" }}>
453459
<b>
454-
{input.subscription == "no"
460+
{input.subscription === "no"
455461
? describePeriod({ quota: input })
456462
: capitalize(input.subscription) + " subscription"}
457463
</b>

src/packages/next/components/store/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ export default function StoreLayout({ page }: Props) {
123123

124124
switch (main) {
125125
case "site-license":
126-
return <SiteLicense noAccount={noAccount} type="license" />;
126+
return <SiteLicense noAccount={noAccount} source="license" />;
127127
case "course":
128-
return <SiteLicense noAccount={noAccount} type="course" />;
128+
return <SiteLicense noAccount={noAccount} source="course" />;
129129
case "cart":
130130
return requireAccount(Cart);
131131
case "checkout":

src/packages/next/components/store/quota-config.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Icon } from "@cocalc/frontend/components/icon";
2323
import { displaySiteLicense } from "@cocalc/util/consts/site-license";
2424
import { plural, unreachable } from "@cocalc/util/misc";
2525
import { BOOST, DISK_DEFAULT_GB, REGULAR } from "@cocalc/util/upgrades/consts";
26+
import type { LicenseSource } from "@cocalc/util/upgrades/shopping";
2627

2728
import PricingItem, { Line } from "components/landing/pricing-item";
2829
import { CSS, Paragraph } from "components/misc";
@@ -35,7 +36,6 @@ import {
3536
Preset,
3637
PresetConfig,
3738
} from "./quota-config-presets";
38-
import type { LicenseType } from "./types";
3939

4040
const { Text } = Typography;
4141

@@ -62,7 +62,7 @@ interface Props {
6262
setPreset?: (preset: Preset | null) => void;
6363
presetAdjusted?: boolean;
6464
setPresetAdjusted?: (adjusted: boolean) => void;
65-
type: LicenseType;
65+
source: LicenseSource;
6666
}
6767

6868
export const QuotaConfig: React.FC<Props> = (props: Props) => {
@@ -78,7 +78,7 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
7878
setPreset,
7979
presetAdjusted,
8080
setPresetAdjusted,
81-
type,
81+
source,
8282
} = props;
8383

8484
const presetsRef = useRef<HTMLDivElement>(null);
@@ -114,13 +114,13 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
114114
if (boost) {
115115
return "Booster";
116116
} else {
117-
switch (type) {
117+
switch (source) {
118118
case "license":
119119
return "Quota Upgrades";
120120
case "course":
121121
return "Project Upgrades";
122122
default:
123-
unreachable(type);
123+
unreachable(source);
124124
}
125125
}
126126
}
@@ -443,7 +443,7 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
443443

444444
return (
445445
<>
446-
<Form.Item label="Preset">
446+
<Form.Item label="Presets">
447447
<Radio.Group
448448
size="large"
449449
value={preset}
@@ -650,7 +650,7 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
650650
</>
651651
);
652652
} else {
653-
switch (type) {
653+
switch (source) {
654654
case "license":
655655
return (
656656
<Tabs
@@ -687,7 +687,7 @@ export const QuotaConfig: React.FC<Props> = (props: Props) => {
687687
case "course":
688688
return renderCoursePresets();
689689
default:
690-
unreachable(type);
690+
unreachable(source);
691691
}
692692
}
693693
}

src/packages/next/components/store/quota-query-params.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { MAX_ALLOWED_RUN_LIMIT } from "./run-limit";
2222
// Various support functions for storing quota parameters as a query parameter in the browser URL
2323

2424
export function encodeRange(
25-
vals: [Date | string | undefined, Date | string | undefined]
25+
vals: [Date | string | undefined, Date | string | undefined],
2626
): string {
2727
const [start, end] = vals;
2828
if (start == null || end == null) {
@@ -75,7 +75,7 @@ const DEDICATED_FIELDS = [
7575
] as const;
7676

7777
function getFormFields(
78-
type: "regular" | "boost" | "dedicated"
78+
type: "regular" | "boost" | "dedicated",
7979
): readonly string[] {
8080
switch (type) {
8181
case "regular":
@@ -87,13 +87,16 @@ function getFormFields(
8787
}
8888

8989
export const ALL_FIELDS: Set<string> = new Set(
90-
REGULAR_FIELDS.concat(DEDICATED_FIELDS as any).concat(["type" as any])
90+
REGULAR_FIELDS.concat(DEDICATED_FIELDS as any).concat([
91+
"type",
92+
"source",
93+
] as any),
9194
);
9295

9396
export function encodeFormValues(
9497
router: NextRouter,
9598
vals: any,
96-
type: "regular" | "boost" | "dedicated"
99+
type: "regular" | "boost" | "dedicated",
97100
): void {
98101
const { query } = router;
99102
for (const key in vals) {
@@ -120,7 +123,7 @@ function decodeValue(val): boolean | number | string | DateRange {
120123

121124
function fixNumVal(
122125
val: any,
123-
param: { min: number; max: number; dflt: number }
126+
param: { min: number; max: number; dflt: number },
124127
): number {
125128
if (typeof val !== "number") {
126129
return param.dflt;
@@ -136,7 +139,7 @@ function fixNumVal(
136139
*/
137140
export function decodeFormValues(
138141
router: NextRouter,
139-
type: "regular" | "boost" | "dedicated"
142+
type: "regular" | "boost" | "dedicated",
140143
): {
141144
[key: string]: string | number | boolean;
142145
} {

0 commit comments

Comments
 (0)