jusrhee пре 2 година
родитељ
комит
ddf0413463

+ 9 - 0
dashboard/src/assets/gift.svg

@@ -0,0 +1,9 @@
+<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.2909 34V10.8566M16.5826 9.46825C16.8707 9.53255 17.1743 9.45374 17.3817 9.24746C17.5892 9.04118 17.6683 8.73915 17.6038 8.45275C17.3452 7.40232 16.3926 3.88491 15.4145 2.91224C14.1959 1.70041 12.2126 1.69536 11 2.90119C9.78751 4.10694 9.79242 6.07925 11.0111 7.29116C12.0052 8.27975 15.5263 9.21113 16.5826 9.46825ZM18.5402 8.45267C18.4756 8.73918 18.5548 9.0411 18.7622 9.24738C18.9697 9.45366 19.2734 9.53235 19.5614 9.46817C20.6177 9.21102 24.1548 8.26375 25.1329 7.29108C26.3515 6.07925 26.3566 4.10694 25.144 2.90111C23.9315 1.69536 21.9482 1.70024 20.7295 2.91215C19.7354 3.90074 18.7988 7.40227 18.5402 8.45267ZM3.16364 18.9568H32.8364C33.479 18.9568 34 18.4387 34 17.7996V12.0138C34 11.3747 33.479 10.8566 32.8364 10.8566H3.16364C2.52098 10.8566 2 11.3747 2 12.0138V17.7996C2 18.4387 2.52098 18.9568 3.16364 18.9568ZM31.0909 18.9568V32.8428C31.0909 33.4819 30.5699 34 29.9273 34H6.07273C5.43007 34 4.90909 33.4819 4.90909 32.8428V18.9568H31.0909Z" stroke="url(#paint0_linear_1699_18)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
+<defs>
+<linearGradient id="paint0_linear_1699_18" x1="14.5" y1="5" x2="33.4613" y2="50.1509" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F8F8F8"/>
+<stop offset="1" stop-color="#666666" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>

+ 186 - 167
dashboard/src/main/home/app-dashboard/AppDashboard.tsx

@@ -1,37 +1,37 @@
-import React, { useEffect, useState, useContext, useMemo } from "react";
-import styled from "styled-components";
+import React, { useContext, useEffect, useMemo, useState } from "react";
 import _ from "lodash";
 import { Link, LinkProps } from "react-router-dom";
+import styled from "styled-components";
+
+import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
+import Loading from "components/Loading";
+import Button from "components/porter/Button";
+import Container from "components/porter/Container";
+import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
+import Fieldset from "components/porter/Fieldset";
+import Icon from "components/porter/Icon";
+import PorterLink from "components/porter/Link";
+import SearchBar from "components/porter/SearchBar";
+import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+import Toggle from "components/porter/Toggle";
 
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { search } from "shared/search";
+import { readableDate } from "shared/string_utils";
 import applications from "assets/applications.svg";
 import box from "assets/box.png";
+import calendar from "assets/calendar-number.svg";
 import github from "assets/github.png";
-import time from "assets/time.png";
-import healthy from "assets/status-healthy.png";
 import grid from "assets/grid.png";
 import list from "assets/list.png";
-import letter from "assets/vector.svg";
-import calendar from "assets/calendar-number.svg";
 import notFound from "assets/not-found.png";
-
-import { Context } from "shared/Context";
-import { search } from "shared/search";
-import api from "shared/api";
-import { readableDate } from "shared/string_utils";
+import healthy from "assets/status-healthy.png";
+import time from "assets/time.png";
+import letter from "assets/vector.svg";
 
 import DashboardHeader from "../cluster-dashboard/DashboardHeader";
-import Container from "components/porter/Container";
-import Button from "components/porter/Button";
-import Spacer from "components/porter/Spacer";
-import Text from "components/porter/Text";
-import SearchBar from "components/porter/SearchBar";
-import Toggle from "components/porter/Toggle";
-import PorterLink from "components/porter/Link";
-import Loading from "components/Loading";
-import Fieldset from "components/porter/Fieldset";
-import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
-import Icon from "components/porter/Icon";
-import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
 
 type Props = {};
 
@@ -53,8 +53,9 @@ const namespaceBlacklist = [
   "monitoring",
 ];
 
-const AppDashboard: React.FC<Props> = ({ }) => {
-  const { currentProject, currentCluster, setFeaturePreview } = useContext(Context);
+const AppDashboard: React.FC<Props> = ({}) => {
+  const { currentProject, currentCluster, setFeaturePreview } =
+    useContext(Context);
   const [apps, setApps] = useState([]);
   const [charts, setCharts] = useState([]);
   const [error, setError] = useState(null);
@@ -93,8 +94,8 @@ const AppDashboard: React.FC<Props> = ({ }) => {
       );
       const apps = res.data;
       const timeRes = await Promise.all(
-        apps.map((app: any) => {
-          return api.getCharts(
+        apps.map(async (app: any) => {
+          return await api.getCharts(
             "<token>",
             {
               limit: 1,
@@ -120,7 +121,7 @@ const AppDashboard: React.FC<Props> = ({ }) => {
       );
       apps.forEach((app: any, i: number) => {
         if (timeRes?.[i]?.data?.[0]?.info?.last_deployed != null) {
-          app["last_deployed"] = readableDate(
+          app.last_deployed = readableDate(
             timeRes[i].data[0].info.last_deployed
           );
         }
@@ -145,7 +146,9 @@ const AppDashboard: React.FC<Props> = ({ }) => {
         {app.repo_name ? (
           <Container row>
             <SmallIcon opacity="0.6" src={github} />
-            <Text size={13} color="#ffffff44">{app.repo_name}</Text>
+            <Text size={13} color="#ffffff44">
+              {app.repo_name}
+            </Text>
           </Container>
         ) : (
           <Container row>
@@ -154,7 +157,9 @@ const AppDashboard: React.FC<Props> = ({ }) => {
               height="18px"
               src="https://cdn4.iconfinder.com/data/icons/logos-and-brands/512/97_Docker_logo_logos-512.png"
             />
-            <Text truncate={true} size={13} color="#ffffff44">{app.image_repo_uri}</Text>
+            <Text truncate={true} size={13} color="#ffffff44">
+              {app.image_repo_uri}
+            </Text>
           </Container>
         )}
       </>
@@ -166,7 +171,7 @@ const AppDashboard: React.FC<Props> = ({ }) => {
       await api.updateStackStep(
         "<token>",
         {
-          step: 'stack-launch-start'
+          step: "stack-launch-start",
         },
         {
           cluster_id: currentCluster.id,
@@ -176,11 +181,10 @@ const AppDashboard: React.FC<Props> = ({ }) => {
     } catch (err) {
       // TODO: handle error
     }
-  }
-
+  };
 
   const renderIcon = (b: string, size?: string) => {
-    var src = box;
+    let src = box;
     if (b) {
       const bp = b.split(",")[0]?.split("/")[1];
       switch (bp) {
@@ -201,7 +205,13 @@ const AppDashboard: React.FC<Props> = ({ }) => {
       }
     }
     return (
-      <>{size === "larger" ? <Icon height="16px" src={src} /> : <Icon height="18px" src={src} />}</>
+      <>
+        {size === "larger" ? (
+          <Icon height="16px" src={src} />
+        ) : (
+          <Icon height="18px" src={src} />
+        )}
+      </>
     );
   };
 
@@ -215,140 +225,149 @@ const AppDashboard: React.FC<Props> = ({ }) => {
       />
       {currentCluster?.status === "UPDATING_UNAVAILABLE" ? (
         <ClusterProvisioningPlaceholder />
-      ) : (
-        apps.length === 0 ? (
-          isLoading ?
-            (<Loading offset="-150px" />) : (
-              <DashboardPlaceholder>
-                <Text size={16}>
-                  No apps have been deployed yet
-                </Text>
-                <Spacer y={0.5} />
-                <Text color={"helper"}>
-                  Get started by deploying your app.
-                </Text>
-                <Spacer y={1} />
-                <PorterLink to="/apps/new/app">
-                  <Button alt onClick={async () => updateStackStartedStep()} height="35px">
-                    Deploy app <Spacer inline x={1} /> <i className="material-icons" style={{ fontSize: '18px' }}>east</i>
-                  </Button>
-                </PorterLink>
-              </DashboardPlaceholder>
-            )
+      ) : apps.length === 0 ? (
+        isLoading ? (
+          <Loading offset="-150px" />
         ) : (
-          <>
-            <Container row spaced>
-              <SearchBar
-                value={searchValue}
-                setValue={(x) => {
-                  if (x === "open_sesame") {
-                    setFeaturePreview(true);
-                  }
-                  setSearchValue(x);
-                }}
-                placeholder="Search applications . . ."
-                width="100%"
-              />
-              <Spacer inline x={2} />
-              <Toggle
-                items={[
-                  { label: <ToggleIcon src={calendar} />, value: "calendar" },
-                  { label: <ToggleIcon src={letter} />, value: "letter" },
-                ]}
-                active={sort}
-                setActive={setSort}
-              />
-              <Spacer inline x={1} />
-
-              <Toggle
-                items={[
-                  { label: <ToggleIcon src={grid} />, value: "grid" },
-                  { label: <ToggleIcon src={list} />, value: "list" },
-                ]}
-                active={view}
-                setActive={setView}
-              />
-
-              <Spacer inline x={2} />
-              <PorterLink to="/apps/new/app">
-                <Button onClick={async () => updateStackStartedStep()} height="30px" width="160px">
-                  <I className="material-icons">add</I> New application
-                </Button>
-              </PorterLink>
-            </Container>
+          <DashboardPlaceholder>
+            <Text size={16}>No apps have been deployed yet</Text>
+            <Spacer y={0.5} />
+            <Text color={"helper"}>Get started by deploying your app.</Text>
             <Spacer y={1} />
+            <PorterLink to="/apps/new/app">
+              <Button
+                alt
+                onClick={async () => {
+                  await updateStackStartedStep();
+                }}
+                height="35px"
+              >
+                Deploy app <Spacer inline x={1} />{" "}
+                <i className="material-icons" style={{ fontSize: "18px" }}>
+                  east
+                </i>
+              </Button>
+            </PorterLink>
+          </DashboardPlaceholder>
+        )
+      ) : (
+        <>
+          <Container row spaced>
+            <SearchBar
+              value={searchValue}
+              setValue={(x) => {
+                if (x === "open_sesame") {
+                  setFeaturePreview(true);
+                }
+                setSearchValue(x);
+              }}
+              placeholder="Search applications . . ."
+              width="100%"
+            />
+            <Spacer inline x={2} />
+            <Toggle
+              items={[
+                { label: <ToggleIcon src={calendar} />, value: "calendar" },
+                { label: <ToggleIcon src={letter} />, value: "letter" },
+              ]}
+              active={sort}
+              setActive={setSort}
+            />
+            <Spacer inline x={1} />
+
+            <Toggle
+              items={[
+                { label: <ToggleIcon src={grid} />, value: "grid" },
+                { label: <ToggleIcon src={list} />, value: "list" },
+              ]}
+              active={view}
+              setActive={setView}
+            />
 
-            {filteredApps.length === 0 ? (
-              <Fieldset>
-                <Container row>
-                  <PlaceholderIcon src={notFound} />
-                  <Text color="helper">No matching apps were found.</Text>
-                </Container>
-              </Fieldset>
-            ) : (isLoading ? (
-              <Loading offset="-150px" />
-            ) : view === "grid" ? (
-              <GridList>
-                {(filteredApps ?? []).map((app: any, i: number) => {
-                  if (!namespaceBlacklist.includes(app.name)) {
-                    return (
-                      <Link to={`/apps/${app.name}`} key={i}>
-                        <Block>
-                          <Container row>
-                            {renderIcon(app["buildpacks"])}
-                            <Spacer inline width="12px" />
-                            <Text size={14}>{app.name}</Text>
-                            <Spacer inline x={2} />
-                          </Container>
-                          <StatusIcon src={healthy} />
+            <Spacer inline x={2} />
+            <PorterLink to="/apps/new/app">
+              <Button
+                onClick={async () => {
+                  await updateStackStartedStep();
+                }}
+                height="30px"
+                width="160px"
+              >
+                <I className="material-icons">add</I> New application
+              </Button>
+            </PorterLink>
+          </Container>
+          <Spacer y={1} />
+
+          {filteredApps.length === 0 ? (
+            <Fieldset>
+              <Container row>
+                <PlaceholderIcon src={notFound} />
+                <Text color="helper">No matching apps were found.</Text>
+              </Container>
+            </Fieldset>
+          ) : isLoading ? (
+            <Loading offset="-150px" />
+          ) : view === "grid" ? (
+            <GridList>
+              {(filteredApps ?? []).map((app: any, i: number) => {
+                if (!namespaceBlacklist.includes(app.name)) {
+                  return (
+                    <Link to={`/apps/${app.name}`} key={i}>
+                      <Block>
+                        <Container row>
+                          {renderIcon(app.buildpacks)}
+                          <Spacer inline width="12px" />
+                          <Text size={14}>{app.name}</Text>
+                          <Spacer inline x={2} />
+                        </Container>
+                        <StatusIcon src={healthy} />
+                        {renderSource(app)}
+                        <Container row>
+                          <SmallIcon opacity="0.4" src={time} />
+                          <Text size={13} color="#ffffff44">
+                            {app.last_deployed}
+                          </Text>
+                        </Container>
+                      </Block>
+                    </Link>
+                  );
+                }
+              })}
+            </GridList>
+          ) : (
+            <List>
+              {(filteredApps ?? []).map((app: any, i: number) => {
+                if (!namespaceBlacklist.includes(app.name)) {
+                  return (
+                    <Link to={`/apps/${app.name}`} key={i}>
+                      <Row>
+                        <Container row>
+                          <Spacer inline width="1px" />
+                          {renderIcon(app.buildpacks, "larger")}
+                          <Spacer inline width="12px" />
+                          <Text size={14}>{app.name}</Text>
+                          <Spacer inline x={1} />
+                          <Icon height="16px" src={healthy} />
+                        </Container>
+                        <Spacer height="15px" />
+                        <Container row>
                           {renderSource(app)}
-                          <Container row>
-                            <SmallIcon opacity="0.4" src={time} />
-                            <Text size={13} color="#ffffff44">{app.last_deployed}</Text>
-                          </Container>
-                        </Block>
-                      </Link>
-                    );
-                  }
-                })}
-              </GridList>
-            ) : (
-              <List>
-                {(filteredApps ?? []).map((app: any, i: number) => {
-                  if (!namespaceBlacklist.includes(app.name)) {
-                    return (
-                      <Link to={`/apps/${app.name}`} key={i}>
-                        <Row>
-                          <Container row>
-                            <Spacer inline width="1px" />
-                            {renderIcon(app["buildpacks"], "larger")}
-                            <Spacer inline width="12px" />
-                            <Text size={14}>
-                              {app.name}
-                            </Text>
-                            <Spacer inline x={1} />
-                            <Icon height="16px" src={healthy} />
-                          </Container>
-                          <Spacer height="15px" />
-                          <Container row>
-                            {renderSource(app)}
-                            <Spacer inline x={1} />
-                            <SmallIcon opacity="0.4" src={time} />
-                            <Text size={13} color="#ffffff44">
-                              {app.last_deployed}
-                            </Text>
-                          </Container>
-                        </Row>
-                      </Link>
-                    );
-                  }
-                })}
-              </List>
-            ))}
-          </>
-        )
-      )
-      }
+                          <Spacer inline x={1} />
+                          <SmallIcon opacity="0.4" src={time} />
+                          <Text size={13} color="#ffffff44">
+                            {app.last_deployed}
+                          </Text>
+                        </Container>
+                      </Row>
+                    </Link>
+                  );
+                }
+              })}
+            </List>
+          )}
+        </>
+      )}
       <Spacer y={5} />
     </StyledAppDashboard>
   );
@@ -451,5 +470,5 @@ const CentralContainer = styled.div`
   display: flex;
   flex-direction: column;
   justify-content: left;
-  align-items: left;   
-`;
+  align-items: left;
+`;

+ 53 - 34
dashboard/src/main/home/app-dashboard/apps/Apps.tsx

@@ -7,10 +7,13 @@ import { z } from "zod";
 
 import ClusterProvisioningPlaceholder from "components/ClusterProvisioningPlaceholder";
 import Loading from "components/Loading";
+import Banner from "components/porter/Banner";
 import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
+import Image from "components/porter/Image";
 import PorterLink from "components/porter/Link";
+import Link from "components/porter/Link";
 import Modal from "components/porter/Modal";
 import SearchBar from "components/porter/SearchBar";
 import Spacer from "components/porter/Spacer";
@@ -28,6 +31,7 @@ import { Context } from "shared/Context";
 import { useDeploymentTarget } from "shared/DeploymentTargetContext";
 import applicationGrad from "assets/application-grad.svg";
 import calendar from "assets/calendar-number.svg";
+import gift from "assets/gift.svg";
 import grid from "assets/grid.png";
 import list from "assets/list.png";
 import pull_request from "assets/pull_request_icon.svg";
@@ -212,31 +216,28 @@ const Apps: React.FC = () => {
       }
 
       return (
-        <DashboardPlaceholder>
-          <Text size={16}>No applications have been created yet</Text>
-          <Spacer y={0.5} />
-          <Text color={"helper"}>Get started by creating an application.</Text>
-          <Spacer y={1} />
-          {currentProject?.billing_enabled && !hasPaymentEnabled ? (
-            <Button
-              alt
-              onClick={() => {
-                setShowBillingModal(true);
-              }}
-              height="35px"
-            >
-              Create a new application
-              <Spacer inline x={1} />{" "}
-              <i className="material-icons" style={{ fontSize: "18px" }}>
-                east
-              </i>
-            </Button>
-          ) : (
-            <PorterLink to="/apps/new/app">
+        <>
+          {currentProject?.sandbox_enabled && (
+            <>
+              <Banner icon={<Image src={gift} />}>
+                $5 of Porter credits have automatically been credited to your
+                account.
+              </Banner>
+              <Spacer y={1} />
+            </>
+          )}
+          <DashboardPlaceholder>
+            <Text size={16}>No applications have been created yet</Text>
+            <Spacer y={0.5} />
+            <Text color={"helper"}>
+              Get started by creating an application.
+            </Text>
+            <Spacer y={1} />
+            {currentProject?.billing_enabled && !hasPaymentEnabled ? (
               <Button
                 alt
-                onClick={async () => {
-                  await updateAppStep({ step: "stack-launch-start" });
+                onClick={() => {
+                  setShowBillingModal(true);
                 }}
                 height="35px"
               >
@@ -246,17 +247,35 @@ const Apps: React.FC = () => {
                   east
                 </i>
               </Button>
-            </PorterLink>
-          )}
-          {showBillingModal && (
-            <BillingModal
-              back={() => setShowBillingModal(false)}
-              onCreate={() => {
-                history.push("/apps/new/app");
-              }}
-            />
-          )}
-        </DashboardPlaceholder>
+            ) : (
+              <PorterLink to="/apps/new/app">
+                <Button
+                  alt
+                  onClick={async () => {
+                    await updateAppStep({ step: "stack-launch-start" });
+                  }}
+                  height="35px"
+                >
+                  Create a new application
+                  <Spacer inline x={1} />{" "}
+                  <i className="material-icons" style={{ fontSize: "18px" }}>
+                    east
+                  </i>
+                </Button>
+              </PorterLink>
+            )}
+            {showBillingModal && (
+              <BillingModal
+                back={() => {
+                  setShowBillingModal(false);
+                }}
+                onCreate={() => {
+                  history.push("/apps/new/app");
+                }}
+              />
+            )}
+          </DashboardPlaceholder>
+        </>
       );
     }
 

+ 21 - 1
dashboard/src/main/home/project-settings/BillingPage.tsx

@@ -6,6 +6,7 @@ import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Fieldset from "components/porter/Fieldset";
 import Icon from "components/porter/Icon";
+import Image from "components/porter/Image";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
 import {
@@ -17,6 +18,7 @@ import {
 
 import { Context } from "shared/Context";
 import cardIcon from "assets/credit-card.svg";
+import gift from "assets/gift.svg";
 import trashIcon from "assets/trash.png";
 
 import BillingModal from "../modals/BillingModal";
@@ -44,12 +46,30 @@ function BillingPage(): JSX.Element {
 
   if (shouldCreate) {
     return (
-      <BillingModal onCreate={onCreate} back={() => setShouldCreate(false)} />
+      <BillingModal
+        onCreate={onCreate}
+        back={() => {
+          setShouldCreate(false);
+        }}
+      />
     );
   }
 
   return (
     <>
+      <Text size={16}>Porter credit balance</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 row>
+        <Image src={gift} style={{ marginTop: "-2px" }} />
+        <Spacer inline x={1} />
+        <Text size={20}>$ 5.00</Text>
+      </Container>
+      <Spacer y={2} />
       <Text size={16}>Payment methods</Text>
       <Spacer y={1} />
       <Text color="helper">