Skip to content

Commit

Permalink
react: add useValidateHandle hook (#826)
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysu authored Feb 6, 2024
1 parent 8c61d97 commit 9aa0fb7
Show file tree
Hide file tree
Showing 25 changed files with 204 additions and 39 deletions.
11 changes: 11 additions & 0 deletions .changeset/chatty-socks-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@lens-protocol/blockchain-bindings": minor
"@lens-protocol/api-bindings": minor
"@lens-protocol/client": minor
"@lens-protocol/react": minor
"@lens-protocol/react-web": minor
"@lens-protocol/react-native": minor
---

**chore:** unified implementation and naming of `isValidHandle` helper among react and client SDKs. deprecated `isValidProfileHandle` in the client sdk.
**feat:** added `useValidateHandle` hook
4 changes: 2 additions & 2 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
uses: pnpm/[email protected]

- name: Use Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
Expand All @@ -38,7 +38,7 @@ jobs:
uses: pnpm/[email protected]

- name: Use Node.js for docs
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
Expand Down
2 changes: 2 additions & 0 deletions examples/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
UseInviteWallets,
UseNotifications,
UseResolveAddress,
UseValidateHandle,
} from './misc';
import {
ProfilesPage,
Expand Down Expand Up @@ -202,6 +203,7 @@ export function App() {
<Route path="useClaimHandle" element={<UseClaimHandle />} />
<Route path="useInviteWallets" element={<UseInviteWallets />} />
<Route path="useResolveAddress" element={<UseResolveAddress />} />
<Route path="useValidateHandle" element={<UseValidateHandle />} />
<Route
path="lensClientInteroperability"
element={<LensClientInteroperability />}
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/LogInPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';
import { useNavigate } from 'react-router-dom';
import { useAccount, useConnect } from 'wagmi';
import { InjectedConnector } from 'wagmi/connectors/injected';
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/components/auth/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { profileId, useLogin, useProfilesManaged } from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { never } from '../../utils';
import { ErrorMessage } from '../error/ErrorMessage';
Expand Down
5 changes: 5 additions & 0 deletions examples/web/src/misc/MiscPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ const hooks = [
description: `Resolves and EVM address from a Lens Handle.`,
path: '/misc/useResolveAddress',
},
{
label: 'useValidateHandle',
description: `Validate the proposed handle before profile creation.`,
path: '/misc/useValidateHandle',
},
];

export function MiscPage() {
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/misc/UseClaimHandle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useUpgradeCredentials,
} from '@lens-protocol/react-web';
import React from 'react';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { RequireWalletSession } from '../components/auth';
import { ErrorMessage } from '../components/error/ErrorMessage';
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/misc/UseInviteWallets.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Profile, useInviteWallets, useInvitedProfiles } from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { RequireProfileSession } from '../components/auth';
import { ErrorMessage } from '../components/error/ErrorMessage';
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/misc/UseResolveAddress.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useResolveAddress } from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

export function UseResolveAddress() {
const { execute, loading } = useResolveAddress();
Expand Down
45 changes: 45 additions & 0 deletions examples/web/src/misc/UseValidateHandle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useValidateHandle } from '@lens-protocol/react-web';
import { toast } from 'react-hot-toast';

export function UseValidateHandle() {
const { execute, loading } = useValidateHandle();

const validate = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);

const localName = formData.get('localName') as string;

const result = await execute({ handle: localName });

if (result.isFailure()) {
toast.error(result.error.message);
return;
}

toast.success(`You can create a new profile with handle: ${localName}`);
return;
};

return (
<div>
<h1>
<code>useValidateHandle</code>
</h1>

<form onSubmit={validate}>
<fieldset>
<legend>New handle</legend>
<label>
Local name:&nbsp;
<input type="text" name="localName" placeholder="wagmi" disabled={loading} />
</label>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-end' }}>
<button disabled={loading}>Validate</button>
</div>
</fieldset>
</form>
</div>
);
}
3 changes: 2 additions & 1 deletion examples/web/src/misc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './MiscPage';
export * from './UseApproveModule';
export * from './UseClaimHandle';
export * from './UseCurrencies';
export * from './UseResolveAddress';
export * from './UseInviteWallets';
export * from './UseNotifications';
export * from './UseResolveAddress';
export * from './UseValidateHandle';
2 changes: 1 addition & 1 deletion examples/web/src/profiles/UseFollowAndUnfollow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
useFollow,
useUnfollow,
} from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { RequireProfileSession } from '../components/auth';
import { ErrorMessage } from '../components/error/ErrorMessage';
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/profiles/UseLazyProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { profileId, useLazyProfile } from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { ErrorMessage } from '../components/error/ErrorMessage';
import { never } from '../utils';
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/profiles/UseLazyProfiles.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { profileId, useLazyProfiles } from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { ErrorMessage } from '../components/error/ErrorMessage';
import { ProfileCard } from './components/ProfileCard';
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/profiles/UseOwnedHandles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
useProfiles,
useUnlinkHandle,
} from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { RequireProfileSession } from '../components/auth';
import { ErrorMessage } from '../components/error/ErrorMessage';
Expand Down
2 changes: 1 addition & 1 deletion examples/web/src/publications/UseLazyPublications.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { profileId, useLazyPublications } from '@lens-protocol/react-web';
import toast from 'react-hot-toast';
import { toast } from 'react-hot-toast';

