Skip to content

Commit

Permalink
Add Prismabuilder++ survey modal
Browse files Browse the repository at this point in the history
  • Loading branch information
albingroen committed Jan 31, 2023
1 parent c765310 commit b906840
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 64 deletions.
64 changes: 39 additions & 25 deletions components/AddField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const AddField = ({
}: AddFieldProps) => {
const { schema } = useSchemaContext();

const [isCustomDefaultValue, setIsCustomDefaultValue] =
useState<boolean>(false);
const [isUpdatedAt, setIsUpdatedAt] = useState<boolean>(false);
const [type, setType] = useState<FieldType>("" as FieldType);
const [defaultValue, setDefaultValue] = useState<string>("");
Expand Down Expand Up @@ -108,32 +110,44 @@ const AddField = ({
</Select.Option>
))}
</Select.Container>
{enumType || PRISMA_DEFAULT_VALUES(type).length ? (
<Select.Container
defaultSelectedKey={defaultValue}
onSelectionChange={(key) => {

<Select.Container
defaultSelectedKey={defaultValue}
onSelectionChange={(key) => {
if (key === "custom") {
setIsCustomDefaultValue(true);
} else {
setIsCustomDefaultValue(false);
setDefaultValue(key);
}}
hint="A Prisma default value function"
label="Default value"
>
<Select.Option key="">No default value</Select.Option>
{(
enumType?.fields?.map((field) => ({
description: "",
value: field,
label: field,
})) || PRISMA_DEFAULT_VALUES(type)
).map((defaultValue) => (
<Select.Option
description={defaultValue.description}
key={defaultValue.value}
>
{defaultValue.label}
</Select.Option>
))}
</Select.Container>
) : null}
}
}}
hint="A Prisma default value function"
label="Default value"
>
<Select.Option key="">No default value</Select.Option>
{(
enumType?.fields?.map((field) => ({
description: "",
value: field,
label: field,
})) || PRISMA_DEFAULT_VALUES(type)
).map((defaultValue) => (
<Select.Option
description={defaultValue.description}
key={defaultValue.value}
>
{defaultValue.label}
</Select.Option>
))}
<Select.Option key="custom" description="Add a custom default value">
Custom default value
</Select.Option>
</Select.Container>

{isCustomDefaultValue && (
<TextField label="Custom default value" onChange={setDefaultValue} />
)}

