-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Social: Add Bluesky connection UI (#94986)
* Extract common types from Mastodon * Add Bluesky form inputs * Enable Bluesky UIs * Clean up and make up * Fix unit test
- Loading branch information
1 parent
a8aee56
commit 6e05a7d
Showing
15 changed files
with
266 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { ExternalLink, FormInputValidation, FormLabel, Spinner } from '@automattic/components'; | ||
import { useTranslate } from 'i18n-calypso'; | ||
import { FormEvent, useEffect, useId, useRef, useState } from 'react'; | ||
import FormsButton from 'calypso/components/forms/form-button'; | ||
import FormSettingExplanation from 'calypso/components/forms/form-setting-explanation'; | ||
import { Connection, Service } from './types'; | ||
|
||
interface Props { | ||
service: Service; | ||
action: () => void; | ||
connectAnother: () => void; | ||
connections: Connection[]; | ||
isConnecting: boolean; | ||
} | ||
|
||
/** | ||
* Example valid handles: | ||
* - username.bsky.social | ||
* - user-name.bsky.social | ||
* - my_domain.com.bsky.social | ||
* - my-domain.com.my-own-server.com | ||
* @param {string} handle - Handle to validate | ||
* @returns {boolean} - Whether the handle is valid | ||
*/ | ||
function isValidBlueskyHandle( handle: string ) { | ||
const parts = handle.split( '.' ).filter( Boolean ); | ||
|
||
// A valid handle should have at least 3 parts - username, domain, and tld | ||
if ( parts.length < 3 ) { | ||
return false; | ||
} | ||
|
||
return parts.every( ( part ) => /^[a-z0-9_-]+$/i.test( part ) ); | ||
} | ||
|
||
const isAlreadyConnected = ( connections: Array< Connection >, handle: string ) => { | ||
return connections.some( ( connection ) => { | ||
const { external_display } = connection; | ||
return external_display === handle; | ||
} ); | ||
}; | ||
|
||
export const Bluesky: React.FC< Props > = ( { | ||
service, | ||
action, | ||
connectAnother, | ||
connections, | ||
isConnecting, | ||
} ) => { | ||
const translate = useTranslate(); | ||
const [ error, setError ] = useState( '' ); | ||
const formRef = useRef< HTMLFormElement >( null ); | ||
|
||
// After sucessfully connecting an account, reset the handle. | ||
// Disabled react-hooks/exhaustive-deps because we don't want to run this on handle change | ||
useEffect( () => { | ||
const handle = formRef.current?.elements.namedItem( 'handle' ) as HTMLInputElement; | ||
|
||
if ( ! isConnecting && isAlreadyConnected( connections, handle.value ) ) { | ||
formRef.current?.reset(); | ||
} | ||
}, [ isConnecting, connections ] ); // eslint-disable-line react-hooks/exhaustive-deps | ||
|
||
/** | ||
* Handle the Connect account submission. | ||
*/ | ||
const handleSubmit = ( e: FormEvent< HTMLFormElement > ) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
|
||
const formData = new FormData( e.target as HTMLFormElement ); | ||
|
||
// Let us make the user's life easier by removing the leading "@" if they added it | ||
const handle = ( formData.get( 'handle' )?.toString().trim() || '' ).replace( /^@/, '' ); | ||
const app_password = formData.get( 'app_password' )?.toString().trim() || ''; | ||
|
||
if ( isAlreadyConnected( connections, handle ) ) { | ||
return setError( translate( 'This account is already connected.' ) ); | ||
} | ||
|
||
if ( ! handle || ! isValidBlueskyHandle( handle ) ) { | ||
return setError( translate( 'Please enter a valid handle.' ) ); | ||
} | ||
|
||
const url = new URL( service.connect_URL ); | ||
url.searchParams.set( 'handle', handle ); | ||
url.searchParams.set( 'app_password', app_password ); | ||
|
||
// TODO: Fix this to avoid mutating props | ||
service.connect_URL = url.toString(); | ||
|
||
connections.length >= 1 ? connectAnother() : action(); | ||
}; | ||
|
||
const id = useId(); | ||
|
||
const showError = !! error; | ||
return ( | ||
<div className="sharing-service-distributed-example"> | ||
<form onSubmit={ handleSubmit } ref={ formRef }> | ||
<div> | ||
<FormLabel htmlFor={ `${ id }-handle` }> | ||
{ translate( 'Handle', { comment: 'Bluesky account handle' } ) } | ||
</FormLabel> | ||
<FormSettingExplanation> | ||
{ translate( 'You can find the handle in your Bluesky profile.' ) } | ||
</FormSettingExplanation> | ||
<input | ||
autoComplete="off" | ||
autoCapitalize="off" | ||
autoCorrect="off" | ||
spellCheck="false" | ||
id={ `${ id }-handle` } | ||
name="handle" | ||
placeholder="username.bsky.social" | ||
required | ||
type="text" | ||
className="form-text-input" | ||
/> | ||
{ showError && <FormInputValidation isError text={ error } /> } | ||
</div> | ||
<div> | ||
<FormLabel htmlFor={ `${ id }-app-password` }>{ translate( 'App password' ) }</FormLabel> | ||
<FormSettingExplanation> | ||
{ translate( | ||
'App password is needed to safely connect your account. App password is different from your account password. You can {{link}}generate it in Bluesky{{/link}}.', | ||
{ | ||
components: { | ||
link: <ExternalLink href="https://bsky.app/settings/app-passwords" />, | ||
}, | ||
} | ||
) } | ||
</FormSettingExplanation> | ||
<input | ||
autoComplete="off" | ||
autoCapitalize="off" | ||
autoCorrect="off" | ||
spellCheck="false" | ||
id={ `${ id }-app-password` } | ||
name="app_password" | ||
type="password" | ||
placeholder="xxxx-xxxx-xxxx-xxxx" | ||
required | ||
className="form-text-input" | ||
/> | ||
{ showError && <FormInputValidation isError text={ error } /> } | ||
</div> | ||
<div> | ||
<FormsButton primary type="submit" disabled={ isConnecting }> | ||
{ translate( 'Connect account' ) } | ||
{ isConnecting && <Spinner /> } | ||
</FormsButton> | ||
</div> | ||
</form> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Bluesky; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { SharingService, connectFor } from 'calypso/my-sites/marketing/connections/service'; | ||
|
||
export class Bluesky extends SharingService {} | ||
|
||
export default connectFor( Bluesky, ( state, props ) => props ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.