1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-27 00:30:09 +02:00

add subs details in profile modal

This commit is contained in:
Kushagra Gour
2024-03-19 15:53:02 +05:30
parent 22a4361baa
commit 14bc964aa4
8 changed files with 284 additions and 46 deletions

View File

@@ -132,6 +132,20 @@ export default class Footer extends Component {
<use xlinkHref="#twitter-icon" /> <use xlinkHref="#twitter-icon" />
</svg> </svg>
</a> </a>
{user?.isPro ? (
<Button
onClick={this.props.proBtnClickHandler}
data-event-category="ui"
data-event-action="manageProFooterBtnClick"
class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile support-link"
aria-label={i18n._(t`Manage your PRO subscription`)}
>
<HStack gap={1}>
<Trans>Manage</Trans>
<ProBadge />
</HStack>
</Button>
) : (
<Button <Button
onClick={this.props.proBtnClickHandler} onClick={this.props.proBtnClickHandler}
data-event-category="ui" data-event-category="ui"
@@ -146,6 +160,7 @@ export default class Footer extends Component {
<ProBadge /> <ProBadge />
</HStack> </HStack>
</Button> </Button>
)}
</div> </div>
{this.props.prefs.isJs13kModeOn ? ( {this.props.prefs.isJs13kModeOn ? (

31
src/components/Panel.jsx Normal file
View File

@@ -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 (
<div
ref={ref}
style={{
padding: padding,
width: fullWidth ? '100%' : 'auto',
height: fullHeight ? '100%' : 'auto'
}}
className={`panel ${classes} ${glowing && 'panelGlowing'} ${
topFocus && 'panelTopFocus'
} ${onlyBorder && 'panelOnlyBorder'}`}
>
{children}
</div>
);
});

View File

@@ -1,34 +1,36 @@
import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks';
import { ProBadge } from './ProBadge'; import { ProBadge } from './ProBadge';
import { HStack, Stack, VStack } from './Stack'; import { HStack, Stack, VStack } from './Stack';
import { Panel } from './Panel';
import { Text } from './Text';
import { getHumanReadableDate } from '../utils';
const DEFAULT_PROFILE_IMG = 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"; "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 ( return (
<div class="tac"> <Stack gap={5}>
<Stack gap={2} align="center">
<img <img
height="80" height="80"
class="profile-modal__avatar-img" class={`profile-modal__avatar-img ${user?.isPro ? 'is-pro' : ''}`}
src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''} src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''}
id="profileAvatarImg" id="profileAvatarImg"
alt="Profile image" alt="Profile image"
/> />
<VStack gap={4}> <VStack gap={1} align="flex-start">
<VStack gap={1}> <h3
<h3 id="profileUserName"> class={`profile-modal__name ${user?.isPro ? 's-pro' : ''}`}
id="profileUserName"
>
{user && user.displayName ? user.displayName : 'Anonymous Creator'} {user && user.displayName ? user.displayName : 'Anonymous Creator'}
</h3> </h3>
{user.isPro && ( {user.isPro && <ProBadge />}
<Stack justify="center">
<ProBadge />
</Stack>
)}
</VStack> </VStack>
</Stack>
<HStack>
<button <button
class="btn btn--primary" class="btn btn--primary"
aria-label="Logout from your account" aria-label="Logout from your account"
@@ -36,8 +38,76 @@ export function Profile({ user, logoutBtnHandler }) {
> >
Logout Logout
</button> </button>
</HStack> </Stack>
);
};
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 (
<VStack gap={2}>
<Header user={user} logoutBtnHandler={logoutBtnHandler} />
{currentSubscription ? (
<Panel>
<VStack align="stretch" gap={1}>
<Text>
Plan:
<Text weight="700">
{' '}
{currentSubscription.attributes.product_name}
</Text>
</Text>
<Text>
Subscription Status:{' '}
<Text weight="700">{currentSubscription.attributes.status}</Text>
</Text>
<Text>
Renews on:{' '}
<Text weight="700">
{getHumanReadableDate(currentSubscription.attributes.renews_at)}
</Text>
</Text>
<a
target="_blank"
href={currentSubscription.attributes.urls.customer_portal}
>
Cancel subscription
</a>
{/* <a
target="_blank"
href={
currentSubscription.attributes.urls
.customer_portal_update_subscription
}
>
Link 2
</a>
<a
target="_blank"
href={currentSubscription.attributes.urls.update_payment_method}
>
Link 3
</a> */}
</VStack>
</Panel>
) : null}
</VStack> </VStack>
</div>
); );
} }

View File

@@ -32,7 +32,7 @@ const sizes = {
export const Text = forwardRef( export const Text = forwardRef(
( (
{ {
size = 0, size = 1,
weight = 'normal', weight = 'normal',
tag, tag,
style = 'normal', style = 'normal',

View File

@@ -1134,9 +1134,14 @@ export default class App extends Component {
return false; return false;
} }
proBtnClickHandler() { proBtnClickHandler() {
if (user?.isPro) {
this.setState({ isProfileModalOpen: true });
trackEvent('ui', 'manageProBtnClick');
} else {
this.setState({ isProModalOpen: true }); this.setState({ isProModalOpen: true });
trackEvent('ui', 'proBtnClick'); trackEvent('ui', 'proBtnClick');
} }
}
codepenBtnClickHandler(e) { codepenBtnClickHandler(e) {
if (this.state.currentItem.cssMode === CssModes.ACSS) { if (this.state.currentItem.cssMode === CssModes.ACSS) {
alert( alert(

View File

@@ -5,6 +5,24 @@ import { deferred } from './deferred';
import { trackEvent } from './analytics'; import { trackEvent } from './analytics';
import { log } from './utils'; 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; 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 = { window.db = {
getDb, getDb,
getUser, getUser,
@@ -187,6 +214,7 @@ import { log } from './utils';
getSettings, getSettings,
fetchItem, fetchItem,
getPublicItemCount, getPublicItemCount,
getUserSubscriptionEvents,
local: dbLocalAlias, local: dbLocalAlias,
sync: dbSyncAlias sync: dbSyncAlias
}; };

View File

@@ -2223,6 +2223,60 @@ while the theme CSS file is loading */
box-shadow: var(--shadow-elevation-low); 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) { @media screen and (max-width: 600px) {
body { body {
font-size: 70%; font-size: 70%;

View File

@@ -205,6 +205,41 @@ export function getHumanDate(timestamp) {
return retVal; 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 // create a one-time event
export function once(node, type, callback) { export function once(node, type, callback) {
// create event // create event