+
-
- Figma library
-
- {" / "}
-
- plugin
-
+
+ Figma library
+
+ {" / "}
+
+ plugin
+
@@ -66,7 +73,11 @@ const Links: React.FC = () => {
*/}
diff --git a/src/components/Modal/DonationModal.tsx b/src/components/Modal/DonationModal.tsx
new file mode 100644
index 0000000..f5eb029
--- /dev/null
+++ b/src/components/Modal/DonationModal.tsx
@@ -0,0 +1,68 @@
+import React, { useState } from "react";
+import { useSetRecoilState } from "recoil";
+import { X } from "phosphor-react";
+
+import { modalOpenAtom } from "../../state/atoms";
+import DonationStepMethod from "./DonationStepMethod";
+import DonationStepDropin from "./DonationStepDropin";
+import DonationStepThanks from "./DonationStepThanks";
+
+const routes = [DonationStepMethod, DonationStepDropin, DonationStepThanks];
+
+export interface StepProps {
+ previousStep: () => void;
+ nextStep: () => void;
+ close: () => void;
+}
+
+interface StepperProps {
+ routes: Array
>;
+ routeProps: any;
+ close: () => void;
+}
+
+const Stepper: React.FC = ({ routes, routeProps, close }) => {
+ const [currentStep, setCurrentStep] = useState(0);
+
+ const previousStep = () => {
+ if (currentStep <= 0) return;
+ setTimeout(() => setCurrentStep((c) => c - 1), 500);
+ };
+
+ const nextStep = () => {
+ if (currentStep >= routes.length - 1) return;
+ setTimeout(() => setCurrentStep((c) => c + 1), 500);
+ };
+
+ const Component = routes[currentStep];
+
+ return (
+ <>
+
+ >
+ );
+};
+
+const DonationModal: React.FC<{}> = () => {
+ const setModalOpen = useSetRecoilState(modalOpenAtom);
+ const close = () => setModalOpen(false);
+
+ return (
+
+
+
+
+ );
+};
+
+export default DonationModal;
diff --git a/src/components/Modal/DonationStepCC.tsx b/src/components/Modal/DonationStepCC.tsx
new file mode 100644
index 0000000..d250dc6
--- /dev/null
+++ b/src/components/Modal/DonationStepCC.tsx
@@ -0,0 +1,80 @@
+import React, { useRef, useEffect } from "react";
+import { client, hostedFields, Client, HostedFields } from "braintree-web";
+
+const BT_STYLES = {
+ ":focus": {
+ color: "blue",
+ },
+ ".valid": {
+ color: "green",
+ },
+ ".invalid": {
+ color: "red",
+ },
+};
+const BT_PAYMENT_FIELDS = {
+ number: {
+ container: "#card-number",
+ placeholder: "4111 1111 1111 1111",
+ },
+ cvv: {
+ container: "#cvv",
+ placeholder: "123",
+ },
+ expirationDate: {
+ container: "#expiration-date",
+ placeholder: "10/2022",
+ },
+};
+
+const PaymentModal: React.FC<{}> = () => {
+ const bt = useRef();
+ const form = useRef();
+
+ const handleSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+ if (!form.current || !bt.current) return;
+
+ const response = await form.current.tokenize();
+ console.log({ response });
+ };
+
+ useEffect(() => {
+ const initializeBt = async () => {
+ try {
+ bt.current = await client.create({
+ authorization: "sandbox_246jdjxq_8h7hm5rvngkykjds",
+ });
+
+ form.current = await hostedFields.create({
+ client: bt.current!!,
+ fields: BT_PAYMENT_FIELDS,
+ styles: BT_STYLES,
+ });
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ initializeBt();
+ }, []);
+
+ return (
+
+ );
+};
+
+export default PaymentModal;
diff --git a/src/components/Modal/DonationStepDropin.tsx b/src/components/Modal/DonationStepDropin.tsx
new file mode 100644
index 0000000..ca13cd4
--- /dev/null
+++ b/src/components/Modal/DonationStepDropin.tsx
@@ -0,0 +1,85 @@
+import React, { useRef, useState, useEffect } from "react";
+import dropin, { Dropin } from "braintree-web-drop-in";
+import { StepProps } from "./DonationModal";
+
+const BT_PAYMENT_FIELDS = {
+ number: {
+ placeholder: "4111 1111 1111 1111",
+ },
+ cvv: {
+ placeholder: "123",
+ },
+ expirationDate: {
+ placeholder: "10/22",
+ },
+ cardholderName: {
+ placeholder: "Person McFace",
+ },
+};
+
+const PaymentModal: React.FC = ({ previousStep }) => {
+ const instance = useRef();
+ const [isValid, setIsValid] = useState(false);
+
+ const submit = async () => {
+ if (!instance.current) return;
+ const payload = await instance.current.requestPaymentMethod();
+ console.log({ payload });
+ };
+
+ useEffect(() => {
+ const initializePayments = async () => {
+ try {
+ instance.current = await dropin.create({
+ authorization: "sandbox_246jdjxq_8h7hm5rvngkykjds",
+ container: "#braintree-dropin",
+ card: {
+ cardholderName: {
+ required: true,
+ },
+ overrides: {
+ fields: BT_PAYMENT_FIELDS,
+ },
+ },
+ paypal: {
+ flow: "checkout",
+ amount: "10.00",
+ currency: "USD",
+ commit: true,
+ },
+ paypalCredit: {
+ flow: "checkout",
+ amount: "10.00",
+ currency: "USD",
+ commit: true,
+ },
+ venmo: { allowNewBrowserTab: false },
+ });
+ instance.current.on("paymentMethodRequestable", () => setIsValid(true));
+ instance.current.on("noPaymentMethodRequestable", () =>
+ setIsValid(false)
+ );
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ initializePayments();
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
+
+export default PaymentModal;
diff --git a/src/components/Modal/DonationStepMethod.tsx b/src/components/Modal/DonationStepMethod.tsx
new file mode 100644
index 0000000..236119e
--- /dev/null
+++ b/src/components/Modal/DonationStepMethod.tsx
@@ -0,0 +1,131 @@
+import React, { useState } from "react";
+
+import { StepProps } from "./DonationModal";
+
+enum DonationAmount {
+ FIVE_DOLLARS = 5,
+ TEN_DOLLARS = 10,
+ TWENTY_DOLLARS = 20,
+ FIFTY_DOLLARS = 50,
+ ONE_HUNDRED_DOLLARS = 100,
+ CUSTOM = -1,
+}
+
+const DonationStepMethod: React.FC = ({
+ previousStep,
+ nextStep,
+ close,
+}) => {
+ const [donationType, setDonationType] = useState();
+ const [donationAmount, setDonationAmount] = useState(0);
+
+ const onDonationChange = (e: React.ChangeEvent) => {
+ setDonationType(+e.target.value as DonationAmount);
+ if (!(+e.target.value === DonationAmount.CUSTOM))
+ setDonationAmount(+e.target.value);
+ };
+
+ void previousStep;
+
+ return (
+ <>
+
+ Thanks for using Phosphor! Would you like to make a donation to help
+ support our work?
+
+
+
+
+
+
+ >
+ );
+};
+
+export default DonationStepMethod;
diff --git a/src/components/Modal/DonationStepThanks.tsx b/src/components/Modal/DonationStepThanks.tsx
new file mode 100644
index 0000000..0e93b53
--- /dev/null
+++ b/src/components/Modal/DonationStepThanks.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const DonationStepThanks: React.FC<{}> = () => {
+ return null;
+};
+
+export default DonationStepThanks;
diff --git a/src/components/Modal/Modal.css b/src/components/Modal/Modal.css
new file mode 100644
index 0000000..a52a4f4
--- /dev/null
+++ b/src/components/Modal/Modal.css
@@ -0,0 +1,67 @@
+.modal-container {
+ position: fixed;
+ display: grid;
+ place-items: center;
+ inset: 0;
+ background-color: rgba(0, 0, 0, 0.4);
+ z-index: 10;
+}
+
+.modal-content {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 400px;
+ min-height: 600px;
+ /* background-color: #ffe8dc; */
+ background-color: white;
+ border-radius: 8px;
+ border: 2px solid black;
+ padding: 24px;
+}
+
+@media screen and (max-width: 480px) {
+ .modal-content {
+ min-width: 90%;
+ min-height: 100%;
+ border: none;
+ border-radius: 0;
+ }
+}
+
+.step-button-container {
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-end;
+ justify-content: center;
+}
+
+button.modal-close-button {
+ position: absolute;
+ top: 24px;
+ right: 24px;
+ background: transparent;
+ padding: 0;
+ cursor: pointer;
+ box-sizing: content-box;
+ z-index: 1;
+}
+
+.payment-methods {
+ display: grid;
+ gap: 16px;
+ grid-template-rows: repeat(2, 1fr);
+ grid-template-columns: repeat(2, 1fr);
+}
+
+.payment-methods button {
+ margin: 0;
+ padding: 0;
+ justify-content: center;
+ height: 96px;
+}
+
+.payment-methods button > svg {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx
new file mode 100644
index 0000000..9c0746b
--- /dev/null
+++ b/src/components/Modal/Modal.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { useRecoilState } from "recoil";
+import { useLockBodyScroll } from "react-use";
+import { useHotkeys } from "react-hotkeys-hook";
+
+import { modalOpenAtom } from "../../state/atoms";
+import DonationModal from "./DonationModal";
+
+import "./Modal.css";
+
+const Modal: React.FC<{}> = () => {
+ const [isModalOpen, setModalOpen] = useRecoilState(modalOpenAtom);
+ useHotkeys("esc", () => isModalOpen && setModalOpen(false), [isModalOpen]);
+ useLockBodyScroll(isModalOpen);
+
+ if (!isModalOpen) return null;
+
+ return (
+
+
+
+ );
+};
+
+export default Modal;
diff --git a/src/state/atoms.ts b/src/state/atoms.ts
index 3ec5a2e..7a2a5bb 100644
--- a/src/state/atoms.ts
+++ b/src/state/atoms.ts
@@ -25,3 +25,8 @@ export const iconPreviewOpenAtom = atom({
key: "iconPreviewOpenAtom",
default: false,
});
+
+export const modalOpenAtom = atom({
+ key: "modalOpenAtom",
+ default: true,
+});