pl-fe: display account local time if specified
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -14,12 +14,14 @@ import Icon from 'pl-fe/components/ui/icon';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import ActionButton from 'pl-fe/features/ui/components/action-button';
|
||||
import { isTimezoneLabel } from 'pl-fe/features/ui/components/profile-field';
|
||||
import { UserPanel } from 'pl-fe/features/ui/util/async-components';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { accountScrobbleQueryOptions } from 'pl-fe/queries/accounts/account-scrobble';
|
||||
import { useAccountHoverCardStore } from 'pl-fe/stores/account-hover-card';
|
||||
|
||||
import AccountLocalTime from './account-local-time';
|
||||
import { showAccountHoverCard } from './hover-account-wrapper';
|
||||
import { ParsedContent } from './parsed-content';
|
||||
import { dateFormatOptions } from './relative-timestamp';
|
||||
@ -77,6 +79,7 @@ const AccountHoverCard: React.FC<IAccountHoverCard> = ({ visible = true }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
const { x, y, strategy, refs, context, placement } = useFloating({
|
||||
open: !!account,
|
||||
elements: {
|
||||
@ -115,6 +118,8 @@ const AccountHoverCard: React.FC<IAccountHoverCard> = ({ visible = true }) => {
|
||||
const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' });
|
||||
const followedBy = me !== account.id && account.relationship?.followed_by === true;
|
||||
|
||||
const timezoneField = account.fields.find(field => isTimezoneLabel(field.name));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx({
|
||||
@ -158,6 +163,10 @@ const AccountHoverCard: React.FC<IAccountHoverCard> = ({ visible = true }) => {
|
||||
</HStack>
|
||||
) : null}
|
||||
|
||||
{timezoneField && (
|
||||
<AccountLocalTime accountId={account.id} field={timezoneField} />
|
||||
)}
|
||||
|
||||
{account.pronouns.length > 0 && (
|
||||
<HStack alignItems='center' space={0.5}>
|
||||
<Icon
|
||||
|
||||
94
packages/pl-fe/src/components/account-local-time.tsx
Normal file
94
packages/pl-fe/src/components/account-local-time.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import { Account } from 'pl-api';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { useLoggedIn } from 'pl-fe/hooks/use-logged-in';
|
||||
|
||||
import HStack from './ui/hstack';
|
||||
import Icon from './ui/icon';
|
||||
import Text from './ui/text';
|
||||
|
||||
const supportedTimeZones = Intl.supportedValuesOf('timeZone');
|
||||
const UTC_REGEX = /(GMT|UTC)([+-])([0-9]{1,2})/i;
|
||||
|
||||
const getSupportedTimezone = (value: string): string | false => {
|
||||
let foundTimezone = supportedTimeZones.find((tz) => value.toLowerCase().startsWith(tz.toLowerCase()));
|
||||
if (!foundTimezone) {
|
||||
const match = value.match(UTC_REGEX);
|
||||
if (match) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_, __, sign, hours] = match;
|
||||
foundTimezone = supportedTimeZones.find((tz) => tz.toLowerCase() === `etc/gmt${sign === '+' ? '-' : '+'}${+hours}`);
|
||||
}
|
||||
}
|
||||
return foundTimezone || false;
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
timezone: { id: 'account.timezone', defaultMessage: 'Timezone: {timezone}' },
|
||||
});
|
||||
|
||||
interface IAccountLocalTime {
|
||||
accountId: string;
|
||||
field: Account['fields'][number];
|
||||
}
|
||||
|
||||
const AccountLocalTime: React.FC<IAccountLocalTime> = ({ accountId, field }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { me } = useLoggedIn();
|
||||
const [localTime, setLocalTime] = useState<string | null>(null);
|
||||
const [isTimezoneEqual, setIsTimezoneEqual] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const timezone = getSupportedTimezone(field.value);
|
||||
if (!timezone) return;
|
||||
|
||||
const format = Intl.DateTimeFormat(intl.locale, {
|
||||
timeZone: timezone,
|
||||
timeStyle: 'short',
|
||||
});
|
||||
|
||||
{
|
||||
const dateNow = new Date();
|
||||
const yourTime = dateNow.toLocaleString(intl.locale, { timeStyle: 'short' });
|
||||
const userLocalTime = format.format(dateNow);
|
||||
setLocalTime(userLocalTime);
|
||||
setIsTimezoneEqual(userLocalTime === yourTime);
|
||||
}
|
||||
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
const init = setInterval(() => {
|
||||
if (new Date().getSeconds() === 0) {
|
||||
clearInterval(init);
|
||||
timer = setInterval(() => {
|
||||
setLocalTime(format.format(new Date()));
|
||||
}, 60000);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (timer) clearInterval(timer);
|
||||
clearInterval(init);
|
||||
};
|
||||
}, [field.name]);
|
||||
|
||||
if (!localTime) return null;
|
||||
|
||||
return (
|
||||
<HStack className='mt-1' alignItems='center' space={0.5} title={intl.formatMessage(messages.timezone, { timezone: field.value })}>
|
||||
<Icon
|
||||
src={require('@tabler/icons/outline/clock.svg')}
|
||||
className='size-4 text-gray-800 dark:text-gray-200'
|
||||
/>
|
||||
<Text size='sm'>
|
||||
{localTime}
|
||||
{me !== accountId && isTimezoneEqual && (
|
||||
<span className='text-green-500'> <FormattedMessage id='account.timezone.equal' defaultMessage='(same as you)' /></span>
|
||||
)}
|
||||
</Text>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export { AccountLocalTime as default };
|
||||
@ -8,7 +8,7 @@ import ProfileField from '../profile-field';
|
||||
import type { Account } from 'pl-fe/normalizers/account';
|
||||
|
||||
interface IProfileFieldsPanel {
|
||||
account: Pick<Account, 'emojis' | 'fields'>;
|
||||
account: Pick<Account, 'emojis' | 'fields' | 'id'>;
|
||||
}
|
||||
|
||||
/** Custom profile fields for sidebar. */
|
||||
@ -16,7 +16,7 @@ const ProfileFieldsPanel: React.FC<IProfileFieldsPanel> = ({ account }) => (
|
||||
<Widget>
|
||||
<Stack space={4}>
|
||||
{account.fields.map((field, i) => (
|
||||
<ProfileField field={field} key={i} emojis={account.emojis} />
|
||||
<ProfileField field={field} key={i} emojis={account.emojis} accountId={account.id} />
|
||||
))}
|
||||
</Stack>
|
||||
</Widget>
|
||||
|
||||
@ -231,7 +231,7 @@ const ProfileInfoPanel: React.FC<IProfileInfoPanel> = ({ account, username }) =>
|
||||
{account.fields.length > 0 && (
|
||||
<Stack space={2} className='mt-4 xl:hidden'>
|
||||
{account.fields.map((field, i) => (
|
||||
<ProfileField field={field} key={i} emojis={account.emojis} />
|
||||
<ProfileField field={field} key={i} emojis={account.emojis} accountId={account.id} />
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl, FormatDateOptions } from 'react-intl';
|
||||
import { defineMessages, useIntl, type FormatDateOptions } from 'react-intl';
|
||||
|
||||
import AccountLocalTime from 'pl-fe/components/account-local-time';
|
||||
import Markup from 'pl-fe/components/markup';
|
||||
import { ParsedContent } from 'pl-fe/components/parsed-content';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
@ -16,6 +17,8 @@ const getTicker = (value: string): string => (value.match(/\$([a-zA-Z]*)/i) || [
|
||||
const isTicker = (value: string): boolean => Boolean(getTicker(value));
|
||||
const isZapEmoji = (value: string) => /^\u26A1[\uFE00-\uFE0F]?$/.test(value);
|
||||
|
||||
const isTimezoneLabel = (value: string) => /^time( |)zone$/i.test(value);
|
||||
|
||||
const messages = defineMessages({
|
||||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
||||
});
|
||||
@ -30,12 +33,13 @@ const dateFormatOptions: FormatDateOptions = {
|
||||
};
|
||||
|
||||
interface IProfileField {
|
||||
accountId: string;
|
||||
field: Account['fields'][number];
|
||||
emojis?: Account['emojis'];
|
||||
}
|
||||
|
||||
/** Renders a single profile field. */
|
||||
const ProfileField: React.FC<IProfileField> = ({ field, emojis }) => {
|
||||
const ProfileField: React.FC<IProfileField> = ({ accountId, field, emojis }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
if (isTicker(field.name)) {
|
||||
@ -72,9 +76,12 @@ const ProfileField: React.FC<IProfileField> = ({ field, emojis }) => {
|
||||
<ParsedContent html={field.value} emojis={emojis} />
|
||||
</Markup>
|
||||
</HStack>
|
||||
{isTimezoneLabel(field.name) && (
|
||||
<AccountLocalTime accountId={accountId} field={field} />
|
||||
)}
|
||||
</dd>
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
||||
export { ProfileField as default };
|
||||
export { ProfileField as default, isTimezoneLabel };
|
||||
|
||||
@ -73,6 +73,8 @@
|
||||
"account.subscribe.failure": "An error occurred trying to subscribe to this account.",
|
||||
"account.subscribe.success": "You have subscribed to this account.",
|
||||
"account.subscribers": "Subscribers",
|
||||
"account.timezone": "Timezone: {timezone}",
|
||||
"account.timezone.equal": "(same as you)",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unendorse": "Don't feature on profile",
|
||||
|
||||
Reference in New Issue
Block a user