import { ErrorMessage } from '../components/error/ErrorMessage';
import { PublicationCard } from './components/PublicationCard';
Expand Down
1 change: 0 additions & 1 deletion packages/api-bindings/src/lens/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from './amount';
export * from './CollectModuleSettings';
export * from './isValidHandle';
export * from './omitTypename';
export * from './publication';
export * from './token-allowance';
Expand Down
8 changes: 0 additions & 8 deletions packages/api-bindings/src/lens/utils/isValidHandle.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { faker } from '@faker-js/faker';

import { isValidHandle } from '../isValidHandle';
import { isValidHandle } from '../profile';

describe(`Given the ${isValidHandle.name} predicate`, () => {
describe('when called with a prospect Lens handle', () => {
Expand All @@ -13,7 +11,7 @@ describe(`Given the ${isValidHandle.name} predicate`, () => {
});

it('should return false if above 26 characters', () => {
expect(isValidHandle(faker.datatype.string(27))).toBe(false);
expect(isValidHandle('a'.repeat(27))).toBe(false);
});

it('should return false if with invalid characters', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/blockchain-bindings/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './contracts';
export * from './data';
export * from './types';
export * from './profile';
export * from './TypedData';
export * from './types';
export * from './utils';
11 changes: 11 additions & 0 deletions packages/blockchain-bindings/src/profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Checks if suggested profile handle name is valid.
*
* @param handle - profile handle to check
* @returns true if the handle is valid
*/
export function isValidHandle(handle: string): boolean {
const validationRegex = /^[a-z](?:[a-z0-9_]{4,25})$/;

return validationRegex.test(handle);
}
18 changes: 7 additions & 11 deletions packages/client/src/submodules/profile/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
/**
* Checks if a profile handle is valid.
*
* @param handle - profile handle to check
* @returns true if the handle is valid
*/
export function isValidProfileHandle(handle: string): boolean {
const validationRegex = /^[a-z](?:[a-z0-9_]{4,25})$/;

return validationRegex.test(handle);
}
export {
isValidHandle,
/**
* @deprecated Use `isValidHandle` instead
*/
isValidHandle as isValidProfileHandle,
} from '@lens-protocol/blockchain-bindings';
1 change: 1 addition & 0 deletions packages/react/src/misc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './useInvitedProfiles';
export * from './useInviteWallets';
export * from './useModuleMetadata';
export * from './useResolveAddress';
export * from './useValidateHandle';
export * from './useWasWalletInvited';

export type { InvitedResult } from '@lens-protocol/api-bindings';
102 changes: 102 additions & 0 deletions packages/react/src/misc/useValidateHandle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { UnspecifiedError, useHandleToAddressLazyQuery } from '@lens-protocol/api-bindings';
import { isValidHandle } from '@lens-protocol/blockchain-bindings';
import { PromiseResult, failure, invariant, success } from '@lens-protocol/shared-kernel';

import { useLensApolloClient } from '../helpers/arguments';
import { UseDeferredTask, useDeferredTask } from '../helpers/tasks';
import { useSharedDependencies } from '../shared';

export class HandleNotAvailableError extends Error {
name = 'HandleNotAvailableError' as const;

constructor(handle: string) {
super(`Handle "${handle}" is already taken`);
}
}

export class InvalidHandleError extends Error {
name = 'InvalidHandleError' as const;

constructor(handle: string) {
super(`Handle "${handle}" is not valid`);
}
}

export type ValidateHandleRequest = {
/** Just the localname portion of a new handle */
handle: string;
};

/**
* Validate the proposed new handle, its format and availability.
*
* This hook will not execute until the returned function is called.
*
* @example
* ```ts
* const { called, error, loading, execute } = useValidateHandle();
* ```
*
* Simple example:
* ```ts
* const { called, error, loading, execute } = useValidateHandle();
*
* const callback = async () => {
* const result = await execute({ handle: 'wagmi' });
*
* if (result.isFailure()) {
* console.error(result.error.message); // handle not valid or already taken
* return;
* }
*
* if (result.value === true) {
* // success - handle is available
* }
* }
* ```
*
* @experimental This hook is experimental and may change in the future.
* @category Misc
* @group Hooks
*/
export function useValidateHandle(): UseDeferredTask<
void,
UnspecifiedError | HandleNotAvailableError | InvalidHandleError,
ValidateHandleRequest
> {
const [fetch] = useHandleToAddressLazyQuery(useLensApolloClient());

const {
config: { environment },
} = useSharedDependencies();

return useDeferredTask(
async (
request,
): PromiseResult<void, UnspecifiedError | HandleNotAvailableError | InvalidHandleError> => {
if (!isValidHandle(request.handle)) {
return failure(new InvalidHandleError(request.handle));
}

const { data, error } = await fetch({
variables: {
request: {
handle: environment.handleResolver(request.handle),
},
},
});

if (error) {
return failure(new UnspecifiedError(error));
}

invariant(data, 'Data must be defined');

if (data.result) {
return failure(new HandleNotAvailableError(request.handle));
}

return success();
},
);
}
3 changes: 2 additions & 1 deletion packages/react/src/profile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ export type {
UnknownFollowPolicy,
NoFeeFollowPolicy, // deprecated
} from '@lens-protocol/api-bindings';
export { isValidHandle, resolveFollowPolicy } from '@lens-protocol/api-bindings';
export { resolveFollowPolicy } from '@lens-protocol/api-bindings';
export { isValidHandle } from '@lens-protocol/blockchain-bindings';

0 comments on commit 9aa0fb7

Please sign in to comment.