diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx
index 4b8792c..634c25c 100644
--- a/src/components/Footer.jsx
+++ b/src/components/Footer.jsx
@@ -132,20 +132,35 @@ export default class Footer extends Component {
-
+ {user?.isPro ? (
+
+ ) : (
+
+ )}
{this.props.prefs.isJs13kModeOn ? (
diff --git a/src/components/Panel.jsx b/src/components/Panel.jsx
new file mode 100644
index 0000000..0653837
--- /dev/null
+++ b/src/components/Panel.jsx
@@ -0,0 +1,31 @@
+import { forwardRef } from 'preact/compat';
+
+export const Panel = forwardRef(function Panel(
+ {
+ classes = '',
+ padding = '2rem',
+ fullWidth = true,
+ fullHeight = false,
+ glowing = false,
+ topFocus = false,
+ onlyBorder = false,
+ children
+ },
+ ref
+) {
+ return (
+
+ {children}
+
+ );
+});
diff --git a/src/components/Profile.jsx b/src/components/Profile.jsx
index 7a1050b..ded7110 100644
--- a/src/components/Profile.jsx
+++ b/src/components/Profile.jsx
@@ -1,43 +1,113 @@
-import { h } from 'preact';
+import { useState, useEffect } from 'preact/hooks';
import { ProBadge } from './ProBadge';
import { HStack, Stack, VStack } from './Stack';
+import { Panel } from './Panel';
+import { Text } from './Text';
+import { getHumanReadableDate } from '../utils';
const DEFAULT_PROFILE_IMG =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z'/%3E%3C/svg%3E";
-export function Profile({ user, logoutBtnHandler }) {
+const Header = ({ user, logoutBtnHandler }) => {
return (
-
-

+
+
+
-
-
-
+
+
{user && user.displayName ? user.displayName : 'Anonymous Creator'}
- {user.isPro && (
-
-
-
- )}
+ {user.isPro && }
+
-
-
-
-
-
+
+
+ );
+};
+
+export function Profile({ user, logoutBtnHandler }) {
+ const [currentSubscription, setCurrentSubscription] = useState(null);
+ useEffect(() => {
+ if (user?.isPro) {
+ window.db.getUserSubscriptionEvents(user.uid).then(events => {
+ console.log(events);
+ const creationEvent = events
+ .filter(event => event.type === 'subscription_created')
+ .sort((a, b) => b.timestamp.seconds - a.timestamp.seconds)[0];
+ if (creationEvent) {
+ console.log(creationEvent);
+ creationEvent.attributes = creationEvent.data.data.attributes;
+ setCurrentSubscription(creationEvent);
+ }
+ });
+ }
+ }, [user]);
+ return (
+
+
+ {currentSubscription ? (
+
+
+
+ Plan:
+
+ {' '}
+ {currentSubscription.attributes.product_name}
+
+
+
+ Subscription Status:{' '}
+ {currentSubscription.attributes.status}
+
+
+
+ Renews on:{' '}
+
+ {getHumanReadableDate(currentSubscription.attributes.renews_at)}
+
+
+
+
+ Cancel subscription
+
+ {/*
+ Link 2
+
+
+ Link 3
+ */}
+
+
+ ) : null}
+
);
}
diff --git a/src/components/Text.jsx b/src/components/Text.jsx
index c6db902..86e44a6 100644
--- a/src/components/Text.jsx
+++ b/src/components/Text.jsx
@@ -32,7 +32,7 @@ const sizes = {
export const Text = forwardRef(
(
{
- size = 0,
+ size = 1,
weight = 'normal',
tag,
style = 'normal',
diff --git a/src/components/app.jsx b/src/components/app.jsx
index a73dfed..5ff0fcc 100644
--- a/src/components/app.jsx
+++ b/src/components/app.jsx
@@ -1134,8 +1134,13 @@ export default class App extends Component {
return false;
}
proBtnClickHandler() {
- this.setState({ isProModalOpen: true });
- trackEvent('ui', 'proBtnClick');
+ if (user?.isPro) {
+ this.setState({ isProfileModalOpen: true });
+ trackEvent('ui', 'manageProBtnClick');
+ } else {
+ this.setState({ isProModalOpen: true });
+ trackEvent('ui', 'proBtnClick');
+ }
}
codepenBtnClickHandler(e) {
if (this.state.currentItem.cssMode === CssModes.ACSS) {
diff --git a/src/db.js b/src/db.js
index ff0c660..233d80e 100644
--- a/src/db.js
+++ b/src/db.js
@@ -5,6 +5,24 @@ import { deferred } from './deferred';
import { trackEvent } from './analytics';
import { log } from './utils';
+/**
+ * Converts a firestore query snapshot into native array
+ * @param {snapshot} querySnapshot Snapshot object returned by a firestore query
+ */
+function getArrayFromQuerySnapshot(querySnapshot) {
+ const arr = [];
+ querySnapshot.forEach(doc => {
+ // doc.data() has to be after doc.id because docs can have `id` key in them which
+ // should override the explicit `id` being set
+ arr.push({
+ id: doc.id,
+ ...doc.data()
+ });
+ // documentCache[doc.id] = doc.data()
+ });
+ return arr;
+}
+
(() => {
const FAUX_DELAY = 1;
@@ -179,6 +197,15 @@ import { log } from './utils';
});
}
+ async function getUserSubscriptionEvents(userId) {
+ const remoteDb = await getDb();
+ return remoteDb
+ .collection('subscriptions')
+ .where('userId', '==', userId)
+ .get()
+ .then(getArrayFromQuerySnapshot);
+ }
+
window.db = {
getDb,
getUser,
@@ -187,6 +214,7 @@ import { log } from './utils';
getSettings,
fetchItem,
getPublicItemCount,
+ getUserSubscriptionEvents,
local: dbLocalAlias,
sync: dbSyncAlias
};
diff --git a/src/style.css b/src/style.css
index 92896a5..6780108 100644
--- a/src/style.css
+++ b/src/style.css
@@ -2223,6 +2223,60 @@ while the theme CSS file is loading */
box-shadow: var(--shadow-elevation-low);
}
+.profile-modal__avatar-img.is-pro {
+ background: linear-gradient(45deg, var(--color-pro-1), var(--color-pro-2));
+ padding: 0.2rem;
+ animation: avatar-rotate 2s forwards;
+}
+@keyframes avatar-rotate {
+ 0% {
+ transform: rotate(10deg);
+ }
+ 100% {
+ transform: rotate(0deg);
+ }
+}
+.profile-modal__name.is-pro {
+ background: linear-gradient(45deg, var(--color-pro-1), var(--color-pro-2));
+ color: transparent;
+ background-clip: text;
+}
+
+/* .PANEL */
+.panel {
+ --panel-bg: rgb(255 255 255 / 5%);
+ position: relative;
+ background: var(--panel-bg);
+ box-shadow: var(--panel-shadow);
+ border-radius: 1rem;
+ /* backdrop-filter: blur(20px); */
+ overflow: hidden;
+}
+
+.panelOnlyBorder {
+ background: none;
+ box-shadow: none;
+ backdrop-filter: none;
+ border: 2px solid var(--clr-border-1);
+}
+
+.panelGlowing {
+ box-shadow: var(--glow-shadow);
+}
+
+.panelTopFocus {
+ background: radial-gradient(
+ 82.25% 100% at 50% 0%,
+ rgba(var(--rgb-gray-1), 0.75) 37.28%,
+ rgba(var(--rgb-gray-0), 0) 100%
+ );
+
+ box-shadow:
+ 0 0 30px rgba(var(--rgb-brand), 0),
+ 0px 20px 50px rgba(0, 0, 0, 0.1),
+ inset 0px 1px 3px rgba(255, 255, 255, 0.1);
+}
+
@media screen and (max-width: 600px) {
body {
font-size: 70%;
diff --git a/src/utils.js b/src/utils.js
index 7b18399..f903b04 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -205,6 +205,41 @@ export function getHumanDate(timestamp) {
return retVal;
}
+/**
+ * Convert any date-ish string/obj to human readable form -> Jul 02, 2021
+ * @param {string?object} date date to be formatted
+ * @returns string
+ */
+export function getHumanReadableDate(
+ date,
+ { showTime = true, utc = false } = {}
+) {
+ if (!date) return '';
+ let d = typeof date.toDate === 'function' ? date.toDate() : new Date(date);
+ if (utc) {
+ d = new Date(
+ Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours())
+ );
+ }
+
+ let options = {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric'
+ };
+ if (showTime) {
+ options = {
+ ...options,
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: true
+ };
+ }
+ const dateTimeString = d.toLocaleString(false, options);
+ return dateTimeString;
+}
+
// create a one-time event
export function once(node, type, callback) {
// create event