diff --git a/public/assets/buttons/droparrowblue.gif b/public/assets/buttons/droparrowblue.gif new file mode 100644 index 00000000..1e45345a Binary files /dev/null and b/public/assets/buttons/droparrowblue.gif differ diff --git a/src/components/account-bar/account-bar.module.css b/src/components/account-bar/account-bar.module.css index cfc0b94a..515ff2e8 100644 --- a/src/components/account-bar/account-bar.module.css +++ b/src/components/account-bar/account-bar.module.css @@ -18,7 +18,7 @@ .userDropdownButton { background: none no-repeat scroll center right; - background-image: url("/public/assets/buttons/droparrowgray.gif"); + background-image: var(--blue-dropdown-arrow); padding-right: 21px; margin-right: -5px; cursor: pointer; @@ -75,5 +75,32 @@ } .selectedTextButton { - color: var(--text-secondary); + color: var(--green); +} + +.dropdown { + float: left; + cursor: pointer; + position: relative; +} + +.dropChoices { + top: 16px; + position: absolute; + left: 0px; + border: 1px solid var(--border-text); + background-color: var(--background); + color: var(--text-primary) ; + white-space: nowrap; + line-height: normal; +} + +.dropdownChoice { + cursor: pointer; + padding: 2px 3px 1px 3px; + display: block; +} + +.dropdownChoice:hover { + background-color: var(--background-primary); } \ No newline at end of file diff --git a/src/components/account-bar/account-bar.tsx b/src/components/account-bar/account-bar.tsx index a8d6cd05..a387055a 100644 --- a/src/components/account-bar/account-bar.tsx +++ b/src/components/account-bar/account-bar.tsx @@ -22,9 +22,11 @@ const AccountBar = () => { const searchBarRef = useRef(null); const searchBarButtonRef = useRef(null); - const [accountSelectVisible, setAccountSelectVisible] = useState(false); - const toggleAccountSelectVisible = () => setAccountSelectVisible(!accountSelectVisible); - const accountSelectRef = useRef(null); + const [isAccountDropdownOpen, setIsAccountDropdownOpen] = useState(false); + const toggleAccountDropdown = () => setIsAccountDropdownOpen(!isAccountDropdownOpen); + const accountDropdownRef = useRef(null); + const accountDropdownChoicesRef = useRef(null); + const accountDropdownClass = isAccountDropdownOpen ? styles.visible : styles.hidden; const accountSelectButtonRef = useRef(null); let submitLink; @@ -41,17 +43,22 @@ const AccountBar = () => { const isOutsideSearchBar = searchBarRef.current && !searchBarRef.current.contains(target) && searchBarButtonRef.current && !searchBarButtonRef.current.contains(target); - const isOutsideAccountSelect = - accountSelectRef.current && !accountSelectRef.current.contains(target) && accountSelectButtonRef.current && !accountSelectButtonRef.current.contains(target); + const isOutsideAccountDropdown = + accountDropdownRef.current && + !accountDropdownRef.current.contains(target) && + accountDropdownChoicesRef.current && + !accountDropdownChoicesRef.current.contains(target); + const isOutsideAccountSelectButton = accountSelectButtonRef.current && !accountSelectButtonRef.current.contains(target); + + if (isOutsideAccountSelectButton && isOutsideAccountDropdown) { + setIsAccountDropdownOpen(false); + } if (isOutsideSearchBar) { setSearchVisible(false); } - if (isOutsideAccountSelect) { - setAccountSelectVisible(false); - } }, - [searchBarRef, accountSelectRef], + [searchBarRef, accountSelectButtonRef, accountDropdownRef, accountDropdownChoicesRef], ); useEffect(() => { @@ -61,39 +68,31 @@ const AccountBar = () => { }; }, [handleClickOutside]); - const accountsOptions = accounts.map((account) => ( - + const accountDropdownOptions = accounts.map((account, index) => ( + setActiveAccount(account?.name)}> + {`u/${account?.author?.shortAddress}`} + )); - accountsOptions[accountsOptions.length] = ( - + , ); - const onAccountSelectChange = async (e: React.ChangeEvent) => { - if (e.target.value === 'createAccount') { - createAccount(); - } else { - setActiveAccount(e.target.value); - } - }; - return (
e.preventDefault()}> {account?.author?.shortAddress} - - {accountSelectVisible && ( - - - + + {isAccountDropdownOpen && ( +
+
+ {accountDropdownOptions} +
+
)}
diff --git a/src/components/header/header.module.css b/src/components/header/header.module.css index 7807d9ac..d07575c8 100644 --- a/src/components/header/header.module.css +++ b/src/components/header/header.module.css @@ -118,13 +118,13 @@ .tabMenu li a { padding: 2px 6px 0 6px; text-decoration: none; - color: var(--text-secondary); + color: var(--green); background-color: var(--background); border-bottom: 1px solid var(--background); } .tabMenu li .selected { - color: var(--text-secondary); + color: var(--green); background-color: var(--background); border: 1px solid var(--border-primary); border-bottom: 2px solid var(--background); diff --git a/src/components/post/label/label.module.css b/src/components/post/label/label.module.css index 9a95ab7a..f64feefd 100644 --- a/src/components/post/label/label.module.css +++ b/src/components/post/label/label.module.css @@ -13,8 +13,8 @@ } .stampPending { - color: var(--label-pending); - border-color: var(--label-pending); + color: var(--yellow); + border-color: var(--yellow); } .stampFailed { diff --git a/src/components/post/post.module.css b/src/components/post/post.module.css index 0e091ebc..50e822b8 100644 --- a/src/components/post/post.module.css +++ b/src/components/post/post.module.css @@ -94,12 +94,12 @@ } .pinnedLink { - color: var(--text-secondary) !important; + color: var(--green) !important; font-weight: bold !important; } .announcement { - color: var(--text-secondary) !important; + color: var(--green) !important; } .domain { @@ -171,10 +171,10 @@ animation-iteration-count: 1; } -.moderator { - color: var(--text-secondary) !important; +.moderator, .admin, .owner { + color: var(--green) !important; } -.admin, .owner { - color: var(--red) !important; +.displayName { + color: var(--text-primary); } \ No newline at end of file diff --git a/src/components/post/post.tsx b/src/components/post/post.tsx index 9a8cce3e..adeb7f11 100644 --- a/src/components/post/post.tsx +++ b/src/components/post/post.tsx @@ -120,16 +120,20 @@ const Post = ({ post, index }: PostProps) => { )}

{t('post_submitted')} {getFormattedTimeAgo(timestamp)} {t('post_by')}{' '} + {post?.author?.displayName && ( + {post?.author?.displayName} + )} e.preventDefault()}> u/{post?.author?.shortAddress || shortAuthorAddress} u/{shortAuthorAddress} {(isAuthorOwner || isAuthorAdmin || isAuthorModerator) && ( - {' '}[ - - {(isAuthorOwner && 'O') || (isAuthorAdmin && 'A') || (isAuthorModerator && 'M')} - + {' '} + [ + + {(isAuthorOwner && 'O') || (isAuthorAdmin && 'A') || (isAuthorModerator && 'M')} + ] )} @@ -140,11 +144,9 @@ const Post = ({ post, index }: PostProps) => { {' '} p/{subplebbit?.shortAddress || subplebbitAddress} - - )} - {pinned && ( - - {t('announcement')} + )} + {pinned && - {t('announcement')}}

diff --git a/src/components/reply/reply.module.css b/src/components/reply/reply.module.css index f41c6ffd..373e24d2 100644 --- a/src/components/reply/reply.module.css +++ b/src/components/reply/reply.module.css @@ -125,10 +125,6 @@ color: var(--text-info); } -.moderator { - color: var(--text-secondary) !important; -} - -.admin, .owner { - color: var(--red) !important; +.moderator, .admin, .owner { + color: var(--green) !important; } \ No newline at end of file diff --git a/src/components/reply/reply.tsx b/src/components/reply/reply.tsx index 9a0adaba..9551ba1f 100644 --- a/src/components/reply/reply.tsx +++ b/src/components/reply/reply.tsx @@ -71,7 +71,7 @@ const ReplyMedia = ({ commentMediaInfo, content, expanded, hasThumbnail, link, l const Reply = ({ reply, depth }: ReplyProps) => { const { cid, content, downvoteCount, flair, link, linkHeight, linkWidth, removed, spoiler, subplebbitAddress, timestamp, upvoteCount } = reply || {}; - const subplebbit = useSubplebbit({subplebbitAddress}); + const subplebbit = useSubplebbit({ subplebbitAddress }); const isAuthorOwner = subplebbit?.roles?.[reply.author.address]?.role === 'owner'; const isAuthorAdmin = subplebbit?.roles?.[reply.author.address]?.role === 'admin'; @@ -147,6 +147,9 @@ const Reply = ({ reply, depth }: ReplyProps) => {

[–] + {reply?.author?.displayName && ( + {reply?.author?.displayName} + )} { @@ -154,14 +157,14 @@ const Reply = ({ reply, depth }: ReplyProps) => { }} className={`${styles.author} ${moderatorClass}`} > - {shortAuthorAddress} + {reply?.author?.displayName ? `u/${shortAuthorAddress}` : shortAuthorAddress} {(isAuthorOwner || isAuthorAdmin || isAuthorModerator) && ( [ - - {(isAuthorOwner && 'O') || (isAuthorAdmin && 'A') || (isAuthorModerator && 'M')} - + + {(isAuthorOwner && 'O') || (isAuthorAdmin && 'A') || (isAuthorModerator && 'M')} + ]{' '} )} diff --git a/src/components/sidebar/sidebar.tsx b/src/components/sidebar/sidebar.tsx index a975197a..729f7447 100644 --- a/src/components/sidebar/sidebar.tsx +++ b/src/components/sidebar/sidebar.tsx @@ -48,11 +48,7 @@ const Sidebar = ({ address, cid, createdAt, description, downvoteCount = 0, role const rulesList = (

Rules -
    - {rules?.map((rule, index) => ( -
  1. {rule}
  2. - ))} -
+
    {rules?.map((rule, index) =>
  1. {rule}
  2. )}
); diff --git a/src/components/topbar/topbar.module.css b/src/components/topbar/topbar.module.css index 66ea172e..12414600 100644 --- a/src/components/topbar/topbar.module.css +++ b/src/components/topbar/topbar.module.css @@ -17,12 +17,15 @@ .dropdown { float: left; - padding-left: 5px; cursor: pointer; display: inline; position: relative; } +.subsDropdown { + padding-left: 5px; +} + .dropChoices { top: 18px; margin-top: 0; @@ -106,7 +109,7 @@ } .srList .srBar li .selected { - color: var(--text-secondary) !important; + color: var(--green) !important; font-weight: bold; } diff --git a/src/components/topbar/topbar.tsx b/src/components/topbar/topbar.tsx index 87799151..aa35a147 100644 --- a/src/components/topbar/topbar.tsx +++ b/src/components/topbar/topbar.tsx @@ -94,7 +94,7 @@ const TopBar = () => { return (
-
+
{t('topbar_my_subs')}
{subscriptions?.map((subscription: string, index: number) => ( diff --git a/src/themes.css b/src/themes.css index ed39a7e3..8b8ee692 100644 --- a/src/themes.css +++ b/src/themes.css @@ -13,7 +13,7 @@ --text-primary: #369; --button-border-primary: #c4dbf1; --button-border-primary-hover: #879eb4; - --text-secondary: #228822; + --green: #228822; --text-info: #888; --border-primary: #5f99cf; --border-text: gray; @@ -27,13 +27,14 @@ --play-button-hover: url("/public/assets/buttons/play-button-hover.png"); --close-button: url("/public/assets/buttons/close-button.png"); --close-button-hover: url("/public/assets/buttons/close-button-hover.png"); - --label-pending: goldenrod; + --yellow: goldenrod; --red: red; --button-large: url('/public/assets/buttons/button-large.png'); --button-large-hover: url('/public/assets/buttons/button-large-hover.png'); --button-large-nub: url('/public/assets/buttons/button-large-nub.png'); --button-large-hover-nub: url('/public/assets/buttons/button-large-nub-hover.png'); --box-shadow: inset 0px 1px 1px rgba(0,0,0,0.3),0px 1px 0px rgba(255,255,255,0.6); + --blue-dropdown-arrow: url("/public/assets/buttons/droparrowblue.gif"); } :root .dark { @@ -51,7 +52,7 @@ --text-primary: #c7c7c7; --button-border-primary: #1f1f1f; --button-border-primary-hover: #3e3e3e; - --text-secondary: #228822; + --green: #228822; --text-info: #757575; --border-primary: #1f1f1f; --border-text: #3e3e3e; @@ -65,11 +66,12 @@ --play-button-hover: url("/public/assets/buttons/play-button-hover.png"); --close-button: url("/public/assets/buttons/close-button-dark.png"); --close-button-hover: url("/public/assets/buttons/close-button-hover.png"); - --label-pending: rgb(200, 171, 0); + --yellow: rgb(200, 171, 0); --red: rgb(200, 0, 0); --button-large: url('/public/assets/buttons/button-large-dark.png'); --button-large-hover: url('/public/assets/buttons/button-large-hover-dark.png'); --button-large-nub: url('/public/assets/buttons/button-large-nub-dark.png'); --button-large-hover-nub: url('/public/assets/buttons/button-large-nub-hover-dark.png'); --box-shadow: none; + --blue-dropdown-arrow: url("/public/assets/buttons/droparrowgray.gif"); } \ No newline at end of file diff --git a/src/views/settings/settings.module.css b/src/views/settings/settings.module.css index 13c91c26..571d9f75 100644 --- a/src/views/settings/settings.module.css +++ b/src/views/settings/settings.module.css @@ -60,12 +60,22 @@ padding-left: 5px; font-style: italic; text-transform: capitalize; - color: var(--text-secondary); + color: var(--green); } -button { +.checkCryptoAddress { + margin-top: 5px; + margin-left: -5px; +} + +.categorySettings button { text-transform: capitalize; margin-left: 5px; + cursor: pointer; +} + +.categorySettings select { + cursor: pointer; } .accountButtons button { @@ -102,8 +112,8 @@ button { max-height: 60vh; } - input { - width: 35vw !important; + .categorySettings input { + width: 35vw; } } @@ -113,4 +123,16 @@ button { position: relative; bottom: 2px; margin-bottom: -2px; +} + +.resolvedMessageSuccess { + color: var(--green); +} + +.resolvedMessageFailed { + color: var(--red); +} + +.resolvedMessageResolving { + color: var(--yellow); } \ No newline at end of file diff --git a/src/views/settings/settings.tsx b/src/views/settings/settings.tsx index 53fc2e0a..309a6d8b 100644 --- a/src/views/settings/settings.tsx +++ b/src/views/settings/settings.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { createAccount, deleteAccount, importAccount, setAccount, setActiveAccount, useAccount, useAccounts } from '@plebbit/plebbit-react-hooks'; +import { createAccount, deleteAccount, importAccount, setAccount, setActiveAccount, useAccount, useAccounts, useResolvedAuthorAddress } from '@plebbit/plebbit-react-hooks'; import stringify from 'json-stringify-pretty-compact'; import useTheme from '../../hooks/use-theme'; import styles from './settings.module.css'; @@ -44,31 +44,120 @@ const ThemeSettings = () => { }; const ProfileSettings = () => { - const cryptoAddressInfo = () => { - alert('A crypto address is more readable than a long string of characters. It can be used to send you crypto directly.\n\nChange your account address to an ENS name you own: in your ENS name page on ens.domains, click on "Records", "Edit Records", "Add record", add "plebbit-author-address" as record name, add your full address as value (you can copy it from your account data) and save.'); + const account = useAccount(); + + const [username, setUsername] = useState(account?.author.displayName || ''); + const [savedUsername, setSavedUsername] = useState(false); + + useEffect(() => { + if (savedUsername) { + setTimeout(() => { + setSavedUsername(false); + }, 2000); + } + }, [savedUsername]); + + const saveUsername = async () => { + try { + await setAccount({ ...account, author: { ...account?.author, displayName: username } }); + } catch (error) { + if (error instanceof Error) { + alert(error.message); + console.log(error); + } else { + console.error('An unknown error occurred:', error); + } + } + setSavedUsername(true); }; + const [cryptoAddress, setCryptoAddress] = useState(account?.author.displayName || ''); + const [savedCryptoAddress, setSavedCryptoAddress] = useState(false); + const [checkCryptoAddress, setCheckCryptoAddress] = useState(false); + const author = { ...account?.author, address: cryptoAddress }; + const { state, error, chainProvider } = useResolvedAuthorAddress({ author, cache: false }); + const resolvedAddressInfoMessageClass = `${state === 'succeeded' ? styles.resolvedMessageSuccess : state === 'failed' ? styles.resolvedMessageFailed : state === 'resolving' ? styles.resolvedMessageResolving : ''}` + + const resolvedAddressInfoMessage = useMemo(() => { + if (state === 'succeeded') { + return 'crypto address resolved successfully'; + } else if (state === 'failed') { + if (error instanceof Error) { + return `failed to resolve crypto address, ${error.message}`; + } else { + return 'cannot resolve crypto address'; + } + } else if (state === 'resolving') { + return `resolving from ${chainProvider?.urls}`; + } else { + return ''; + } + }, [state, error, chainProvider]); + + const cryptoAddressInfo = () => { + alert( + 'Change your account address to an ENS name you own: in your ENS name page on ens.domains, click on "Records", "Edit Records", "Add record", add "plebbit-author-address" as record name, add your full address as value (you can copy it from your account data) and save.', + ); + }; + + const saveCryptoAddress = async () => { + if (state !== 'succeeded') { + alert('Cannot save crypto address, it is not resolved'); + return; + } + + try { + await setAccount({ ...account, author: { ...account?.author, address: cryptoAddress } }); + } catch (error) { + if (error instanceof Error) { + alert(error.message); + console.log(error); + } else { + console.error('An unknown error occurred:', error); + } + } + setSavedCryptoAddress(true); + } + + useEffect(() => { + if (savedCryptoAddress) { + setTimeout(() => { + setSavedCryptoAddress(false); + }, 2000); + } + }, [savedCryptoAddress]); + return ( - username + display name
- - - Saved. + setUsername(e.target.value)} /> + + {savedUsername && Saved.}
crypto address - +
- - - - Saved. + setCryptoAddress(e.target.value)} /> + + {savedCryptoAddress && Saved.}
+
+ {' '} + {checkCryptoAddress ? ( + {resolvedAddressInfoMessage} + ) : 'if the crypto address is resolved p2p'} +
+
); -} +}; const AccountSettings = () => { const account = useAccount(); @@ -76,17 +165,17 @@ const AccountSettings = () => { const [text, setText] = useState(''); const [switchToLastAccount, setSwitchToLastAccount] = useState(false); - const accountJson = useMemo(() => stringify({account: {...account, plebbit: undefined, karma: undefined, unreadNotificationCount: undefined}}), [account]) + const accountJson = useMemo(() => stringify({ account: { ...account, plebbit: undefined, karma: undefined, unreadNotificationCount: undefined } }), [account]); const accountsOptions = accounts.map((account) => ( )); useEffect(() => { - setText(accountJson) - }, [accountJson]) + setText(accountJson); + }, [accountJson]); useEffect(() => { if (switchToLastAccount && accounts.length > 0) { @@ -108,25 +197,25 @@ const AccountSettings = () => { } } setSwitchToLastAccount(true); - } + }; const _deleteAccount = (accountName: string) => { if (!accountName) { - return + return; } else if (window.confirm(`Are you sure you want to delete ${accountName}?`)) { deleteAccount(accountName); setSwitchToLastAccount(true); } else { - return + return; } - } + }; const saveAccount = async () => { try { - const newAccount = JSON.parse(text).account + const newAccount = JSON.parse(text).account; // force keeping the same id, makes it easier to copy paste - await setAccount({...newAccount, id: account?.id}) - alert(`Saved ${newAccount.name}`) + await setAccount({ ...newAccount, id: account?.id }); + alert(`Saved ${newAccount.name}`); } catch (error) { if (error instanceof Error) { alert(error.message); @@ -135,14 +224,14 @@ const AccountSettings = () => { console.error('An unknown error occurred:', error); } } - } + }; const _importAccount = async () => { try { - const newAccount = JSON.parse(text) - await importAccount(text) + const newAccount = JSON.parse(text); + await importAccount(text); setSwitchToLastAccount(true); - alert(`Imported ${newAccount.account?.name}`) + alert(`Imported ${newAccount.account?.name}`); } catch (error) { if (error instanceof Error) { alert(error.message); @@ -151,16 +240,19 @@ const AccountSettings = () => { console.error('An unknown error occurred:', error); } } - } + }; return (
- is the current account + {' '} + is the current account
account data
-