add icon for paid patreon sub

This commit is contained in:
Paul Mineev
2022-04-13 03:40:39 -05:00
committed by Umputun
parent 001b994e73
commit c987513886
17 changed files with 123 additions and 109 deletions

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 510 B

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 741 B

Before

Width:  |  Height:  |  Size: 741 B

After

Width:  |  Height:  |  Size: 741 B

Before

Width:  |  Height:  |  Size: 500 B

After

Width:  |  Height:  |  Size: 500 B

Before

Width:  |  Height:  |  Size: 240 B

After

Width:  |  Height:  |  Size: 240 B

Before

Width:  |  Height:  |  Size: 139 B

After

Width:  |  Height:  |  Size: 139 B

Before

Width:  |  Height:  |  Size: 393 B

After

Width:  |  Height:  |  Size: 393 B

Before

Width:  |  Height:  |  Size: 531 B

After

Width:  |  Height:  |  Size: 531 B

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 388 B

+1
View File
@@ -7,6 +7,7 @@ export type User = {
block: boolean;
verified: boolean;
email_subscription?: boolean;
paid_sub?: boolean;
};
/** data which is used on user-info page */
@@ -1,19 +1,19 @@
export const OAUTH_DATA = {
facebook: require('./assets/facebook.svg').default as string,
twitter: require('./assets/twitter.svg').default as string,
patreon: require('./assets/patreon.svg').default as string,
google: require('./assets/google.svg').default as string,
microsoft: require('./assets/microsoft.svg').default as string,
yandex: require('./assets/yandex.svg').default as string,
dev: require('./assets/dev.svg').default as string,
facebook: require('assets/social/facebook.svg').default as string,
twitter: require('assets/social/twitter.svg').default as string,
patreon: require('assets/social/patreon.svg').default as string,
google: require('assets/social/google.svg').default as string,
microsoft: require('assets/social/microsoft.svg').default as string,
yandex: require('assets/social/yandex.svg').default as string,
dev: require('assets/social/dev.svg').default as string,
github: {
name: 'GitHub',
icons: {
light: require('./assets/github-light.svg').default as string,
dark: require('./assets/github-dark.svg').default as string,
light: require('assets/social/github-light.svg').default as string,
dark: require('assets/social/github-dark.svg').default as string,
},
},
telegram: require('./assets/telegram.svg').default as string,
telegram: require('assets/social/telegram.svg').default as string,
} as const;
export const OAUTH_PROVIDERS = Object.keys(OAUTH_DATA);
@@ -0,0 +1,8 @@
.user {
display: flex;
align-items: center;
}
.user > * + * {
margin-left: 4px;
}
@@ -1,16 +1,8 @@
import { h } from 'preact';
import { mount } from 'enzyme';
jest.mock('react-intl', () => {
const messages = require('locales/en.json');
const reactIntl = jest.requireActual('react-intl');
const intlProvider = new reactIntl.IntlProvider({ locale: 'en', messages }, {});
return {
...reactIntl,
useIntl: () => intlProvider.state.intl,
};
});
import '@testing-library/jest-dom';
import { screen } from '@testing-library/preact';
import { render } from 'tests/utils';
import { useIntl, IntlProvider } from 'react-intl';
@@ -21,46 +13,58 @@ import { sleep } from 'utils/sleep';
import { Comment, CommentProps } from './comment';
function mountComment(props: CommentProps) {
function Wrapper(updateProps: Partial<CommentProps> = {}) {
const intl = useIntl();
return (
<IntlProvider locale="en" messages={enMessages}>
<Comment {...props} {...updateProps} intl={intl} />
</IntlProvider>
);
}
return mount(<Wrapper />);
function CommentWithIntl(props: CommentProps) {
return <Comment {...props} intl={useIntl()} />;
}
const DefaultProps: Partial<CommentProps> = {
post_info: {
read_only: false,
} as PostInfo,
view: 'main',
data: {
text: 'test comment',
vote: 0,
user: {
id: 'someone',
picture: 'somepicture-url',
},
time: new Date().toString(),
locator: {
url: 'somelocatorurl',
site: 'remark',
},
} as CommentType,
user: {
admin: false,
id: 'testuser',
picture: 'somepicture-url',
} as User,
} as CommentProps;
// @depricated
function mountComment(props: CommentProps) {
return mount(
<IntlProvider locale="en" messages={enMessages}>
<CommentWithIntl {...props} />
</IntlProvider>
);
}
function getDefaultProps() {
return {
post_info: {
read_only: false,
} as PostInfo,
view: 'main',
data: {
text: 'test comment',
vote: 0,
user: {
id: 'someone',
picture: 'somepicture-url',
} as User,
time: new Date().toString(),
locator: {
url: 'somelocatorurl',
site: 'remark',
},
} as CommentType,
user: {
admin: false,
id: 'testuser',
picture: 'somepicture-url',
} as User,
} as CommentProps;
}
const DefaultProps = getDefaultProps();
describe('<Comment />', () => {
it('should render patreon subscriber icon', async () => {
const props = getDefaultProps() as CommentProps;
props.data.user.paid_sub = true;
render(<CommentWithIntl {...props} />);
const patreonSubscriberIcon = await screen.findByAltText('Patreon Paid Subscriber');
expect(patreonSubscriberIcon).toBeVisible();
expect(patreonSubscriberIcon.tagName).toBe('IMG');
});
describe('voting', () => {
it('should be disabled for an anonymous user', () => {
const wrapper = mountComment({ ...DefaultProps, user: { id: 'anonymous_1' } } as CommentProps);
@@ -243,37 +247,24 @@ describe('<Comment />', () => {
expect(controls.hasClass('comment__verification_clickable')).toEqual(false);
});
it('should be editable', () => {
it('should be editable', async () => {
StaticStore.config.edit_duration = 300;
const initTime = new Date().toString();
const changedTime = new Date(Date.now() + 10 * 1000).toString();
const props = {
...DefaultProps,
user: DefaultProps.user as User,
data: {
...DefaultProps.data,
id: '100',
user: DefaultProps.user as User,
vote: 1,
time: initTime,
delete: false,
orig: 'test',
} as CommentType,
repliesCount: 0,
} as CommentProps;
const component = mountComment(props);
const comment = component.find(Comment);
const props = getDefaultProps();
props.repliesCount = 0;
props.user!.id = '100';
props.data.user.id = '100';
Object.assign(props.data, {
id: '101',
vote: 1,
time: new Date().toString(),
delete: false,
orig: 'test',
});
expect((comment.state('editDeadline') as Date).getTime()).toBe(
new Date(new Date(initTime).getTime() + 300 * 1000).getTime()
);
component.setProps({ data: { ...props.data, time: changedTime } });
expect((comment.state('editDeadline') as Date).getTime()).toBe(
new Date(new Date(changedTime).getTime() + 300 * 1000).getTime()
);
render(<CommentWithIntl {...props} />);
// it can be less than 300 due to test checks time
expect(['299', '300']).toContain(screen.getByRole('timer').innerText);
});
it('should not be editable', () => {
+36 -22
View File
@@ -22,6 +22,7 @@ import { getVoteMessage, VoteMessagesTypes } from './getVoteMessage';
import { getBlockingDurations } from './getBlockingDurations';
import { boundActions } from './connected-comment';
import style from './comment.module.css';
import './styles';
export type CommentProps = {
@@ -79,7 +80,7 @@ export class Comment extends Component<CommentProps, State> {
/** comment text node. Used in comment text copying */
textNode = createRef<HTMLDivElement>();
updateState = (props: CommentProps) => {
updateState(props: CommentProps) {
const newState: Partial<State> = {
scoreDelta: props.data.vote,
cachedScore: props.data.score,
@@ -103,7 +104,7 @@ export class Comment extends Component<CommentProps, State> {
}
return newState;
};
}
state = {
renderDummy: typeof this.props.inView === 'boolean' ? !this.props.inView : false,
@@ -595,26 +596,35 @@ export class Comment extends Component<CommentProps, State> {
<Avatar url={o.user.picture} />
</div>
)}
{props.view !== 'user' && (
<button onClick={() => this.toggleUserInfoVisibility()} className="comment__username">
{o.user.name}
</button>
)}
{isAdmin && props.view !== 'user' && (
<span
{...getHandleClickProps(this.toggleVerify)}
aria-label={intl.formatMessage(messages.toggleVerification)}
title={intl.formatMessage(o.user.verified ? messages.verifiedUser : messages.unverifiedUser)}
className={b('comment__verification', {}, { active: o.user.verified, clickable: true })}
/>
)}
{!isAdmin && !!o.user.verified && props.view !== 'user' && (
<span
title={intl.formatMessage(messages.verifiedUser)}
className={b('comment__verification', {}, { active: true })}
/>
)}
<div className={style.user}>
{props.view !== 'user' && (
<button onClick={() => this.toggleUserInfoVisibility()} className="comment__username">
{o.user.name}
</button>
)}
{o.user.paid_sub && (
<img
width={12}
height={12}
src={require('assets/social/patreon.svg').default}
alt={intl.formatMessage(messages.paidPatreon)}
/>
)}
{isAdmin && props.view !== 'user' && (
<span
{...getHandleClickProps(this.toggleVerify)}
aria-label={intl.formatMessage(messages.toggleVerification)}
title={intl.formatMessage(o.user.verified ? messages.verifiedUser : messages.unverifiedUser)}
className={b('comment__verification', {}, { active: o.user.verified, clickable: true })}
/>
)}
{!isAdmin && !!o.user.verified && props.view !== 'user' && (
<span
title={intl.formatMessage(messages.verifiedUser)}
className={b('comment__verification', {}, { active: true })}
/>
)}
</div>
<a href={`${o.locator.url}#${COMMENT_NODE_CLASSNAME_PREFIX}${o.id}`} className="comment__time">
{getLocalDatetime(this.props.intl, o.time)}
@@ -878,4 +888,8 @@ const messages = defineMessages({
id: 'comment.time',
defaultMessage: '{day} at {time}',
},
paidPatreon: {
id: 'comment.paid-patreon',
defaultMessage: 'Patreon Paid Subscriber',
},
});
+1 -1
View File
@@ -56,6 +56,6 @@ export class Countdown extends Component<Props, State> {
}, 1000);
}
render(props: Props) {
return <span {...exclude(props, 'time', 'onTimePassed')} ref={this.elemRef} />;
return <span role="timer" {...exclude(props, 'time', 'onTimePassed')} ref={this.elemRef} />;
}
}
+1 -1
View File
@@ -195,7 +195,7 @@ module.exports = (_, { mode, analyze }) => {
options: {
name: '[name].[ext]',
publicPath: PUBLIC_PATH,
limit: 1200,
limit: false,
},
},
};