diff --git a/frontend/packages/client/package.json b/frontend/packages/client/package.json index 6bb507d02..18ebeee99 100644 --- a/frontend/packages/client/package.json +++ b/frontend/packages/client/package.json @@ -18,13 +18,14 @@ "@tanstack/react-query": "4.3.3", "@tanstack/react-query-devtools": "4.3.3", "@trivago/prettier-plugin-sort-imports": "^3.3.0", - "buffer": "^6.0.3", "@types/jest": "^29.1.2", "@types/node": "^18.8.5", "@types/react": "^18.0.21", "@types/react-blockies": "^1.4.1", + "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.6", "@types/react-router-dom": "^5.3.3", + "buffer": "^6.0.3", "bulma": "0.9.3", "bulma-popover": "^1.1.1", "classnames": "^2.2.6", diff --git a/frontend/packages/client/src/App.sass b/frontend/packages/client/src/App.sass index f56712528..ce3a8efc7 100644 --- a/frontend/packages/client/src/App.sass +++ b/frontend/packages/client/src/App.sass @@ -1,4 +1,3 @@ -@import url('https://fonts.googleapis.com/css?family=DM+Sans:regular,bold,italic&subset=latin,latin-ext') @charset "utf-8" // Set brand colors diff --git a/frontend/packages/client/src/components/CommunityLinks.js b/frontend/packages/client/src/components/CommunityLinks.js index 33ccea87f..49965b589 100644 --- a/frontend/packages/client/src/components/CommunityLinks.js +++ b/frontend/packages/client/src/components/CommunityLinks.js @@ -8,9 +8,16 @@ export default function CommunityLinks({ discordUrl, githubUrl, } = {}) { + const showTitle = [ + instagramUrl, + twitterUrl, + websiteUrl, + discordUrl, + githubUrl, + ].every((val) => !!val); return (
- Links + {showTitle && Links} {websiteUrl && ( ((props, ref) => { + // 1. Reuse the `useTab` hook + const tabProps = useTab({ ...props, ref }); + const isSelected = !!tabProps['aria-selected']; + + // 2. Hook into the Tabs `size`, `variant`, props + const styles = useMultiStyleConfig('Tabs', tabProps); + + return ( + + + + {tabProps.children} + + {isSelected ? ( + + ) : null} + + + ); +}); + +export default CustomTab; diff --git a/frontend/packages/client/src/components/Proposal/HeaderNavigation.js b/frontend/packages/client/src/components/Proposal/HeaderNavigation.js index a8360955c..16ab06776 100644 --- a/frontend/packages/client/src/components/Proposal/HeaderNavigation.js +++ b/frontend/packages/client/src/components/Proposal/HeaderNavigation.js @@ -1,7 +1,7 @@ import { JoinCommunityButton } from 'components'; import { useMediaQuery, useVotesForAddress } from 'hooks'; import BackButton from './BackButton'; -import ShareDropdown from './ShareDropdown'; +import ShareDropdown from './ShareProposalDropdown'; const HeaderNavigation = ({ communityId, diff --git a/frontend/packages/client/src/components/Proposal/ShareDropdown.js b/frontend/packages/client/src/components/Proposal/ShareDropdown.js deleted file mode 100644 index 5e6044476..000000000 --- a/frontend/packages/client/src/components/Proposal/ShareDropdown.js +++ /dev/null @@ -1,92 +0,0 @@ -import { useRef, useState } from 'react'; -import CopyToClipboard from 'react-copy-to-clipboard'; -import { Svg } from '@cast/shared-components'; -import { useOnClickOutside } from 'hooks'; -import { FRONTEND_URL } from 'const'; -import classnames from 'classnames'; - -export default function ShareDropdown({ - communityId, - proposalId, - proposalName = '', - isMobile, - userVoted, -} = {}) { - const [dropdownStatus, setDropdownStatus] = useState(''); - const dropdownRef = useRef(); - // use for click out on dropdown - const closeDropDown = () => { - setDropdownStatus(''); - }; - useOnClickOutside(dropdownRef, closeDropDown); - - const styleButtons = isMobile ? { maxHeight: '32px' } : { maxHeight: '40px' }; - - const stylesShareButton = classnames( - 'button rounded-lg is-flex has-text-weight-bold has-background-white px-4 ml-4', - { 'small-text': isMobile } - ); - - const proposalUrl = `${FRONTEND_URL}/#/community/${communityId}/proposal/${proposalId}`; - - const twitterPost = `https://twitter.com/intent/tweet?text=${encodeURIComponent( - userVoted - ? `I just voted on ${proposalName} on CAST! ${proposalUrl}` - : `Check out ${proposalName} on CAST! ${proposalUrl}` - )} `; - - return ( -
-
-
setDropdownStatus('is-active')} - > - Share -
-
-
- ); -} diff --git a/frontend/packages/client/src/components/Proposal/ShareProposalDropdown.tsx b/frontend/packages/client/src/components/Proposal/ShareProposalDropdown.tsx new file mode 100644 index 000000000..7c4a8a310 --- /dev/null +++ b/frontend/packages/client/src/components/Proposal/ShareProposalDropdown.tsx @@ -0,0 +1,39 @@ +import { ShareDropdown } from 'components'; +import { FRONTEND_URL } from 'const'; +import { Box } from '@chakra-ui/react'; + +interface ShareProposalDropdownProps { + communityId: string; + proposalId: string; + proposalName: string; + userVoted: boolean; +} +const ShareProposalDropdown: React.FC = ( + { + communityId, + proposalId, + proposalName = '', + userVoted, + } = {} as ShareProposalDropdownProps +) => { + const proposalUrl = `${FRONTEND_URL}/#/community/${communityId}/proposal/${proposalId}`; + + const twitterPost = `https://twitter.com/intent/tweet?text=${encodeURIComponent( + userVoted + ? `I just voted on ${proposalName} on CAST! ${proposalUrl}` + : `Check out ${proposalName} on CAST! ${proposalUrl}` + )} `; + + return ( + + + + ); +}; +export default ShareProposalDropdown; diff --git a/frontend/packages/client/src/components/ShareDropdown.tsx b/frontend/packages/client/src/components/ShareDropdown.tsx new file mode 100644 index 000000000..1ea42697f --- /dev/null +++ b/frontend/packages/client/src/components/ShareDropdown.tsx @@ -0,0 +1,96 @@ +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { Svg } from '@cast/shared-components'; +import { useMediaQuery } from 'hooks'; +import { + Flex, + Link, + Menu, + MenuButton, + MenuDivider, + MenuItem, + MenuList, + Text, +} from '@chakra-ui/react'; + +interface ShareDropdownProps { + twitterShareString: string; + copyString: string; + isIconOnly?: boolean; + offset?: [number, number]; + direction?: 'ltr' | 'rtl'; +} +const ShareDropdown: React.FC = ({ + isIconOnly = true, + twitterShareString = '', + copyString = '', + offset, + direction, +}) => { + const isBiggerThanMobile = useMediaQuery(); + + const twitterPost = `https://twitter.com/intent/tweet?text=${encodeURIComponent( + twitterShareString + )} `; + + return ( + <> + + + + + {!isIconOnly ? ( + + Share + + ) : null} + + + + + {}}> + + + + Copy Link + + + + + + + + + Tweet + + + + + + ); +}; +export default ShareDropdown; diff --git a/frontend/packages/client/src/components/Tooltip.js b/frontend/packages/client/src/components/Tooltip.tsx similarity index 67% rename from frontend/packages/client/src/components/Tooltip.js rename to frontend/packages/client/src/components/Tooltip.tsx index 5e46eb162..f891e6ef6 100644 --- a/frontend/packages/client/src/components/Tooltip.js +++ b/frontend/packages/client/src/components/Tooltip.tsx @@ -1,11 +1,19 @@ -export default function Tooltip({ +interface TooltipProps { + enabled: boolean; + position: 'left' | 'right' | 'top' | 'bottom'; + text: string; + children?: React.ReactNode; + classNames: string; + alwaysVisible: boolean; +} +const Tooltip: React.FC = ({ enabled = true, position, text, children, classNames = '', alwaysVisible = false, -}) { +}) => { const positionConfig = { left: 'has-tooltip-left', right: 'has-tooltip-right', @@ -23,4 +31,6 @@ export default function Tooltip({ {children} ); -} +}; + +export default Tooltip; diff --git a/frontend/packages/client/src/components/UserProfile/ShareProfileDropdown.tsx b/frontend/packages/client/src/components/UserProfile/ShareProfileDropdown.tsx new file mode 100644 index 000000000..5ff731752 --- /dev/null +++ b/frontend/packages/client/src/components/UserProfile/ShareProfileDropdown.tsx @@ -0,0 +1,27 @@ +import { ShareDropdown } from 'components'; +import { FRONTEND_URL } from 'const'; +import { Box } from '@chakra-ui/react'; + +interface ShareProfileDropdownProps { + userAddr: string; +} +const ShareProfileDropdown: React.FC = ({ + userAddr, +}) => { + const profileUrl = `${FRONTEND_URL}/#/profile?addr=${userAddr}`; + + const twitterPost = `https://twitter.com/intent/tweet?text=${encodeURIComponent( + `Check at my profile in ${profileUrl} on CAST! ` + )} `; + + return ( + + + + ); +}; +export default ShareProfileDropdown; diff --git a/frontend/packages/client/src/components/WalletAddress.tsx b/frontend/packages/client/src/components/WalletAddress.tsx new file mode 100644 index 000000000..a6022fb95 --- /dev/null +++ b/frontend/packages/client/src/components/WalletAddress.tsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from 'react'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { Svg } from '@cast/shared-components'; +import { Tooltip } from 'components'; +import { Flex, HStack, Text } from '@chakra-ui/react'; + +const WalletAddress: React.FC<{ addr: string }> = ({ addr }) => { + const [addressCopied, setAddressCopied] = useState(false); + + const markAddressCopied = () => setAddressCopied(true); + + useEffect(() => { + let timeout: NodeJS.Timeout; + if (addressCopied) { + timeout = setTimeout(() => { + setAddressCopied(false); + }, 500); + } + return () => clearTimeout(timeout); + }, [addressCopied]); + + return ( + + + + + + {addr} + + + + + + + + + ); +}; + +export default WalletAddress; diff --git a/frontend/packages/client/src/components/index.js b/frontend/packages/client/src/components/index.js index 88dcb050b..bfe5ea455 100644 --- a/frontend/packages/client/src/components/index.js +++ b/frontend/packages/client/src/components/index.js @@ -60,3 +60,8 @@ export { default as FilterPill } from './FilterPill'; export { default as BrowseCommunityButton } from './BrowseCommunityButton'; export * from './Proposal'; export { Card } from './Card'; +export { default as Tooltip } from './Tooltip'; +export { default as ShareDropdown } from './ShareDropdown'; +export { default as WalletAddress } from './WalletAddress'; +export { default as ShareProfileDropdown } from './UserProfile/ShareProfileDropdown'; +export { default as CustomTab } from './CustomTab'; diff --git a/frontend/packages/client/src/layout/PageContainer.tsx b/frontend/packages/client/src/layout/PageContainer.tsx new file mode 100644 index 000000000..9c8a49004 --- /dev/null +++ b/frontend/packages/client/src/layout/PageContainer.tsx @@ -0,0 +1,15 @@ +import { Container } from '@chakra-ui/react'; + +interface Props { + children?: React.ReactNode; +} + +const PageContainer: React.FC = ({ children }) => { + return ( + + {children} + + ); +}; + +export default PageContainer; diff --git a/frontend/packages/client/src/pages/UserProfile.tsx b/frontend/packages/client/src/pages/UserProfile.tsx new file mode 100644 index 000000000..5bc25aa43 --- /dev/null +++ b/frontend/packages/client/src/pages/UserProfile.tsx @@ -0,0 +1,132 @@ +import Blockies from 'react-blockies'; +import { Link as RouterLink } from 'react-router-dom'; +import { useWebContext } from 'contexts/Web3'; +import { + CommunityLinks, + CustomTab, + ShareProfileDropdown, + WalletAddress, +} from 'components'; +import { useMediaQuery, useQueryParams } from 'hooks'; +import { + Box, + Button, + Flex, + HStack, + Link, + Spacer, + TabList, + TabPanel, + TabPanels, + Tabs, + VStack, +} from '@chakra-ui/react'; +import PageContainer from 'layout/PageContainer'; + +const UserProfile: React.FC = () => { + const { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + user: { addr }, + } = useWebContext(); + + const isBiggerThanMobile = useMediaQuery(); + + const { userAddress }: { userAddress: string } = useQueryParams({ + userAddress: 'addr', + }); + + // if there's an address provided in the query param then use it to get user information + const currentUserAddr: string = + userAddress === addr || !userAddress ? addr : userAddress; + + // Load here info for currentUserAddr with hook + const { + instagramUrl, + twitterUrl, + websiteUrl, + discordUrl, + githubUrl, + }: { [key: string]: string } = {}; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Activity + Communities + Memberships + + + + +

Here Show all Activities

+
+ +

Show Communities!

+
+ +

Show Memberships

+
+
+
+
+
+
+ ); +}; + +export default UserProfile; diff --git a/frontend/packages/client/src/pages/index.js b/frontend/packages/client/src/pages/index.js index 1e7bc4bc8..5c506043d 100644 --- a/frontend/packages/client/src/pages/index.js +++ b/frontend/packages/client/src/pages/index.js @@ -15,6 +15,7 @@ const Community = lazy(() => import('./Community')); const CommunityEditor = lazy(() => import('./CommunityEditor')); const CommunityCreate = lazy(() => import('./CommunityCreate')); const BrowseCommunities = lazy(() => import('./BrowseCommunities')); +const UserProfile = lazy(() => import('./UserProfile')); export default function AppPages() { return ( @@ -48,7 +49,9 @@ export default function AppPages() { - + + + diff --git a/frontend/packages/shared-components/package.json b/frontend/packages/shared-components/package.json index c4f9eab9d..0d8420266 100644 --- a/frontend/packages/shared-components/package.json +++ b/frontend/packages/shared-components/package.json @@ -4,6 +4,7 @@ "private": true, "main": "src/index.js", "dependencies": { + "@fontsource/dm-sans": "^4.5.9", "@types/jest": "^29.1.2", "@types/node": "^18.8.5", "@types/react": "^18.0.21", diff --git a/frontend/packages/shared-components/src/Button/Button.stories.js b/frontend/packages/shared-components/src/Button/Button.stories.js new file mode 100644 index 000000000..212165b50 --- /dev/null +++ b/frontend/packages/shared-components/src/Button/Button.stories.js @@ -0,0 +1,30 @@ +import { Button } from '@chakra-ui/react'; + +export default { + title: 'Shared Components/Button', + component: Button, + argTypes: { + size: { + control: { + type: 'select', + }, + options: ['lg', 'md', 'sm'], + }, + border: { + control: { + type: 'select', + }, + options: ['', 'light'], + }, + bgColor: { + control: { + type: 'select', + }, + options: ['yellow.500', 'grey.300'], + }, + }, +}; + +const Template = (args) => ; + +export const Controls = Template.bind({}); diff --git a/frontend/packages/shared-components/src/Svg/Copy.js b/frontend/packages/shared-components/src/Svg/Copy.js index 72732709f..dd7da045a 100644 --- a/frontend/packages/shared-components/src/Svg/Copy.js +++ b/frontend/packages/shared-components/src/Svg/Copy.js @@ -1,33 +1,70 @@ -const Copy = ({ width = '24', height = '24' }) => ( - - - - - - - - - - - -); +const Copy = ({ width = '24', height = '24', bold = true }) => { + if (!bold) { + return ( + + + + + + + + + + + ); + } + return ( + + + + + + + + + + + + ); +}; export default Copy; diff --git a/frontend/packages/shared-components/src/theme/components/Button.ts b/frontend/packages/shared-components/src/theme/components/Button.ts new file mode 100644 index 000000000..9b94564aa --- /dev/null +++ b/frontend/packages/shared-components/src/theme/components/Button.ts @@ -0,0 +1,26 @@ +const Button = { + baseStyle: { + // adds rounded border + borderRadius: '3xl', + _hover: { + textDecoration: 'none', + }, + _focus: { + 'box-shadow': 'none', + }, + }, + sizes: { + lg: { + height: 11, // 2.75rem => 44px + }, + md: { + height: 10, // 2.5rem => 40px + }, + sm: { + height: 8, //2.125 => 34px + }, + }, + defaultProps: {}, +}; + +export default Button; diff --git a/frontend/packages/shared-components/src/theme/components/Link.js b/frontend/packages/shared-components/src/theme/components/Link.js index 419dc21c2..db83bd1de 100644 --- a/frontend/packages/shared-components/src/theme/components/Link.js +++ b/frontend/packages/shared-components/src/theme/components/Link.js @@ -3,6 +3,11 @@ const Link = { underlined: { textDecoration: 'underline', }, + noHover: { + _hover: { + textDecoration: 'none', + }, + }, }, }; diff --git a/frontend/packages/shared-components/src/theme/components/Tabs.ts b/frontend/packages/shared-components/src/theme/components/Tabs.ts new file mode 100644 index 000000000..0c39c026e --- /dev/null +++ b/frontend/packages/shared-components/src/theme/components/Tabs.ts @@ -0,0 +1,37 @@ +const Tabs = { + variants: { + profile: { + tab: { + _first: { + marginStart: 0, + }, + _selected: { + borderBottom: '2px solid', + borderColor: 'black', + fontWeight: 'bold', + color: 'black', + }, + _focus: { + 'box-shadow': 'none', + }, + marginLeft: 3, + marginRight: 3, + paddingLeft: 0, + paddingRight: 0, + }, + tablist: { + color: 'grey.500', + borderBottom: '1px solid', + borderColor: 'inherit', + fontSize: 'lg', + lineHeight: '23px', + }, + tabpanel: { + marginTop: 10, + padding: 0, + }, + }, + }, +}; + +export default Tabs; diff --git a/frontend/packages/shared-components/src/theme/definitions.js b/frontend/packages/shared-components/src/theme/definitions.js index 943e529ee..5c7763de8 100644 --- a/frontend/packages/shared-components/src/theme/definitions.js +++ b/frontend/packages/shared-components/src/theme/definitions.js @@ -2,21 +2,32 @@ // https://v1.chakra-ui.com/docs/styled-system/theming/theme const definitions = { fonts: { - body: 'DM Sans', - heading: 'DM Sans', - mono: 'DM Sans', + body: `'DM Sans', sans-serif`, + heading: `'DM Sans', sans-serif`, + mono: `'DM Sans', sans-serif`, }, lineHeights: { shorter: 1.2858, }, + sizes: { + 8: '2.125rem', // 34 px for button size small + 11: '2.75rem', // 44 px for button size large + }, + // https://chakra-ui.com/docs/styled-system/responsive-styles + breakpoints: { + sm: '22.5em', // from 0 to 360px + md: '48em', // 768px => from 360px up to 768px: mobile + lg: '62em', // 992px => from 768px to 992px: tablet + xl: '80em', // 1280px => from 992px to 1280px: desktop + }, fontSizes: { xxs: '0.625rem', // 10px - xs: '0.75rem', - sm: '0.875rem', - md: '1rem', - lg: '1.125rem', - xl: '1.25rem', - '2xl': '1.5rem', + xs: '0.75rem', // 12px + sm: '0.875rem', // 14px + md: '1rem', // 16px + lg: '1.125rem', // 18px + xl: '1.25rem', // 20px + '2xl': '1.5rem', // 24px '3xl': '1.875rem', '4xl': '2.25rem', '5xl': '3rem', @@ -25,24 +36,6 @@ const definitions = { '8xl': '6rem', '9xl': '8rem', }, - // colors used in CAST designs - colors: { - red: { - 600: '#C2130A', - 500: '#F54339', - 300: '#F8746D', - 1000: '#FFF6F5', - }, - grey: { - 600: '#4D4D4D', - 500: '#636363', - 300: '#DCDCDC', - 200: '#F9F9F9', - }, - yellow: { - 500: '#FBD84D', - }, - }, }; export default definitions; diff --git a/frontend/packages/shared-components/src/theme/foundations/borders.js b/frontend/packages/shared-components/src/theme/foundations/borders.js index f2a802183..3e74aac79 100644 --- a/frontend/packages/shared-components/src/theme/foundations/borders.js +++ b/frontend/packages/shared-components/src/theme/foundations/borders.js @@ -1,7 +1,7 @@ -import definitions from '../definitions'; +import colors from './colors'; const borders = { - light: `1px solid ${definitions.colors.grey[300]}`, + light: `1px solid ${colors.grey[300]}`, }; export default borders; diff --git a/frontend/packages/shared-components/src/theme/foundations/colors.js b/frontend/packages/shared-components/src/theme/foundations/colors.js new file mode 100644 index 000000000..7a541fb1b --- /dev/null +++ b/frontend/packages/shared-components/src/theme/foundations/colors.js @@ -0,0 +1,26 @@ +// Theme color definitions are here: +// https://github.com/chakra-ui/chakra-ui/blob/main/packages/components/theme/src/foundations/colors.ts + +const colors = { + // colors used in CAST designs + blackAlpha: { + 600: 'rgba(0, 0, 0, 0.48)', + }, + red: { + 600: '#C2130A', + 500: '#F54339', + 300: '#F8746D', + 1000: '#FFF6F5', + }, + grey: { + 600: '#4D4D4D', + 500: '#636363', + 300: '#DCDCDC', + 200: '#F9F9F9', + }, + yellow: { + 500: '#FBD84D', + }, +}; + +export default colors; diff --git a/frontend/packages/shared-components/src/theme/index.ts b/frontend/packages/shared-components/src/theme/index.ts index a31a80052..dfd18eab6 100644 --- a/frontend/packages/shared-components/src/theme/index.ts +++ b/frontend/packages/shared-components/src/theme/index.ts @@ -1,8 +1,10 @@ // // *********************************** // Component style overrides for Chakra components +import Button from './components/Button'; import Input from './components/Input'; import Link from './components/Link'; +import Tabs from './components/Tabs'; import Text from './components/Text'; // *********************************** import { extendTheme } from '@chakra-ui/react'; @@ -19,6 +21,7 @@ import definitions from './definitions'; // // Foundational style overrides import borders from './foundations/borders'; +import colors from './foundations/colors'; // *********************************** // // *********************************** @@ -31,12 +34,15 @@ const overrides = { ...definitions, styles, borders, + colors, // Other foundational style overrides go here components: { Input, Card: CardStyle, Link, Text, + Button, + Tabs, // Other components go here }, }; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 80731a43f..8846665a6 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2444,6 +2444,11 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@fontsource/dm-sans@^4.5.9": + version "4.5.9" + resolved "https://registry.yarnpkg.com/@fontsource/dm-sans/-/dm-sans-4.5.9.tgz#a48f9c815728ebb47e1c58e5176a9d4aa6cb25ea" + integrity sha512-qaFvSBJhoeley1A9myRUUuHbm9sz7ADyC2WodezBzdFSPs2VD/p5WPPwtpVE8b1SglQrOPAnxA7s3fH5zOtiMQ== + "@gar/promisify@^1.0.1": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -4996,6 +5001,13 @@ dependencies: "@types/react" "*" +"@types/react-copy-to-clipboard@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz#558f2c38a97f53693e537815f6024f1e41e36a7e" + integrity sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ== + dependencies: + "@types/react" "*" + "@types/react-dom@<18.0.0": version "17.0.17" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.17.tgz#2e3743277a793a96a99f1bf87614598289da68a1"