From 08f114a15c0293519d240f0acbd02d47c60d6187 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Jun 2022 11:03:12 -0400 Subject: [PATCH 01/11] min chars --- .../features/verification/registration.tsx | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/verification/registration.tsx b/app/soapbox/features/verification/registration.tsx index 8b76dce34..68a4805db 100644 --- a/app/soapbox/features/verification/registration.tsx +++ b/app/soapbox/features/verification/registration.tsx @@ -1,4 +1,5 @@ import { AxiosError } from 'axios'; +import classNames from 'classnames'; import * as React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; @@ -9,7 +10,7 @@ import { fetchInstance } from 'soapbox/actions/instance'; import { startOnboarding } from 'soapbox/actions/onboarding'; import snackbar from 'soapbox/actions/snackbar'; import { createAccount, removeStoredVerification } from 'soapbox/actions/verification'; -import { Button, Form, FormGroup, Input } from 'soapbox/components/ui'; +import { Button, Form, FormGroup, HStack, Icon, Input, Stack, Text } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; import { getRedirectUrl } from 'soapbox/utils/redirect'; @@ -44,6 +45,8 @@ const Registration = () => { const [shouldRedirect, setShouldRedirect] = React.useState(false); const { username, password } = state; + const meetsLengthRequirements = React.useMemo(() => password.length >= 8, [password]); + const handleSubmit = React.useCallback((event) => { event.preventDefault(); @@ -119,6 +122,21 @@ const Registration = () => { onChange={handleInputChange} required /> + + + + + + 8 characters + +
From 4fc43afe1b557d93a00b9e5bbd01f2f0576b82bf Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Jun 2022 13:45:09 -0400 Subject: [PATCH 02/11] Add new ValidationCheckmark component --- .../__tests__/validation-checkmark.test.tsx | 29 +++++++++++++++++++ .../components/validation-checkmark.tsx | 28 ++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 app/soapbox/components/__tests__/validation-checkmark.test.tsx create mode 100644 app/soapbox/components/validation-checkmark.tsx diff --git a/app/soapbox/components/__tests__/validation-checkmark.test.tsx b/app/soapbox/components/__tests__/validation-checkmark.test.tsx new file mode 100644 index 000000000..d3a02c718 --- /dev/null +++ b/app/soapbox/components/__tests__/validation-checkmark.test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { render, screen } from '../../jest/test-helpers'; +import ValidationCheckmark from '../validation-checkmark'; + +describe('', () => { + it('renders text', () => { + const text = 'some validation'; + render(); + + expect(screen.getByTestId('validation-checkmark')).toHaveTextContent(text); + }); + + it('uses a green check when valid', () => { + const text = 'some validation'; + render(); + + expect(screen.getByTestId('svg-icon-loader')).toHaveClass('text-success-500'); + expect(screen.getByTestId('svg-icon-loader')).not.toHaveClass('text-gray-500'); + }); + + it('uses a gray check when valid', () => { + const text = 'some validation'; + render(); + + expect(screen.getByTestId('svg-icon-loader')).toHaveClass('text-gray-500'); + expect(screen.getByTestId('svg-icon-loader')).not.toHaveClass('text-success-500'); + }); +}); diff --git a/app/soapbox/components/validation-checkmark.tsx b/app/soapbox/components/validation-checkmark.tsx new file mode 100644 index 000000000..0c0ab42d6 --- /dev/null +++ b/app/soapbox/components/validation-checkmark.tsx @@ -0,0 +1,28 @@ +import classNames from 'classnames'; +import React from 'react'; + +import { HStack, Icon, Text } from 'soapbox/components/ui'; + +interface IValidationCheckmark { + isValid: boolean + text: string +} + +const ValidationCheckmark = ({ isValid, text }: IValidationCheckmark) => { + return ( + + + + {text} + + ); +}; + +export default ValidationCheckmark; From cf128d70b4def1dfc0310221f6524fb9e6689732 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Jun 2022 13:45:23 -0400 Subject: [PATCH 03/11] Apply new ValidationCheckmark component to Registration --- .../__tests__/registration.test.tsx | 17 +++++ .../features/verification/registration.tsx | 69 ++++++++++++++----- app/soapbox/locales/en.json | 3 + 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/app/soapbox/features/verification/__tests__/registration.test.tsx b/app/soapbox/features/verification/__tests__/registration.test.tsx index ff5da7e97..f82c0dab4 100644 --- a/app/soapbox/features/verification/__tests__/registration.test.tsx +++ b/app/soapbox/features/verification/__tests__/registration.test.tsx @@ -64,4 +64,21 @@ describe('', () => { expect(screen.getByTestId('toast')).toHaveTextContent(/failed to register your account/i); }); }); + + describe('validations', () => { + it('should undisable button with valid password', async() => { + render(); + + expect(screen.getByTestId('button')).toBeDisabled(); + fireEvent.change(screen.getByTestId('password-input'), { target: { value: 'Password' } }); + expect(screen.getByTestId('button')).not.toBeDisabled(); + }); + + it('should disable button with invalid password', async() => { + render(); + + fireEvent.change(screen.getByTestId('password-input'), { target: { value: 'Passwor' } }); + expect(screen.getByTestId('button')).toBeDisabled(); + }); + }); }); diff --git a/app/soapbox/features/verification/registration.tsx b/app/soapbox/features/verification/registration.tsx index 68a4805db..567db8b16 100644 --- a/app/soapbox/features/verification/registration.tsx +++ b/app/soapbox/features/verification/registration.tsx @@ -1,5 +1,4 @@ import { AxiosError } from 'axios'; -import classNames from 'classnames'; import * as React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useDispatch } from 'react-redux'; @@ -10,7 +9,8 @@ import { fetchInstance } from 'soapbox/actions/instance'; import { startOnboarding } from 'soapbox/actions/onboarding'; import snackbar from 'soapbox/actions/snackbar'; import { createAccount, removeStoredVerification } from 'soapbox/actions/verification'; -import { Button, Form, FormGroup, HStack, Icon, Input, Stack, Text } from 'soapbox/components/ui'; +import { Button, Form, FormGroup, Input, Stack } from 'soapbox/components/ui'; +import ValidationCheckmark from 'soapbox/components/validation-checkmark'; import { useAppSelector } from 'soapbox/hooks'; import { getRedirectUrl } from 'soapbox/utils/redirect'; @@ -27,6 +27,18 @@ const messages = defineMessages({ id: 'registrations.error', defaultMessage: 'Failed to register your account.', }, + minimumCharacters: { + id: 'registration.validation.minimum_characters', + defaultMessage: '8 characters', + }, + capitalLetter: { + id: 'registration.validation.capital_letter', + defaultMessage: '1 capital letter', + }, + lowercaseLetter: { + id: 'registration.validation.lowercase_letter', + defaultMessage: '1 lowercase letter', + }, }); const initialState = { @@ -34,6 +46,19 @@ const initialState = { password: '', }; +const hasUppercaseCharacter = (string: string) => { + for (let i = 0; i < string.length; i++) { + if (string.charAt(i) === string.charAt(i).toUpperCase() && string.charAt(i).match(/[a-z]/i)) { + return true; + } + } + return false; +}; + +const hasLowercaseCharacter = (string: string) => { + return string.toUpperCase() !== string; +}; + const Registration = () => { const dispatch = useDispatch(); const intl = useIntl(); @@ -46,11 +71,13 @@ const Registration = () => { const { username, password } = state; const meetsLengthRequirements = React.useMemo(() => password.length >= 8, [password]); + const meetsCapitalLetterRequirements = React.useMemo(() => hasUppercaseCharacter(password), [password]); + const meetsLowercaseLetterRequirements = React.useMemo(() => hasLowercaseCharacter(password), [password]); + const hasValidPassword = meetsLengthRequirements && meetsCapitalLetterRequirements && meetsLowercaseLetterRequirements; const handleSubmit = React.useCallback((event) => { event.preventDefault(); - // TODO: handle validation errors from Pepe dispatch(createAccount(username, password)) .then(() => dispatch(logIn(intl, username, password))) .then(({ access_token }: any) => dispatch(verifyCredentials(access_token))) @@ -121,26 +148,36 @@ const Registration = () => { value={password} onChange={handleInputChange} required + data-testid='password-input' /> - - - + + - 8 characters - + + +
- +
diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 9f3894ac5..d6808069a 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -833,6 +833,9 @@ "registration.sign_up": "Sign up", "registration.tos": "Terms of Service", "registration.username_unavailable": "Username is already taken.", + "registration.validation.minimum_characters": "8 characters", + "registration.validation.capital_letter": "1 capital letter", + "registration.validation.lowercase_letter": "1 lowercase letter", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", "relative_time.just_now": "now", From 00f6df3f371dc21e902f51ad66b1ad1b5c4fc024 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 9 Jun 2022 14:29:01 -0500 Subject: [PATCH 04/11] Properly import quotes from reblogs --- app/soapbox/actions/importer/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/soapbox/actions/importer/index.js b/app/soapbox/actions/importer/index.js index 9ad58e114..cb4305901 100644 --- a/app/soapbox/actions/importer/index.js +++ b/app/soapbox/actions/importer/index.js @@ -69,10 +69,21 @@ export function importFetchedStatus(status, idempotencyKey) { dispatch(importFetchedStatus(status.quote)); } + // Pleroma quotes if (status.pleroma?.quote?.id) { dispatch(importFetchedStatus(status.pleroma.quote)); } + // Fedibird quote from reblog + if (status.reblog?.quote?.id) { + dispatch(importFetchedStatus(status.reblog.quote)); + } + + // Pleroma quote from reblog + if (status.reblog?.pleroma?.quote?.id) { + dispatch(importFetchedStatus(status.reblog.pleroma.quote)); + } + if (status.poll?.id) { dispatch(importFetchedPoll(status.poll)); } From 309e6c1a67fd9332c000e2347173c61d2fdc0f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 9 Jun 2022 21:47:21 +0200 Subject: [PATCH 05/11] Reactions modal fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/account.tsx | 12 +++++++-- app/soapbox/components/hover_ref_wrapper.tsx | 6 +++-- app/soapbox/components/ui/tabs/tabs.tsx | 2 +- .../ui/components/reactions_modal.tsx | 25 +++++++++++-------- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index e0fe7ff14..d6d076889 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -9,7 +9,7 @@ import { getAcct } from 'soapbox/utils/accounts'; import { displayFqn } from 'soapbox/utils/state'; import RelativeTimestamp from './relative_timestamp'; -import { Avatar, HStack, Icon, IconButton, Text } from './ui'; +import { Avatar, Emoji, HStack, Icon, IconButton, Text } from './ui'; import type { Account as AccountEntity } from 'soapbox/types/entities'; @@ -60,6 +60,7 @@ interface IAccount { withDate?: boolean, withRelationship?: boolean, showEdit?: boolean, + emoji?: string, } const Account = ({ @@ -80,6 +81,7 @@ const Account = ({ withDate = false, withRelationship = true, showEdit = false, + emoji, }: IAccount) => { const overflowRef = React.useRef(null); const actionRef = React.useRef(null); @@ -160,7 +162,7 @@ const Account = ({ {children}} + wrapper={(children) => {children}} > event.stopPropagation()} > + {emoji && ( + + )} diff --git a/app/soapbox/components/hover_ref_wrapper.tsx b/app/soapbox/components/hover_ref_wrapper.tsx index f1da2e65d..2ef2d8372 100644 --- a/app/soapbox/components/hover_ref_wrapper.tsx +++ b/app/soapbox/components/hover_ref_wrapper.tsx @@ -1,3 +1,4 @@ +import classNames from 'classnames'; import { debounce } from 'lodash'; import React, { useRef } from 'react'; import { useDispatch } from 'react-redux'; @@ -15,10 +16,11 @@ const showProfileHoverCard = debounce((dispatch, ref, accountId) => { interface IHoverRefWrapper { accountId: string, inline: boolean, + className?: string, } /** Makes a profile hover card appear when the wrapped element is hovered. */ -export const HoverRefWrapper: React.FC = ({ accountId, children, inline = false }) => { +export const HoverRefWrapper: React.FC = ({ accountId, children, inline = false, className }) => { const dispatch = useDispatch(); const ref = useRef(null); const Elem: keyof JSX.IntrinsicElements = inline ? 'span' : 'div'; @@ -42,7 +44,7 @@ export const HoverRefWrapper: React.FC = ({ accountId, childre return ( = ({ index, ...props }) => { }; /** Structure to represent a tab. */ -type Item = { +export type Item = { /** Tab text. */ text: React.ReactNode, /** Tab tooltip text. */ diff --git a/app/soapbox/features/ui/components/reactions_modal.tsx b/app/soapbox/features/ui/components/reactions_modal.tsx index 4832f943d..f343dc5c4 100644 --- a/app/soapbox/features/ui/components/reactions_modal.tsx +++ b/app/soapbox/features/ui/components/reactions_modal.tsx @@ -3,12 +3,13 @@ import React, { useEffect, useState } from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { fetchFavourites, fetchReactions } from 'soapbox/actions/interactions'; -import FilterBar from 'soapbox/components/filter_bar'; import ScrollableList from 'soapbox/components/scrollable_list'; -import { Modal, Spinner } from 'soapbox/components/ui'; +import { Emoji, Modal, Spinner, Tabs } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account_container'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import type { Item } from 'soapbox/components/ui/tabs/tabs'; + const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, all: { id: 'reactions.all', defaultMessage: 'All' }, @@ -24,14 +25,14 @@ const ReactionsModal: React.FC = ({ onClose, statusId, reaction const dispatch = useAppDispatch(); const intl = useIntl(); const [reaction, setReaction] = useState(initialReaction); - const reactions = useAppSelector, count: number, name: string, }>>((state) => { const favourites = state.user_lists.getIn(['favourited_by', statusId]); const reactions = state.user_lists.getIn(['reactions', statusId]); - return favourites && reactions && ImmutableList(favourites ? [{ accounts: favourites, count: favourites.size, name: '👍' }] : []).concat(reactions || []); + return favourites && reactions && ImmutableList(favourites.size ? [{ accounts: favourites, count: favourites.size, name: '👍' }] : []).concat(reactions || []); }); const fetchData = () => { @@ -44,7 +45,7 @@ const ReactionsModal: React.FC = ({ onClose, statusId, reaction }; const renderFilterBar = () => { - const items = [ + const items: Array = [ { text: intl.formatMessage(messages.all), action: () => setReaction(''), @@ -54,13 +55,16 @@ const ReactionsModal: React.FC = ({ onClose, statusId, reaction reactions.forEach(reaction => items.push( { - text: `${reaction.name} ${reaction.count}`, + text:
+ + {reaction.count} +
, action: () => setReaction(reaction.name), name: reaction.name, }, )); - return ; + return ; }; useEffect(() => { @@ -69,7 +73,7 @@ const ReactionsModal: React.FC = ({ onClose, statusId, reaction const accounts = reactions && (reaction ? reactions.find(({ name }) => name === reaction)?.accounts.map(account => ({ id: account, reaction: reaction })) - : reactions.map(({ accounts, name }) => accounts.map(account => ({ id: account, reaction: name }))).flat()); + : reactions.map(({ accounts, name }) => accounts.map(account => ({ id: account, reaction: name }))).flatten()) as Array<{ id: string, reaction: string }>; let body; @@ -79,14 +83,15 @@ const ReactionsModal: React.FC = ({ onClose, statusId, reaction const emptyMessage = ; body = (<> - {reactions.length > 0 && renderFilterBar()} + {reactions.size > 0 && renderFilterBar()} {accounts.map((account) => - , + , )} ); From a8b738a719c372f70c76b7cdd454edfab7f6a14f Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Jun 2022 15:51:50 -0400 Subject: [PATCH 06/11] Add to other Password inputs --- .../components/validation-checkmark.tsx | 4 +- .../components/password_reset_confirm.tsx | 11 ++- app/soapbox/features/edit_password/index.tsx | 12 +++- .../components/password-indicator.tsx | 72 +++++++++++++++++++ .../features/verification/registration.tsx | 50 ++----------- app/soapbox/utils/features.ts | 8 +++ 6 files changed, 105 insertions(+), 52 deletions(-) create mode 100644 app/soapbox/features/verification/components/password-indicator.tsx diff --git a/app/soapbox/components/validation-checkmark.tsx b/app/soapbox/components/validation-checkmark.tsx index 0c0ab42d6..111066ccf 100644 --- a/app/soapbox/components/validation-checkmark.tsx +++ b/app/soapbox/components/validation-checkmark.tsx @@ -12,10 +12,10 @@ const ValidationCheckmark = ({ isValid, text }: IValidationCheckmark) => { return ( diff --git a/app/soapbox/features/auth_login/components/password_reset_confirm.tsx b/app/soapbox/features/auth_login/components/password_reset_confirm.tsx index 2f30e700f..a7709834f 100644 --- a/app/soapbox/features/auth_login/components/password_reset_confirm.tsx +++ b/app/soapbox/features/auth_login/components/password_reset_confirm.tsx @@ -4,7 +4,8 @@ import { Redirect } from 'react-router-dom'; import { resetPasswordConfirm } from 'soapbox/actions/security'; import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; +import PasswordIndicator from 'soapbox/features/verification/components/password-indicator'; +import { useAppDispatch, useFeatures } from 'soapbox/hooks'; const token = new URLSearchParams(window.location.search).get('reset_password_token'); @@ -22,9 +23,11 @@ const Statuses = { const PasswordResetConfirm = () => { const intl = useIntl(); const dispatch = useAppDispatch(); + const { passwordRequirements } = useFeatures(); const [password, setPassword] = React.useState(''); const [status, setStatus] = React.useState(Statuses.IDLE); + const [hasValidPassword, setHasValidPassword] = React.useState(passwordRequirements ? false : true); const isLoading = status === Statuses.LOADING; @@ -71,10 +74,14 @@ const PasswordResetConfirm = () => { onChange={onChange} required /> + + {passwordRequirements && ( + + )} - diff --git a/app/soapbox/features/edit_password/index.tsx b/app/soapbox/features/edit_password/index.tsx index e95e6dec4..5cebc3c98 100644 --- a/app/soapbox/features/edit_password/index.tsx +++ b/app/soapbox/features/edit_password/index.tsx @@ -4,7 +4,9 @@ import { defineMessages, useIntl } from 'react-intl'; import { changePassword } from 'soapbox/actions/security'; import snackbar from 'soapbox/actions/snackbar'; import { Button, Card, CardBody, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; +import { useAppDispatch, useFeatures } from 'soapbox/hooks'; + +import PasswordIndicator from '../verification/components/password-indicator'; const messages = defineMessages({ updatePasswordSuccess: { id: 'security.update_password.success', defaultMessage: 'Password successfully updated.' }, @@ -22,9 +24,11 @@ const initialState = { currentPassword: '', newPassword: '', newPasswordConfirma const EditPassword = () => { const intl = useIntl(); const dispatch = useAppDispatch(); + const { passwordRequirements } = useFeatures(); const [state, setState] = React.useState(initialState); const [isLoading, setLoading] = React.useState(false); + const [hasValidPassword, setHasValidPassword] = React.useState(passwordRequirements ? false : true); const { currentPassword, newPassword, newPasswordConfirmation } = state; @@ -75,6 +79,10 @@ const EditPassword = () => { onChange={handleInputChange} value={newPassword} /> + + {passwordRequirements && ( + + )} @@ -91,7 +99,7 @@ const EditPassword = () => { {intl.formatMessage(messages.cancel)} - diff --git a/app/soapbox/features/verification/components/password-indicator.tsx b/app/soapbox/features/verification/components/password-indicator.tsx new file mode 100644 index 000000000..7b804d3d6 --- /dev/null +++ b/app/soapbox/features/verification/components/password-indicator.tsx @@ -0,0 +1,72 @@ +import React, { useEffect, useMemo } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { Stack } from 'soapbox/components/ui'; +import ValidationCheckmark from 'soapbox/components/validation-checkmark'; + +const messages = defineMessages({ + minimumCharacters: { + id: 'registration.validation.minimum_characters', + defaultMessage: '8 characters', + }, + capitalLetter: { + id: 'registration.validation.capital_letter', + defaultMessage: '1 capital letter', + }, + lowercaseLetter: { + id: 'registration.validation.lowercase_letter', + defaultMessage: '1 lowercase letter', + }, +}); + +const hasUppercaseCharacter = (string: string) => { + for (let i = 0; i < string.length; i++) { + if (string.charAt(i) === string.charAt(i).toUpperCase() && string.charAt(i).match(/[a-z]/i)) { + return true; + } + } + return false; +}; + +const hasLowercaseCharacter = (string: string) => { + return string.toUpperCase() !== string; +}; + +interface IPasswordIndicator { + onChange(isValid: boolean): void + password: string +} + +const PasswordIndicator = ({ onChange, password }: IPasswordIndicator) => { + const intl = useIntl(); + + const meetsLengthRequirements = useMemo(() => password.length >= 8, [password]); + const meetsCapitalLetterRequirements = useMemo(() => hasUppercaseCharacter(password), [password]); + const meetsLowercaseLetterRequirements = useMemo(() => hasLowercaseCharacter(password), [password]); + const hasValidPassword = meetsLengthRequirements && meetsCapitalLetterRequirements && meetsLowercaseLetterRequirements; + + useEffect(() => { + onChange(hasValidPassword); + }, [hasValidPassword]); + + return ( + + + + + + + + ); +}; + +export default PasswordIndicator; diff --git a/app/soapbox/features/verification/registration.tsx b/app/soapbox/features/verification/registration.tsx index 567db8b16..923badd61 100644 --- a/app/soapbox/features/verification/registration.tsx +++ b/app/soapbox/features/verification/registration.tsx @@ -14,6 +14,8 @@ import ValidationCheckmark from 'soapbox/components/validation-checkmark'; import { useAppSelector } from 'soapbox/hooks'; import { getRedirectUrl } from 'soapbox/utils/redirect'; +import PasswordIndicator from './components/password-indicator'; + const messages = defineMessages({ success: { id: 'registrations.success', @@ -27,18 +29,6 @@ const messages = defineMessages({ id: 'registrations.error', defaultMessage: 'Failed to register your account.', }, - minimumCharacters: { - id: 'registration.validation.minimum_characters', - defaultMessage: '8 characters', - }, - capitalLetter: { - id: 'registration.validation.capital_letter', - defaultMessage: '1 capital letter', - }, - lowercaseLetter: { - id: 'registration.validation.lowercase_letter', - defaultMessage: '1 lowercase letter', - }, }); const initialState = { @@ -46,19 +36,6 @@ const initialState = { password: '', }; -const hasUppercaseCharacter = (string: string) => { - for (let i = 0; i < string.length; i++) { - if (string.charAt(i) === string.charAt(i).toUpperCase() && string.charAt(i).match(/[a-z]/i)) { - return true; - } - } - return false; -}; - -const hasLowercaseCharacter = (string: string) => { - return string.toUpperCase() !== string; -}; - const Registration = () => { const dispatch = useDispatch(); const intl = useIntl(); @@ -68,13 +45,9 @@ const Registration = () => { const [state, setState] = React.useState(initialState); const [shouldRedirect, setShouldRedirect] = React.useState(false); + const [hasValidPassword, setHasValidPassword] = React.useState(false); const { username, password } = state; - const meetsLengthRequirements = React.useMemo(() => password.length >= 8, [password]); - const meetsCapitalLetterRequirements = React.useMemo(() => hasUppercaseCharacter(password), [password]); - const meetsLowercaseLetterRequirements = React.useMemo(() => hasLowercaseCharacter(password), [password]); - const hasValidPassword = meetsLengthRequirements && meetsCapitalLetterRequirements && meetsLowercaseLetterRequirements; - const handleSubmit = React.useCallback((event) => { event.preventDefault(); @@ -151,22 +124,7 @@ const Registration = () => { data-testid='password-input' /> - - - - - - - +
diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 891cdd621..8f8a65cc1 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -380,6 +380,14 @@ const getInstanceFeatures = (instance: Instance) => { */ paginatedContext: v.software === TRUTHSOCIAL, + /** + * Require minimum password requirements. + * - 8 characters + * - 1 uppercase + * - 1 lowercase + */ + passwordRequirements: v.software === TRUTHSOCIAL, + /** * Displays a form to follow a user when logged out. * @see POST /main/ostatus From 1b88f2f36e29a5622e23f9fc05ae8c4c5eff011b Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Jun 2022 15:53:03 -0400 Subject: [PATCH 07/11] Fix test --- .../components/__tests__/validation-checkmark.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/__tests__/validation-checkmark.test.tsx b/app/soapbox/components/__tests__/validation-checkmark.test.tsx index d3a02c718..c9e204a6e 100644 --- a/app/soapbox/components/__tests__/validation-checkmark.test.tsx +++ b/app/soapbox/components/__tests__/validation-checkmark.test.tsx @@ -16,14 +16,14 @@ describe('', () => { render(); expect(screen.getByTestId('svg-icon-loader')).toHaveClass('text-success-500'); - expect(screen.getByTestId('svg-icon-loader')).not.toHaveClass('text-gray-500'); + expect(screen.getByTestId('svg-icon-loader')).not.toHaveClass('text-gray-400'); }); it('uses a gray check when valid', () => { const text = 'some validation'; render(); - expect(screen.getByTestId('svg-icon-loader')).toHaveClass('text-gray-500'); + expect(screen.getByTestId('svg-icon-loader')).toHaveClass('text-gray-400'); expect(screen.getByTestId('svg-icon-loader')).not.toHaveClass('text-success-500'); }); }); From f5024d6c8e960399d065e22c0761e5bbaa0075e2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 9 Jun 2022 14:59:27 -0500 Subject: [PATCH 08/11] Account lookup: fetch relationship --- app/soapbox/actions/accounts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/actions/accounts.js b/app/soapbox/actions/accounts.js index 0fbb5c1a5..5cc0008a4 100644 --- a/app/soapbox/actions/accounts.js +++ b/app/soapbox/actions/accounts.js @@ -176,6 +176,7 @@ export function fetchAccountByUsername(username, history) { }); } else if (features.accountLookup) { return dispatch(accountLookup(username)).then(account => { + dispatch(fetchRelationships([account.id])); dispatch(fetchAccountSuccess(account)); }).catch(error => { dispatch(fetchAccountFail(null, error)); From 28f0274b46ae379f9b783df5422d086a9633c0eb Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 9 Jun 2022 15:10:49 -0500 Subject: [PATCH 09/11] Fix actions/accounts test for accountLookup --- app/soapbox/actions/__tests__/accounts.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/soapbox/actions/__tests__/accounts.test.ts b/app/soapbox/actions/__tests__/accounts.test.ts index 3b1abbf9f..b02469527 100644 --- a/app/soapbox/actions/__tests__/accounts.test.ts +++ b/app/soapbox/actions/__tests__/accounts.test.ts @@ -264,7 +264,8 @@ describe('fetchAccountByUsername()', () => { }); expect(actions[1].type).toEqual('ACCOUNTS_IMPORT'); expect(actions[2].type).toEqual('ACCOUNT_LOOKUP_SUCCESS'); - expect(actions[3].type).toEqual('ACCOUNT_FETCH_SUCCESS'); + expect(actions[3].type).toEqual('RELATIONSHIPS_FETCH_REQUEST'); + expect(actions[4].type).toEqual('ACCOUNT_FETCH_SUCCESS'); }); }); From ecb3362ce2cf110c202b3998ff6857dbf889f861 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Jun 2022 16:44:49 -0400 Subject: [PATCH 10/11] Ensure whole number --- .../features/compose/components/polls/duration-selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/compose/components/polls/duration-selector.tsx b/app/soapbox/features/compose/components/polls/duration-selector.tsx index 491530d22..10027824e 100644 --- a/app/soapbox/features/compose/components/polls/duration-selector.tsx +++ b/app/soapbox/features/compose/components/polls/duration-selector.tsx @@ -27,7 +27,7 @@ const DurationSelector = ({ onDurationChange }: IDurationSelector) => { now.setMinutes(now.getMinutes() + minutes); now.setHours(now.getHours() + hours); - return (now - future) / 1000; + return Math.round((now - future) / 1000); }, [days, hours, minutes]); useEffect(() => { From 56715757b8a2ea8156dc35cf3dc5e82238720146 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 10 Jun 2022 10:34:35 -0400 Subject: [PATCH 11/11] Lint --- app/soapbox/features/verification/registration.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/soapbox/features/verification/registration.tsx b/app/soapbox/features/verification/registration.tsx index 923badd61..c2c09fbce 100644 --- a/app/soapbox/features/verification/registration.tsx +++ b/app/soapbox/features/verification/registration.tsx @@ -9,8 +9,7 @@ import { fetchInstance } from 'soapbox/actions/instance'; import { startOnboarding } from 'soapbox/actions/onboarding'; import snackbar from 'soapbox/actions/snackbar'; import { createAccount, removeStoredVerification } from 'soapbox/actions/verification'; -import { Button, Form, FormGroup, Input, Stack } from 'soapbox/components/ui'; -import ValidationCheckmark from 'soapbox/components/validation-checkmark'; +import { Button, Form, FormGroup, Input } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; import { getRedirectUrl } from 'soapbox/utils/redirect';