Quellcode durchsuchen

Add remaining/total credits, improve ui

Mauricio Araujo vor 2 Jahren
Ursprung
Commit
8080f6c75f

+ 18 - 6
api/types/billing_metronome.go

@@ -83,6 +83,12 @@ type CreditType struct {
 	ID   string `json:"id"`
 }
 
+// GrantAmount represents the amount of credits granted
+type GrantAmount struct {
+	Amount     float64    `json:"amount"`
+	CreditType CreditType `json:"credit_type"`
+}
+
 // Balance represents the effective balance of the grant as of the end of the customer's
 // current billing period.
 type Balance struct {
@@ -96,12 +102,18 @@ type Balance struct {
 
 // CreditGrant is a grant given to a specific user on a specific plan
 type CreditGrant struct {
-	ID          uuid.UUID `json:"id"`
-	Name        string    `json:"name"`
-	Balance     Balance   `json:"balance"`
-	Reason      string    `json:"reason"`
-	EffectiveAt string    `json:"effective_at"`
-	ExpiresAt   string    `json:"expires_at"`
+	ID          uuid.UUID   `json:"id"`
+	Name        string      `json:"name"`
+	GrantAmount GrantAmount `json:"grant_amount"`
+	Balance     Balance     `json:"balance"`
+	Reason      string      `json:"reason"`
+	EffectiveAt string      `json:"effective_at"`
+	ExpiresAt   string      `json:"expires_at"`
+}
+
+type ListCreditGrantsResponse struct {
+	RemainingCredits float64 `json:"remaining_credits"`
+	GrantedCredits   float64 `json:"granted_credits"`
 }
 
 // DashboardOptions are optional dashboard specific options

+ 4 - 13
dashboard/src/lib/billing/types.tsx

@@ -27,19 +27,10 @@ export const Plan = z.object({
   trial_info: Trial,
 });
 
-export type CreditGrantList = CreditGrant[];
-export type CreditGrant = z.infer<typeof CreditGrantValidator>;
-export const CreditGrantValidator = z.object({
-  id: z.string(),
-  name: z.string(),
-  balance: z.object({
-    excluding_pending: z.number(),
-    including_pending: z.number(),
-    effective_at: z.string(),
-  }),
-  reason: z.string(),
-  effective_at: z.string(),
-  expires_at: z.string(),
+export type CreditGrants = z.infer<typeof CreditGrantsValidator>;
+export const CreditGrantsValidator = z.object({
+  granted_credits: z.number(),
+  remaining_credits: z.number(),
 });
 
 export const ClientSecretResponse = z.string();

+ 3 - 3
dashboard/src/lib/hooks/useStripe.tsx

@@ -4,7 +4,7 @@ import { z } from "zod";
 
 import {
   ClientSecretResponse,
-  CreditGrantList,
+  CreditGrants,
   PaymentMethodList,
   PaymentMethodValidator,
   Plan,
@@ -38,7 +38,7 @@ type TGetPublishableKey = {
 };
 
 type TGetCredits = {
-  totalCredits: number;
+  creditGrants: CreditGrants;
 };
 
 type TGetPlan = {
@@ -208,7 +208,7 @@ export const usePorterCredits = (): TGetCredits => {
   );
 
   return {
-    totalCredits: creditsReq.data,
+    creditGrants: creditsReq.data,
   };
 };
 

+ 51 - 37
dashboard/src/main/home/project-settings/BillingPage.tsx

@@ -29,7 +29,7 @@ function BillingPage(): JSX.Element {
   const [shouldCreate, setShouldCreate] = useState(false);
   const { currentProject } = useContext(Context);
 
-  const { totalCredits } = usePorterCredits();
+  const { creditGrants } = usePorterCredits();
   const { plan } = useCustomerPlan();
 
   const {
@@ -76,6 +76,8 @@ function BillingPage(): JSX.Element {
     return rt.format(relativeValue, relativeFormat);
   };
 
+  const readableDate = (s: string) => new Date(s).toLocaleDateString();
+
   const onCreate = async () => {
     await refetchPaymentMethods();
     setShouldCreate(false);
@@ -97,51 +99,63 @@ function BillingPage(): JSX.Element {
     <>
       {currentProject?.metronome_enabled ? (
         <div>
-          <Text size={16}>Porter credit grants</Text>
-          <Spacer y={1} />
-          <Text color="helper">
-            View the amount of Porter credits you have available to spend on
-            resources within this project.
-          </Text>
-          <Spacer y={1} />
-
-          <Container>
-            <Image src={gift} style={{ marginTop: "-2px" }} />
-            <Spacer inline x={1} />
-            <Text size={20}>
-              {totalCredits > 0 ? `$${formatCredits(totalCredits)}` : "$ 0.00"}
+          <div>
+            <Text size={16}>Porter credit grants</Text>
+            <Spacer y={1} />
+            <Text color="helper">
+              View the amount of Porter credits you have available to spend on
+              resources within this project.
             </Text>
-          </Container>
-          <Spacer y={1} />
+            <Spacer y={1} />
 
-          <Text size={16}>Plan Details</Text>
-          <Spacer y={1} />
-          <Text color="helper">
-            View the details of the current plan you are subscribed to.
-          </Text>
-          <Spacer y={1} />
+            <Container>
+              <Image src={gift} style={{ marginTop: "-2px" }} />
+              <Spacer inline x={1} />
+              <Text size={20}>
+                {creditGrants !== undefined &&
+                creditGrants.remaining_credits > 0
+                  ? `$${formatCredits(
+                      creditGrants.remaining_credits
+                    )}/$${formatCredits(creditGrants.granted_credits)}`
+                  : "$ 0.00"}
+              </Text>
+            </Container>
+            <Spacer y={2} />
+          </div>
 
           <div>
-            {plan === undefined ? (
-              <Loading></Loading>
-            ) : (
+            <Text size={16}>Plan Details</Text>
+            <Spacer y={1} />
+            <Text color="helper">
+              View the details of the current billing plan of this project.
+            </Text>
+            <Spacer y={1} />
+
+            <Text>Active Plan</Text>
+            <Spacer y={0.5} />
+            {plan !== undefined ? (
               <Fieldset>
-                <Container row>
-                  <Text size={14}>{plan.plan_name}</Text>
+                <Container row spaced>
+                  <Container row>
+                    <Text color="helper">{plan.plan_name}</Text>
+                  </Container>
+                  <Container row>
+                    {plan.trial_info !== undefined && false ? (
+                      <Text>
+                        Free trial ends{" "}
+                        {relativeTime(plan.trial_info.ending_before)}
+                      </Text>
+                    ) : (
+                      <Text>Started on {readableDate(plan.starting_on)}</Text>
+                    )}
+                  </Container>
                 </Container>
-                {plan.trial_info !== undefined ? (
-                  <div>
-                    <Text size={13}>
-                      Trial Ending {relativeTime(plan.trial_info.ending_before)}
-                    </Text>
-                  </div>
-                ) : (
-                  <div></div>
-                )}
               </Fieldset>
+            ) : (
+              <Loading></Loading>
             )}
+            <Spacer y={1} />
           </div>
-          <Spacer y={2} />
         </div>
       ) : (
         <div></div>

+ 5 - 4
internal/billing/metronome.go

@@ -177,7 +177,7 @@ func (m MetronomeClient) EndCustomerPlan(ctx context.Context, customerID uuid.UU
 }
 
 // ListCustomerCredits will return the total number of credits for the customer
-func (m MetronomeClient) ListCustomerCredits(ctx context.Context, customerID uuid.UUID) (credits float64, err error) {
+func (m MetronomeClient) ListCustomerCredits(ctx context.Context, customerID uuid.UUID) (credits types.ListCreditGrantsResponse, err error) {
 	ctx, span := telemetry.NewSpan(ctx, "list-customer-credits")
 	defer span.End()
 
@@ -202,12 +202,13 @@ func (m MetronomeClient) ListCustomerCredits(ctx context.Context, customerID uui
 		return credits, telemetry.Error(ctx, span, err, "failed to list customer credits")
 	}
 
-	var totalCredits float64
+	var response types.ListCreditGrantsResponse
 	for _, grant := range result.Data {
-		totalCredits += grant.Balance.IncludingPending
+		response.GrantedCredits += grant.GrantAmount.Amount
+		response.RemainingCredits += grant.Balance.IncludingPending
 	}
 
-	return totalCredits, nil
+	return response, nil
 }
 
 func do(method string, path string, apiKey string, body interface{}, data interface{}) (err error) {