<div className="flex space-x-8 items-start py-2">
<div className="flex flex-col space-y-3">
<label
Expand Down
26 changes: 14 additions & 12 deletions components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ const Modal = ({ children, heading, open, onClose }: ModalProps) => {
<Dialog.Overlay className="fixed inset-0 bg-black opacity-30" />

<div className="w-full relative bg-white rounded-lg max-w-2xl mx-auto my-8">
<div className="p-4 border-b flex justify-between items-center space-x-4">
<Dialog.Title className="text-xl font-medium">
{heading}
</Dialog.Title>
{heading && (
<div className="p-4 border-b flex justify-between items-center space-x-4">
<Dialog.Title className="text-xl font-medium">
{heading}
</Dialog.Title>

<button
className="text-gray-500 hover:text-gray-600 transition"
aria-label="Exit modal"
onClick={onClose}
>
<X />
</button>
</div>
<button
className="text-gray-500 hover:text-gray-600 transition"
aria-label="Exit modal"
onClick={onClose}
>
<X />
</button>
</div>
)}

<div className="p-6 overflow-auto">{children}</div>
</div>
Expand Down
91 changes: 91 additions & 0 deletions components/PricingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Modal from "./Modal";
import cn from "../lib/classNames";
import { Button } from "@prisma/lens";
import { useState } from "react";

type PricingModalProps = {
onClose: (price?: number) => void;
open: boolean;
};

const PRICES = [5, 10, 15];

const PricingModal = ({ onClose, open }: PricingModalProps) => {
const [price, setPrice] = useState<number>();

return (
<Modal
open={open}
onClose={() => {
onClose(price);
}}
>
<div className="flex flex-col gap-4 antialiased">
<div className="flex flex-col items-center text-center rounded-lg bg-gradient-to-br from-gray-50 to-gray-100 pt-10 px-10 pb-5 shadow-inner">
<div className="flex flex-col items-center gap-3.5">
<h1 className="text-3xl font-semibold text-transparent bg-clip-text bg-gradient-to-r from-black to-yellow-700">
Prismabuilder<span className="text-yellow-500">++</span>
</h1>

<i>An extended version of Prisma Schema Builder is in the works</i>
</div>

<hr className="mt-8 max-w-sm border-gray-300 w-full" />

<ul className="flex flex-col gap-3.5 text-xl text-left mt-9">
<li>💾 &nbsp;Save your schemas in the cloud</li>
<li>🖥️ &nbsp;Access to a desktop version of the app</li>
<li>📣 &nbsp;Priority on GitHub issues and features</li>
<li>🌚 &nbsp;Exclusive dark mode color theme</li>
</ul>

<hr className="mt-8 max-w-sm border-gray-300 w-full" />

<p
className="mt-9 text-xl leading-tight font-semibold"
style={{ lineHeight: 1.6 }}
>
How much would you be willing to pay for Prismabuilder++ on a
monthly basis?
</p>

<div className="flex gap-3.5 mt-8">
{PRICES.map((PRICE) => (
<button
key={PRICE}
type="button"
className={cn(
"rounded-lg border p-5 flex flex-col items-center transition",
PRICE === price
? "border-indigo-500 bg-indigo-500 text-white"
: "border-gray-500 hover:bg-gray-200 hover:border-gray-600"
)}
onClick={() => {
setPrice(PRICE === price ? undefined : PRICE);
}}
>
<p className="text-3xl font-medium tracking-wide">
${PRICE} <span className="text-xs font-medium">/month</span>
</p>
</button>
))}
</div>

<div className="flex w-full mt-10">
<Button
fillParent
variant="secondary"
onClick={() => {
onClose(price);
}}
>
Close
</Button>
</div>
</div>
</div>
</Modal>
);
};

export default PricingModal;
76 changes: 49 additions & 27 deletions components/UpdateField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Modal from "./Modal";
import { Button, Select, Separator, TextField } from "@prisma/lens";
import { Button, Select, TextField } from "@prisma/lens";
import { TYPES } from "../lib/fields";
import { Field, FieldType, Model } from "../lib/types";
import { PRISMA_DEFAULT_VALUES } from "../lib/prisma";
Expand Down Expand Up @@ -56,6 +56,17 @@ const UpdateField = ({

const enumType = schema.enums.find((e) => e.name === type && e.fields.length);

const defaultDefaultValues =
enumType?.fields?.map((field) => ({
description: "",
value: field,
label: field,
})) || PRISMA_DEFAULT_VALUES(type);

const [isCustomDefaultValue, setIsCustomDefaultValue] = useState<boolean>(
!defaultDefaultValues.some((dv) => dv.value === defaultValue)
);

return (
<Modal
open={open}
Expand Down Expand Up @@ -111,33 +122,44 @@ const UpdateField = ({
</Select.Option>
))}
</Select.Container>
{enumType || PRISMA_DEFAULT_VALUES(type).length ? (
<Select.Container
defaultSelectedKey={defaultValue}
onSelectionChange={(key) => {

<Select.Container
defaultSelectedKey={isCustomDefaultValue ? "custom" : defaultValue}
onSelectionChange={(key) => {
if (key === "custom") {
setIsCustomDefaultValue(true);
setDefaultValue("");
} else {
setIsCustomDefaultValue(false);
setDefaultValue(key);
}}
hint="A Prisma default value function"
label="Default value"
key={defaultValue}
>
<Select.Option key="">No default value</Select.Option>
{(
enumType?.fields?.map((field) => ({
description: "",
value: field,
label: field,
})) || PRISMA_DEFAULT_VALUES(type)
).map((defaultValue) => (
<Select.Option
description={defaultValue.description}
key={defaultValue.value}
>
{defaultValue.label}
</Select.Option>
))}
</Select.Container>
) : null}
}
}}
hint="A Prisma default value function"
label="Default value"
key={defaultValue}
>
<Select.Option key="">No default value</Select.Option>
{defaultDefaultValues.map((defaultValue) => (
<Select.Option
description={defaultValue.description}
key={defaultValue.value}
>
{defaultValue.label}
</Select.Option>
))}
<Select.Option key="custom" description="Add a custom default value">
Custom default value
</Select.Option>
</Select.Container>

{isCustomDefaultValue && (
<TextField
label="Custom default value"
onChange={setDefaultValue}
value={defaultValue}
/>
)}

<div className="flex space-x-8 items-start py-2">
<div className="flex flex-col space-y-3">
<label
Expand Down
4 changes: 4 additions & 0 deletions lib/classNames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const cn = (...classes: Array<string | false | undefined>) =>
classes.filter(Boolean).join(" ");

export default cn;
24 changes: 24 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { SchemaContext } from "../lib/context";
import { Toaster } from "react-hot-toast";
import { useEffect, useState } from "react";
import { useRouter } from "next/dist/client/router";
import PricingModal from "../components/PricingModal";
import axios from "axios";

splitbee.init();

Expand All @@ -33,12 +35,17 @@ function MyApp({ Component, pageProps }: AppProps) {
}, [schemas]);

const [hasSeenWelcomeModal, setHasSeenWelcomeModal] = useState<boolean>(true);
const [hasSeenPricingModal, setHasSeenPricingModal] = useState<boolean>(true);

useEffect(() => {
if (localStorage) {
setHasSeenWelcomeModal(
Boolean(localStorage.getItem("hasSeenWelcomeModal"))
);

setHasSeenPricingModal(
Boolean(localStorage.getItem("hasSeenPricingModal"))
);
}
}, []);

Expand All @@ -47,6 +54,21 @@ function MyApp({ Component, pageProps }: AppProps) {
setHasSeenWelcomeModal(true);
};

const onClosePricingModal = (price?: number) => {
localStorage.setItem("hasSeenPricingModal", "true");
setHasSeenPricingModal(true);

if (price) {
try {
axios.post("/api/log", {
channel: "pricing-survey",
event: `Chose $${price}`,
icon: "💸",
});
} catch {}
}
};

const schema = schemas?.find((s) => s.name === router.query.schemaId);
const isOldModelRoute = router.pathname.startsWith("/models");
const isRoot = router.pathname === "/";
Expand All @@ -61,6 +83,8 @@ function MyApp({ Component, pageProps }: AppProps) {

<WelcomeModal open={!hasSeenWelcomeModal} onClose={onCloseWelcomeModal} />

<PricingModal open={!hasSeenPricingModal} onClose={onClosePricingModal} />

<LensProvider>
<SchemaContext.Provider
value={{
Expand Down
29 changes: 29 additions & 0 deletions pages/api/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import axios, { AxiosError } from "axios";
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const response = await axios.post(
"https://api.logsnag.com/v1/log",
{
project: "prisma-schema-builder",
channel: req.body.channel,
event: req.body.event,
icon: req.body.icon,
},
{
headers: {
Authorization: `Bearer ${process.env.LOGSNAG_API_TOKEN}`,
},
}
);

res.send(response?.data);
} catch (e) {
console.log((e as AxiosError).response?.data.validation);
res.status(500).send("Failed to send log");
}
}

1 comment on commit b906840

@vercel
Copy link

@vercel vercel bot commented on b906840 Jan 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.