mirror of
https://github.com/placeholder-soft/web.git
synced 2026-01-12 22:45:00 +08:00
Add location to basename profile / TextRecords (#1153)
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import Fieldset from 'apps/web/src/components/Fieldset';
|
||||
import Input from 'apps/web/src/components/Input';
|
||||
import Label from 'apps/web/src/components/Label';
|
||||
import {
|
||||
textRecordsKeysPlaceholderForDisplay,
|
||||
USERNAME_LOCATION_MAX_LENGTH,
|
||||
UsernameTextRecordKeys,
|
||||
} from 'apps/web/src/utils/usernames';
|
||||
import { ChangeEvent, ReactNode, useCallback, useId } from 'react';
|
||||
|
||||
export type UsernameLocationFieldProps = {
|
||||
labelChildren?: ReactNode;
|
||||
onChange: (key: UsernameTextRecordKeys, value: string) => void;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export default function UsernameLocationField({
|
||||
labelChildren = 'Location',
|
||||
onChange,
|
||||
value,
|
||||
disabled = false,
|
||||
}: UsernameLocationFieldProps) {
|
||||
const onChangeLocation = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const location = event.target.value;
|
||||
if (location.length > USERNAME_LOCATION_MAX_LENGTH) {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
if (onChange) onChange(UsernameTextRecordKeys.Location, location);
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const usernameLocationFieldId = useId();
|
||||
|
||||
return (
|
||||
<Fieldset>
|
||||
{labelChildren && <Label htmlFor={usernameLocationFieldId}>{labelChildren}</Label>}
|
||||
<Input
|
||||
id={usernameLocationFieldId}
|
||||
placeholder={textRecordsKeysPlaceholderForDisplay[UsernameTextRecordKeys.Location]}
|
||||
maxLength={USERNAME_LOCATION_MAX_LENGTH}
|
||||
onChange={onChangeLocation}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
className="flex-1 rounded-md border border-gray-40/20 p-2 text-black"
|
||||
/>
|
||||
</Fieldset>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export default function UsernameProfileCard() {
|
||||
});
|
||||
|
||||
const textRecordDescription = existingTextRecords[UsernameTextRecordKeys.Description];
|
||||
const textRecordLocation = existingTextRecords[UsernameTextRecordKeys.Location];
|
||||
|
||||
const textRecordsSocial = textRecordsSocialFieldsEnabled.reduce(
|
||||
(previousValue, textRecordKey) => {
|
||||
@@ -29,9 +30,11 @@ export default function UsernameProfileCard() {
|
||||
{} as UsernameTextRecords,
|
||||
);
|
||||
|
||||
const hasTextRecordsSocials = Object.values(textRecordsSocial).filter((v) => !!v).length > 0;
|
||||
|
||||
// TODO: Empty state / CTA to edit if owner
|
||||
const hasTextRecordsToDisplay =
|
||||
!!textRecordDescription || Object.values(textRecordsSocial).filter((v) => !!v).length > 0;
|
||||
!!textRecordDescription || !!textRecordLocation || hasTextRecordsSocials;
|
||||
|
||||
if (!hasTextRecordsToDisplay) {
|
||||
return;
|
||||
@@ -47,32 +50,45 @@ export default function UsernameProfileCard() {
|
||||
<p className="break-words font-bold text-illoblack">{textRecordDescription}</p>
|
||||
)}
|
||||
|
||||
<ul className="flex flex-col gap-2">
|
||||
{textRecordsSocialFieldsEnabled.map(
|
||||
(textRecordKey) =>
|
||||
!!existingTextRecords[textRecordKey] && (
|
||||
<li key={textRecordKey}>
|
||||
<Link
|
||||
href={formatSocialFieldUrl(textRecordKey, existingTextRecords[textRecordKey])}
|
||||
target="_blank"
|
||||
className="flex items-center gap-2 text-palette-foregroundMuted hover:text-blue-500"
|
||||
>
|
||||
<span>
|
||||
<Icon
|
||||
name={textRecordsSocialFieldsEnabledIcons[textRecordKey]}
|
||||
height="1rem"
|
||||
width="1rem"
|
||||
color="currentColor"
|
||||
/>
|
||||
</span>
|
||||
<span className="overflow-hidden text-ellipsis">
|
||||
{formatSocialFieldForDisplay(textRecordKey, existingTextRecords[textRecordKey])}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
{!!textRecordLocation && (
|
||||
<p className="flex items-center gap-2 text-palette-foregroundMuted ">
|
||||
<span>
|
||||
<Icon name="map" height="1rem" width="1rem" color="currentColor" />
|
||||
</span>
|
||||
<span className="overflow-hidden text-ellipsis">{textRecordLocation}</span>
|
||||
</p>
|
||||
)}
|
||||
{hasTextRecordsSocials && (
|
||||
<ul className="flex flex-col gap-2">
|
||||
{textRecordsSocialFieldsEnabled.map(
|
||||
(textRecordKey) =>
|
||||
!!existingTextRecords[textRecordKey] && (
|
||||
<li key={textRecordKey}>
|
||||
<Link
|
||||
href={formatSocialFieldUrl(textRecordKey, existingTextRecords[textRecordKey])}
|
||||
target="_blank"
|
||||
className="flex items-center gap-2 text-palette-foregroundMuted hover:text-blue-500"
|
||||
>
|
||||
<span>
|
||||
<Icon
|
||||
name={textRecordsSocialFieldsEnabledIcons[textRecordKey]}
|
||||
height="1rem"
|
||||
width="1rem"
|
||||
color="currentColor"
|
||||
/>
|
||||
</span>
|
||||
<span className="overflow-hidden text-ellipsis">
|
||||
{formatSocialFieldForDisplay(
|
||||
textRecordKey,
|
||||
existingTextRecords[textRecordKey],
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
UsernameTextRecordKeys,
|
||||
} from 'apps/web/src/utils/usernames';
|
||||
import UsernameCastsField from 'apps/web/src/components/Basenames/UsernameCastsField';
|
||||
import UsernameLocationField from 'apps/web/src/components/Basenames/UsernameLocationField';
|
||||
|
||||
const settingTabClass = classNames(
|
||||
'flex flex-col justify-between gap-8 text-gray/60 md:items-center p-4 md:p-8',
|
||||
@@ -72,6 +73,11 @@ export default function UsernameProfileSettingsManageProfile() {
|
||||
value={updatedTextRecords[UsernameTextRecordKeys.Description]}
|
||||
disabled={writeTextRecordsIsPending}
|
||||
/>
|
||||
<UsernameLocationField
|
||||
onChange={onChangeTextRecord}
|
||||
value={updatedTextRecords[UsernameTextRecordKeys.Location]}
|
||||
disabled={writeTextRecordsIsPending}
|
||||
/>
|
||||
<Fieldset>
|
||||
<Label>Socials</Label>
|
||||
{textRecordsSocialFieldsEnabled.map((textRecordKey) => (
|
||||
|
||||
@@ -521,6 +521,23 @@ const ICONS: Record<string, (props: SvgProps) => JSX.Element> = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
|
||||
map: ({ color, width, height }: SvgProps) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M11.4424 23.8252L11.5273 23.8726L11.5612 23.8916C11.6958 23.9627 11.8464 24 11.9994 24C12.1524 24 12.303 23.9627 12.4376 23.8916L12.4715 23.8738L12.5576 23.8252C13.0317 23.5503 13.4943 23.2569 13.9442 22.9457C15.1092 22.1415 16.1976 21.2362 17.1964 20.2409C19.5527 17.8822 22 14.3383 22 9.77851C22 7.18509 20.9464 4.69789 19.0711 2.86406C17.1957 1.03023 14.6522 0 12 0C9.34784 0 6.8043 1.03023 4.92893 2.86406C3.05357 4.69789 2 7.18509 2 9.77851C2 14.3371 4.44848 17.8822 6.80364 20.2409C7.80199 21.2362 8.89003 22.1414 10.0545 22.9457C10.5049 23.2569 10.9679 23.5503 11.4424 23.8252ZM12 13.3343C12.9644 13.3343 13.8893 12.9597 14.5713 12.2929C15.2532 11.626 15.6364 10.7216 15.6364 9.77851C15.6364 8.83545 15.2532 7.93101 14.5713 7.26416C13.8893 6.59732 12.9644 6.22269 12 6.22269C11.0356 6.22269 10.1107 6.59732 9.4287 7.26416C8.74675 7.93101 8.36364 8.83545 8.36364 9.77851C8.36364 10.7216 8.74675 11.626 9.4287 12.2929C10.1107 12.9597 11.0356 13.3343 12 13.3343Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
export function Icon({ name, color = 'white', width = '24', height = '24' }: IconProps) {
|
||||
|
||||
@@ -38,6 +38,7 @@ export default function useReadBaseEnsTextRecords({
|
||||
[UsernameTextRecordKeys.Github]: '',
|
||||
[UsernameTextRecordKeys.Email]: '',
|
||||
[UsernameTextRecordKeys.Phone]: '',
|
||||
[UsernameTextRecordKeys.Location]: '',
|
||||
[UsernameTextRecordKeys.Avatar]: '',
|
||||
[UsernameTextRecordKeys.Frames]: '',
|
||||
[UsernameTextRecordKeys.Casts]: '',
|
||||
|
||||
@@ -63,6 +63,7 @@ export const USERNAME_MIN_CHARACTER_LENGTH = 3;
|
||||
export const USERNAME_MAX_CHARACTER_LENGTH = 20;
|
||||
|
||||
export const USERNAME_DESCRIPTION_MAX_LENGTH = 200;
|
||||
export const USERNAME_LOCATION_MAX_LENGTH = 100;
|
||||
|
||||
// DANGER: Changing this post-mainnet launch means the stored data won't be accessible via the updated key
|
||||
export enum UsernameTextRecordKeys {
|
||||
@@ -75,6 +76,7 @@ export enum UsernameTextRecordKeys {
|
||||
Email = 'email',
|
||||
Phone = 'phone',
|
||||
Avatar = 'avatar',
|
||||
Location = 'location',
|
||||
|
||||
// Socials
|
||||
Github = 'com.github',
|
||||
@@ -177,6 +179,7 @@ export const textRecordsKeysEnabled = [
|
||||
UsernameTextRecordKeys.Github,
|
||||
UsernameTextRecordKeys.Email,
|
||||
UsernameTextRecordKeys.Phone,
|
||||
UsernameTextRecordKeys.Location,
|
||||
UsernameTextRecordKeys.Twitter,
|
||||
UsernameTextRecordKeys.Farcaster,
|
||||
UsernameTextRecordKeys.Lens,
|
||||
@@ -196,6 +199,7 @@ export const textRecordsKeysForDisplay = {
|
||||
[UsernameTextRecordKeys.Github]: 'Github',
|
||||
[UsernameTextRecordKeys.Email]: 'Email',
|
||||
[UsernameTextRecordKeys.Phone]: 'Phone',
|
||||
[UsernameTextRecordKeys.Location]: 'Location',
|
||||
[UsernameTextRecordKeys.Twitter]: 'Twitter / X',
|
||||
[UsernameTextRecordKeys.Farcaster]: 'Farcaster',
|
||||
[UsernameTextRecordKeys.Lens]: 'Lens',
|
||||
@@ -215,6 +219,7 @@ export const textRecordsKeysPlaceholderForDisplay = {
|
||||
[UsernameTextRecordKeys.Github]: 'Username',
|
||||
[UsernameTextRecordKeys.Email]: 'Personal email',
|
||||
[UsernameTextRecordKeys.Phone]: '+1 415 ..',
|
||||
[UsernameTextRecordKeys.Location]: 'New York, NY, USA',
|
||||
[UsernameTextRecordKeys.Twitter]: 'Username',
|
||||
[UsernameTextRecordKeys.Farcaster]: 'Username',
|
||||
[UsernameTextRecordKeys.Lens]: 'name.lens',
|
||||
|
||||
Reference in New Issue
Block a user