Skip to content

Commit

Permalink
feat: add user blocking functionality to web3 (#1322)
Browse files Browse the repository at this point in the history
  • Loading branch information
e-schneid committed May 26, 2022
1 parent 8f98d27 commit 5803876
Show file tree
Hide file tree
Showing 19 changed files with 316 additions and 72 deletions.
14 changes: 12 additions & 2 deletions packages/api/src/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as JWT from './utils/jwt.js'
import { JSONResponse } from './utils/json-response.js'
import { JWT_ISSUER } from './constants.js'
import { HTTPError } from './errors.js'
import { getTagValue, hasTag } from './utils/tags.js'

/**
* @typedef {{ _id: string, issuer: string }} User
Expand Down Expand Up @@ -124,9 +125,18 @@ export async function userAccountGet (request, env) {
* @param {import('./env').Env} env
*/
export async function userInfoGet (request, env) {
const info = await env.db.getUser(request.auth.user.issuer)
const user = await env.db.getUser(request.auth.user.issuer, { includeTags: true })

return new JSONResponse({
info
info: {
...user,
tags: {
HasAccountRestriction: hasTag(user, 'HasAccountRestriction', 'true'),
HasPsaAccess: hasTag(user, 'HasPsaAccess', 'true'),
HasSuperHotAccess: hasTag(user, 'HasSuperHotAccess', 'true'),
StorageLimitBytes: getTagValue(user, 'StorageLimitBytes', '')
}
}
})
}

Expand Down
14 changes: 14 additions & 0 deletions packages/api/src/utils/tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function getTagValue (user, tagName, defaultValue) {
return (
user.tags?.find((tag) => tag.tag === tagName && !tag.deleted_at)?.value ||
defaultValue
)
}

export function hasTag (user, tagName, value) {
return Boolean(
user.tags?.find(
(tag) => tag.tag === tagName && tag.value === value && !tag.deleted_at
)
)
}
16 changes: 14 additions & 2 deletions packages/db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ const userQuery = `
updated:updated_at
`

const userQueryWithTags = `
_id:id::text,
issuer,
name,
email,
github,
publicAddress:public_address,
created:inserted_at,
updated:updated_at,
tags:user_tag_user_id_fkey(user_id,id,tag,value)
`

const psaPinRequestTableName = 'psa_pin_request'
const pinRequestSelect = `
_id:id::text,
Expand Down Expand Up @@ -126,11 +138,11 @@ export class DBClient {
* @param {string} issuer
* @return {Promise<import('./db-client-types').UserOutput | undefined>}
*/
async getUser (issuer) {
async getUser (issuer, { includeTags } = { includeTags: false }) {
/** @type {{ data: import('./db-client-types').UserOutput[], error: PostgrestError }} */
const { data, error } = await this._client
.from('user')
.select(userQuery)
.select(includeTags ? userQueryWithTags : userQuery)
.eq('issuer', issuer)

if (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState, useEffect } from 'react';
import Link from 'next/link';

import Modal from 'modules/zero/components/modal/modal';
import CloseIcon from 'assets/icons/close';
import Button from 'components/button/button.js';
import GradientBackground from '../../gradientbackground/gradientbackground.js';

const AccountBlockedModal = ({ hasAccountRestriction }) => {
const modalState = useState(false);

useEffect(() => {
if (hasAccountRestriction && !sessionStorage.hasSeenAccountBlockedModal) {
modalState[1](true);
sessionStorage.hasSeenAccountBlockedModal = true;
}
}, [hasAccountRestriction, modalState]);

return (
<div className="account-blocked-modal">
<Modal
className=""
closeIcon={<CloseIcon className="file-uploader-close" />}
modalState={modalState}
showCloseButton
>
<div className="background-view-wrapper">
<GradientBackground variant="upload-cta-gradient" />
</div>
<div className="account-blocked-container">
<p className="content">
You may have been temporarily blocked from uploading new files. You may, however, continue to view and take
actions on existing uploads. If you feel this was a mistake please contact{' '}
<Link href="mailto:support@web3.storage.com">support@web3.storage.com</Link>
</p>

<Button onClick={() => modalState[1](false)} className="confirm">
Confirm
</Button>
</div>
</Modal>
</div>
);
};

export default AccountBlockedModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.account-blocked-container {
position: relative;
color: $ebony;
padding: 3.125rem 8.5rem 3.125rem 8.125rem;

@include medium {
padding: 2.375rem 1.3125rem;
width: 100%;
}

> * {
position: relative;
}

.saturated-variant {
@include mini {
transform: translate(-5rem, 0) scaleY(2) rotate(-40deg) !important;
}
@include tiny {
width: 1000px !important;
}
}

.content {
margin-bottom: 20px;
}
}

.account-blocked-modal {
.modalContainer {
max-width: calc($containerWidth - 10%);
@include borderRadius_Large;
@include medium {
width: 90%;
}
}
.modalClose {
color: $ebony;
font-size: 2rem;
}

svg {
color: $ebony;
}
}

.background-view-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
29 changes: 22 additions & 7 deletions packages/website/components/account/ctaCard/CTACard.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import clsx from 'clsx';

import Button from 'components/button/button';
import Button, { ButtonVariant } from 'components/button/button';
import Tooltip from 'ZeroComponents/tooltip/tooltip';

export const CTAThemeType = {
LIGHT: 'light',
Expand All @@ -25,16 +26,30 @@ export const CTAThemeType = {
const CTACard = ({ className = '', heading, description, ctas, theme = CTAThemeType.LIGHT, background }) => {
return (
<div className={clsx('section cta-card', className, `cta-card__${theme}`)}>
{background}
<div className="cta-card__background">{background}</div>
<h4>{heading}</h4>
<span>{description}</span>
{!!ctas?.length && (
<div className="cta-buttons-container">
{ctas.map(({ onClick = () => null, children: text, ...buttonProps }) => (
<Button key={buttonProps.href || text} onClick={onClick} {...buttonProps}>
{text}
</Button>
))}
{ctas.map(({ onClick = () => null, children: text, ...buttonProps }) => {
const btn = (
<Button
href={buttonProps.href}
key={buttonProps.href || text}
onClick={onClick}
{...buttonProps}
variant={buttonProps.disabled ? ButtonVariant.GRAY : buttonProps.variant}
>
{text}
</Button>
);

if (buttonProps.tooltip) {
return <Tooltip content={buttonProps.tooltip}>{btn}</Tooltip>;
}

return btn;
})}
</div>
)}
</div>
Expand Down
10 changes: 10 additions & 0 deletions packages/website/components/account/ctaCard/CTACard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ $cta-card-padding-top: 5rem;
padding-bottom: 1.5625rem;
@include label_4;
}

&__background {
position: absolute;
border-radius: 0.625rem;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
}

.cta-buttons-container {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const FilesManager = ({ className, content, onFileUpload }) => {
} = useRouter();
const {
storageData: { refetch },
info,
} = useUser();
const [filteredFiles, setFilteredFiles] = useState(files);
const [sortedFiles, setSortedFiles] = useState(filteredFiles);
Expand Down Expand Up @@ -192,13 +193,15 @@ const FilesManager = ({ className, content, onFileUpload }) => {
<div className="files-manager-title has-upload-button">
<div className="title">{content?.heading}</div>
<Button
disabled={info?.tags?.['HasAccountRestriction']}
onClick={onFileUpload}
variant={content?.upload.theme}
tracking={{
ui: countly.ui[content?.upload.ui],
action: content?.upload.action,
data: { isFirstFile: false },
}}
tooltip={info?.tags?.['HasAccountRestriction'] ? content?.upload.accountRestrictedText : ''}
>
{content?.upload.text}
</Button>
Expand Down Expand Up @@ -257,6 +260,8 @@ const FilesManager = ({ className, content, onFileUpload }) => {
action: content?.table.cta.action,
data: { isFirstFile: true },
}}
disabled={info?.tags?.['HasAccountRestriction']}
tooltip={info?.tags?.['HasAccountRestriction'] ? content?.table.cta.accountRestrictedText : ''}
>
{content?.table.cta.text}
</Button>
Expand Down
13 changes: 9 additions & 4 deletions packages/website/components/button/button.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import React, { useCallback } from 'react';

import ZeroButton from 'ZeroComponents/button/button';
import { trackEvent, events } from 'lib/countly';
import Tooltip from 'ZeroComponents/tooltip/tooltip';

export const ButtonVariant = {
GRAY: 'gray',
DARK: 'dark',
LIGHT: 'light',
PURPLE: 'purple',
Expand All @@ -26,6 +28,7 @@ export const ButtonVariant = {
* @prop {React.MouseEventHandler<HTMLButtonElement>} [onClick]
* @prop {string} [className]
* @prop {string} [href]
* @prop {string} [tooltip]
* @prop {TrackingProps} [tracking]
* @prop {string} [variant]
* @prop {React.ReactNode} [children]
Expand All @@ -37,27 +40,29 @@ export const ButtonVariant = {
* @param {ButtonProps & Partial<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'>>} props
* @returns
*/
const Button = ({ className, onClick, href, tracking, variant = ButtonVariant.DARK, children, ...props }) => {
const Button = ({ className, tooltip, onClick, tracking, variant = ButtonVariant.DARK, children, ...props }) => {
const onClickHandler = useCallback(
event => {
tracking &&
trackEvent(tracking.event || events.CTA_LINK_CLICK, {
ui: tracking.ui,
action: tracking.action,
link: href || '',
link: props.href || '',
...(tracking.data || {}),
});
onClick && onClick(event);
},
[href, onClick, tracking]
[props.href, onClick, tracking]
);

return (
const btn = (
// @ts-ignore Ignoring ZeroButton as it is not properly typed
<ZeroButton {...props} className={clsx('button', variant, className)} onClick={onClickHandler}>
{children}
</ZeroButton>
);

return tooltip ? <Tooltip content={tooltip}>{btn}</Tooltip> : btn;
};

export default Button;
Loading

0 comments on commit 5803876

Please sign in to comment.