diff --git a/.eslintignore b/.eslintignore index 5e1bfd09d..14ebc1271 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,4 @@ storybook-build/ build/ dist/ -jest.config.js +jest.config.js \ No newline at end of file diff --git a/amity-uikit.config.json b/amity-uikit.config.json new file mode 100644 index 000000000..4a87df28b --- /dev/null +++ b/amity-uikit.config.json @@ -0,0 +1,133 @@ +{ + "global_theme": { + "light_theme": { + "primary_color": "#1054DE", + "secondary_color": "#292B32" + } + }, + "excludes": [], + "customizations": { + "select_target_page/*/*": { + "page_theme": { + "light_theme": { + "primary_color": "#1054DE", + "secondary_color": "#292B32" + } + }, + "title": "Share to" + }, + "select_target_page/*/back_button": { + "back_icon": "back" + }, + "camera_page/*/*": { + "resolution": "720p", + "page_theme": { + "light_theme": { + "primary_color": "#1054DE", + "secondary_color": "#292B32" + } + } + }, + "camera_page/*/close_button": { + "close_icon": "close", + "background_color": "#80000000" + }, + "create_story_page/*/*": {}, + "create_story_page/*/back_button": { + "back_icon": "back", + "background_color": "#80000000" + }, + "create_story_page/*/aspect_ratio_button": { + "aspect_ratio_icon": "aspect_ratio", + "background_color": "#80000000" + }, + "create_story_page/*/story_hyperlink_button": { + "hyperlink_button_icon": "hyperlink_button", + "background_color": "#80000000" + }, + "create_story_page/*/hyper_link": { + "hyper_link_icon": "hyper_link", + "background_color": "#80000000" + }, + "create_story_page/*/share_story_button": { + "share_icon": "share_story_button", + "background_color": "#FFFFFF", + "hide_avatar": false + }, + "story_page/*/*": {}, + "story_page/*/progress_bar": {}, + "story_page/*/overflow_menu": { + "overflow_menu_icon": "threeDot" + }, + "story_page/*/close_button": { + "close_icon": "close" + }, + "story_page/*/story_impression_button": { + "impression_icon": "impressionIcon" + }, + "story_page/*/story_comment_button": { + "comment_icon": "comment" + }, + "story_page/*/story_reaction_button": { + "reaction_icon": "like", + "background_color": "#1243EE" + }, + "story_page/*/create_new_story_button": { + "create_new_story_icon": "plus", + "background_color": "#1243EE" + }, + "story_page/*/speaker_button": { + "mute_icon": "mute", + "unmute_icon": "unmute" + }, + "*/edit_comment_component/*": { + "component_theme": { + "light_theme": { + "primary_color": "#1054DE", + "secondary_color": "#292B32" + } + } + }, + "*/edit_comment_component/cancel_button": { + "cancel_icon": "", + "cancel_button_text": "Cancel", + "background_color": "#FFFFFF" + }, + "*/edit_comment_component/save_button": { + "save_icon": "", + "save_button_text": "Save", + "background_color": "#1054DE" + }, + "*/hyper_link_config_component/*": { + "component_theme": { + "light_theme": { + "primary_color": "#1054DE", + "secondary_color": "#292B32" + } + } + }, + "*/hyper_link_config_component/done_button": { + "done_icon": "", + "done_button_text": "Done", + "background_color": "#1243EE" + }, + "*/hyper_link_config_component/cancel_button": { + "cancel_icon": "", + "cancel_button_text": "Cancel" + }, + "*/comment_tray_component/*": {}, + "*/story_tab_component/*": {}, + "*/story_tab_component/story_ring": { + "progress_color": ["#339AF9", "#78FA58"], + "fail_color": ["#FA4D30"], + "background_color": ["#EBECEF"] + }, + "*/story_tab_component/create_new_story_button": { + "create_new_story_icon": "plus", + "background_color": "#1243EE" + }, + "*/*/close_button": { + "close_icon": "close" + } + } +} diff --git a/code-of-conduct.md b/code-of-conduct.md index dd3af28f3..a89b212a1 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,3 +1,3 @@ # Code of conduct -Please refer to the [Amity code of conduct](https://docs.amity.co/support/code-of-conduct) +Please refer to the [Amity code of conduct](https://docs.amity.co/support/code-of-conduct) \ No newline at end of file diff --git a/readme.md b/readme.md deleted file mode 100644 index beb8a717e..000000000 --- a/readme.md +++ /dev/null @@ -1,26 +0,0 @@ -# Amity Ui-Kit for Web (open-source) - -## Getting started - -### Installation - -Here are the steps to install ui-kit together with another project. - -1. git clone git@github.com:AmityCo/Amity-Social-Cloud-UIKit-Web-OpenSource.git -2. cd ./Amity-Social-Cloud-UIKit-Web-OpenSource -3. npm ci -4. npm link -5. npm link ./``/node_modules/react ./``/node_modules/react-dom -6. npm run build -7. cd ./`` -8. npm link @amityco/ui-kit-open-source --save - -** We need to link react module to react module in destination project so that react is the same instance otherwise we will encounter [issues with react hook](https://medium.com/bbc-product-technology/solving-the-problem-with-npm-link-and-react-hooks-266c832dd019). - -### Documentation - -Please refer to our online documentation at https://docs.amity.co or contact a Ui-Kit representative at **developers@amity.co** for support. - -## Contributing - -See [our contributing guide](https://github.com/EkoCommunications/AmityUiKitWeb/blob/develop/CONTRIBUTING.md) diff --git a/security.md b/security.md index f2634807f..da632f8b2 100644 --- a/security.md +++ b/security.md @@ -1,3 +1,3 @@ # Security policy -Please refer to the [Amity security policies](https://docs.amity.co/support/security) +Please refer to the [Amity security policies](https://docs.amity.co/support/security) \ No newline at end of file diff --git a/src/chat/components/ChatDetails/ChatDetailsMembers/index.tsx b/src/chat/components/ChatDetails/ChatDetailsMembers/index.tsx index 847f2bd80..3c6c387ad 100644 --- a/src/chat/components/ChatDetails/ChatDetailsMembers/index.tsx +++ b/src/chat/components/ChatDetails/ChatDetailsMembers/index.tsx @@ -69,7 +69,7 @@ const ChatDetailsMembers = ({ - {members.length > 0 ? ( + {members?.length > 0 ? ( 0 + toAddMemberIds?.length > 0 ? ChannelRepository.Membership.addMembers(channelId, toAddMemberIds) : null, - toRemoveMemberIds.length > 0 + toRemoveMemberIds?.length > 0 ? ChannelRepository.Membership.removeMembers(channelId, toRemoveMemberIds) : null, ].filter(isNonNullable), diff --git a/src/chat/components/MessageList/index.tsx b/src/chat/components/MessageList/index.tsx index b171ad329..5783d67b2 100644 --- a/src/chat/components/MessageList/index.tsx +++ b/src/chat/components/MessageList/index.tsx @@ -66,7 +66,7 @@ const MessageList = ({ channelId }: MessageListProps) => { next={loadMore} loader={isLoading ? Loading... : null} inverse={true} - dataLength={messages.length} + dataLength={messages?.length || 0} style={{ display: 'flex', flexDirection: 'column-reverse' }} height={containerRef.current.clientHeight} > diff --git a/src/chat/components/RecentChat/index.tsx b/src/chat/components/RecentChat/index.tsx index b90f301e1..3621e19a0 100644 --- a/src/chat/components/RecentChat/index.tsx +++ b/src/chat/components/RecentChat/index.tsx @@ -56,7 +56,7 @@ const RecentChat = ({ hasMore={hasMore} next={loadMore} loader={hasMore && Loading...} - dataLength={channels.length} + dataLength={channels?.length || 0} height={containerRef.current.clientHeight} > {Array.isArray(channels) && diff --git a/src/chat/components/UserSelectorModal/ContactList/index.tsx b/src/chat/components/UserSelectorModal/ContactList/index.tsx index b1268c6b7..0a0a73541 100644 --- a/src/chat/components/UserSelectorModal/ContactList/index.tsx +++ b/src/chat/components/UserSelectorModal/ContactList/index.tsx @@ -61,7 +61,7 @@ const UserList = ({ hasMore={hasMore} next={loadMore} loader={Loading...} - dataLength={filterUsers.length} + dataLength={filterUsers?.length || 0} > {filterUsers.map((userData) => ( diff --git a/src/chat/components/UserSelectorModal/index.tsx b/src/chat/components/UserSelectorModal/index.tsx index 62d2e1ea9..00f6502c5 100644 --- a/src/chat/components/UserSelectorModal/index.tsx +++ b/src/chat/components/UserSelectorModal/index.tsx @@ -104,7 +104,7 @@ const UserSelectorModal = ({ - {query.length > 0 && ( + {query?.length > 0 && ( )} - {query.length === 0 && ( + {(!query || query?.length === 0) && ( { - const textLength = text.length; + const textLength = text?.length || 0; const allChunks: Chunk[] = []; const append = (start: number, end: number, highlight: boolean) => { allChunks.push({ start, end, highlight }); }; - if (chunks.length === 0) { + if (!chunks || chunks?.length === 0) { append(0, textLength, false); } else { let lastIndex = 0; diff --git a/src/core/components/Confirm/index.tsx b/src/core/components/Confirm/index.tsx index a5fb7ce21..097321633 100644 --- a/src/core/components/Confirm/index.tsx +++ b/src/core/components/Confirm/index.tsx @@ -1,7 +1,13 @@ import React, { useState } from 'react'; import { PrimaryButton } from '~/core/components/Button'; -import { ConfirmModal, Footer, DefaultOkButton, DefaultCancelButton } from './styles'; +import { + ConfirmModal, + Footer, + DefaultOkButton, + DefaultCancelButton, + ConfirmModalContent, +} from './styles'; const Confirm = ({ 'data-qa-anchor': dataQaAnchor = '', @@ -35,7 +41,7 @@ const Confirm = ({ } onCancel={onCancel} > - {content} + {content} ); diff --git a/src/core/components/Confirm/styles.tsx b/src/core/components/Confirm/styles.tsx index c1c605ceb..2c92172f6 100644 --- a/src/core/components/Confirm/styles.tsx +++ b/src/core/components/Confirm/styles.tsx @@ -6,6 +6,10 @@ export const ConfirmModal = styled(Modal)` max-width: 360px; `; +export const ConfirmModalContent = styled.div` + padding: 1rem 1rem 0.75rem 1rem; +`; + export const Footer = styled.div` display: flex; justify-content: flex-end; diff --git a/src/core/components/InputText/InsideInputText.tsx b/src/core/components/InputText/InsideInputText.tsx index 3d2b6a99a..78e22238a 100644 --- a/src/core/components/InputText/InsideInputText.tsx +++ b/src/core/components/InputText/InsideInputText.tsx @@ -12,7 +12,7 @@ const Container = styled.div` display: flex; flex-wrap: wrap; min-width: 1em; - background: ${({ theme }) => theme.palette.system.background}; + background: ${({ theme }) => theme.palette.base.shade4}; border: 1px solid #e3e4e8; border-radius: 4px; transition: background 0.2s, border-color 0.2s; diff --git a/src/core/components/UserChip/UIUserChip.tsx b/src/core/components/UserChip/UIUserChip.tsx index f1ade0055..82166afcf 100644 --- a/src/core/components/UserChip/UIUserChip.tsx +++ b/src/core/components/UserChip/UIUserChip.tsx @@ -1,10 +1,10 @@ import React, { useCallback } from 'react'; import Avatar from '~/core/components/Avatar'; -import { Close, Remove } from '~/icons'; + import { backgroundImage as UserImage } from '~/icons/User'; -import { Chip, Name, RoundButton } from './styles'; +import { Chip, Name, RoundButton, Close } from './styles'; import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; interface UIUserChipProps { diff --git a/src/core/providers/UiKitProvider/index.tsx b/src/core/providers/UiKitProvider/index.tsx index c67f6507a..ba5ad04a9 100644 --- a/src/core/providers/UiKitProvider/index.tsx +++ b/src/core/providers/UiKitProvider/index.tsx @@ -18,6 +18,10 @@ import CustomComponentsProvider, { CustomComponentType } from '../CustomComponen import PostRendererProvider, { PostRendererConfigType, } from '~/social/providers/PostRendererProvider'; +import { Config, CustomizationProvider } from '~/social/v4/providers/CustomizationProvider'; + +import amityConfig from '../../../../amity-uikit.config.json'; +import { PageBehaviorProvider } from '~/social/v4/providers/PageBehaviorProvider'; interface UiKitProviderProps { apiKey: string; @@ -26,6 +30,7 @@ interface UiKitProviderProps { http?: string; mqtt?: string; }; + authToken?: string; userId: string; displayName: string; customComponents?: CustomComponentType; @@ -42,17 +47,21 @@ interface UiKitProviderProps { onEditUser?: (userId: string) => void; onMessageUser?: (userId: string) => void; }; + pageBehavior?: { + closeAction?: () => void; + hyperLinkAction?: () => void; + }; socialCommunityCreationButtonVisible?: boolean; onConnectionStatusChange?: (state: Amity.SessionStates) => void; onConnected?: () => void; onDisconnected?: () => void; - getAuthToken?: () => Promise; } const UiKitProvider = ({ apiKey, apiRegion, apiEndpoint, + authToken, userId, displayName, customComponents = {}, @@ -61,9 +70,9 @@ const UiKitProvider = ({ children /* TODO localization */, socialCommunityCreationButtonVisible, actionHandlers, + pageBehavior, onConnectionStatusChange, onDisconnected, - getAuthToken, }: UiKitProviderProps) => { const [isConnected, setIsConnected] = useState(false); const [client, setClient] = useState(null); @@ -95,25 +104,20 @@ const UiKitProvider = ({ const currentIsConnected = ASCClient.isConnected(); if (!currentIsConnected) { - let params: Amity.ConnectClientParams = { userId, displayName }; - - if (getAuthToken) { - const authToken = await getAuthToken(); - params = { ...params, authToken }; - } - - await ASCClient.login(params, { - async sessionWillRenewAccessToken(renewal: Amity.AccessTokenRenewal) { - // secure mode - if (getAuthToken) { - const authToken = await getAuthToken(); - renewal.renewWithAuthToken(authToken); - return; - } - - renewal.renew(); + await ASCClient.login( + { userId, displayName, authToken }, + { + sessionWillRenewAccessToken(renewal) { + // secure mode + if (authToken) { + renewal.renewWithAuthToken(authToken); + return; + } + + renewal.renew(); + }, }, - }); + ); } setIsConnected(true); @@ -145,28 +149,34 @@ const UiKitProvider = ({ return ( - - - - - - - - {children} - - - - - - - - - + + + + + + + + + + + {children} + + + + + + + + + + + + ); }; diff --git a/src/core/providers/UiKitProvider/theme/default-theme.ts b/src/core/providers/UiKitProvider/theme/default-theme.ts index b821f34ff..8dfbe9800 100644 --- a/src/core/providers/UiKitProvider/theme/default-theme.ts +++ b/src/core/providers/UiKitProvider/theme/default-theme.ts @@ -26,6 +26,10 @@ const defaultTheme = { fontWeight: 600, fontSize: '16px', }, + subTitle: { + fontWight: 400, + fontSize: '13px', + }, body: { fontWeight: 'normal', fontSize: '14px', diff --git a/src/core/providers/UiKitProvider/theme/index.ts b/src/core/providers/UiKitProvider/theme/index.ts index e0a9f0d20..75febb338 100644 --- a/src/core/providers/UiKitProvider/theme/index.ts +++ b/src/core/providers/UiKitProvider/theme/index.ts @@ -2,6 +2,7 @@ import merge from 'lodash/merge'; import defaultTheme from './default-theme'; import { buildPaletteTheme } from './palette'; import { buildTypographyTheme } from './typography'; +import { defaultThemeV4 } from '~/social/v4/constants/default-theme-v4'; /** * Builds a global theme object combining default theme and an optional override theme. @@ -17,6 +18,7 @@ const buildGlobalTheme = (overrideTheme = {}) => { ...mergedTheme, palette: paletteExtended, typography: typographyExtended, + v4: defaultThemeV4, }; }; diff --git a/src/core/v4/components/BottomSheet/BottomSheet.tsx b/src/core/v4/components/BottomSheet/BottomSheet.tsx new file mode 100644 index 000000000..06de6d200 --- /dev/null +++ b/src/core/v4/components/BottomSheet/BottomSheet.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { MobileSheet } from './styles'; + +interface BottomSheetProps { + isOpen: boolean; + onClose: () => void; + children: React.ReactNode; + rootId?: string; + mountPoint?: HTMLElement; + detent?: 'content-height' | 'full-height'; +} + +export const BottomSheet = ({ + isOpen = false, + onClose = () => {}, + detent = 'content-height', + rootId, + children, + mountPoint, + ...props +}: BottomSheetProps) => { + return ( + + {children} + + ); +}; diff --git a/src/core/v4/components/BottomSheet/index.ts b/src/core/v4/components/BottomSheet/index.ts new file mode 100644 index 000000000..96840b011 --- /dev/null +++ b/src/core/v4/components/BottomSheet/index.ts @@ -0,0 +1 @@ +export { BottomSheet } from './BottomSheet'; diff --git a/src/core/v4/components/BottomSheet/styles.tsx b/src/core/v4/components/BottomSheet/styles.tsx new file mode 100644 index 000000000..7fd07b966 --- /dev/null +++ b/src/core/v4/components/BottomSheet/styles.tsx @@ -0,0 +1,39 @@ +import styled from 'styled-components'; +import Sheet from 'react-modal-sheet'; +import { SecondaryButton } from '~/core/components/Button'; + +export const MobileSheet = styled(Sheet)` + margin: 0 auto; + width: 100%; +`; + +export const MobileSheetNestedBackDrop = styled(MobileSheet.Backdrop)` + background-color: rgba(0, 0, 0, 0.5); +`; + +export const MobileSheetContainer = styled(MobileSheet.Container)` + z-index: 101; +`; + +export const MobileSheetHeader = styled(MobileSheet.Header)` + ${({ theme }) => theme.typography.title}; + color: ${({ theme }) => theme.palette.base.default}; + text-align: center; + border-bottom: 1px solid #e3e4e8; + padding-bottom: 0.5rem; + z-index: 100; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +`; + +export const MobileSheetContent = styled(MobileSheet.Content)` + z-index: 100; +`; + +export const MobileSheetButton = styled(SecondaryButton)` + display: flex; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; + width: 100%; +`; diff --git a/src/core/v4/components/Icon/Icon.tsx b/src/core/v4/components/Icon/Icon.tsx new file mode 100644 index 000000000..c5621ac13 --- /dev/null +++ b/src/core/v4/components/Icon/Icon.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import * as Icons from '~/icons'; + +type IconName = keyof typeof Icons; + +export interface IconProps { + name: IconName | null; + size?: number; + style?: React.CSSProperties; + onClick?: (e: React.MouseEvent) => void; +} + +export const Icon: React.FC = ({ name, size = 24, style, onClick, ...props }) => { + const iconMap = { + ...Icons, + }; + + const IconComponent = iconMap[name as keyof typeof iconMap]; + + if (!IconComponent) { + console.error(`Icon not found: ${name}`); + return null; + } + + return ( + + ); +}; diff --git a/src/core/v4/components/Icon/index.ts b/src/core/v4/components/Icon/index.ts new file mode 100644 index 000000000..3f20921be --- /dev/null +++ b/src/core/v4/components/Icon/index.ts @@ -0,0 +1 @@ +export { Icon } from './Icon'; diff --git a/src/core/v4/components/index.ts b/src/core/v4/components/index.ts new file mode 100644 index 000000000..ec2479445 --- /dev/null +++ b/src/core/v4/components/index.ts @@ -0,0 +1,2 @@ +export { BottomSheet } from './BottomSheet'; +export { Icon } from './Icon'; diff --git a/src/i18n/en.json b/src/i18n/en.json index 4ff40a169..5b8a61786 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -51,7 +51,7 @@ "collapsible.viewAll": "View all", "collapsible.viewAllFiles": "View all files", "collapsible.viewMoreComments": "View more comments", - "collapsible.viewMoreReplies": "View more replies", + "collapsible.viewMoreReplies": "View {count, plural, one {# reply} other {# replies}}", "community.createSuccess": "Your community was successfully created", "community.updateSuccess": "Your community was successfully updated", @@ -234,7 +234,7 @@ "CloseCommunityAction.description": "Closing this community will remove the community page and all its content and comments. This cannot be undone.", "report.doReport": "Report", "report.undoReport": "Undo Report", - "comment.deleteBody": "This comment will be permanently deleted. Continue?", + "comment.deleteBody": "This comment will be permanently removed.", "reply.delete": "Delete reply", "reply.deleteBody": "This reply will be permanently deleted. Continue?", "reply.edit": "Edit reply", @@ -355,6 +355,13 @@ "storyViewer.notification.error": "Failed to share story", "storyViewer.footer.failed": "Failed to upload", "storyViewer.footer.uploading": "Uploading...", + "storyViewer.commentSheet.title": "Comments", + "storyViewer.commentSheet.empty": "No comments yet", + "storyViewer.commentSheet.disabled": "Comments are disabled for this story", + "storyViewer.commentSheet.replyingTo": "Replying to", + "storyViewer.toast.like.disabled": "Join community to interact with all stories", + + "storyViewer.commentComposeBar.submit": "Post", "storyDraft.button.shareStory": "Share story", diff --git a/src/icons/ArrowRight2.tsx b/src/icons/ArrowRight2.tsx new file mode 100644 index 000000000..95bb2debd --- /dev/null +++ b/src/icons/ArrowRight2.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +function Icon() { + return ( + + + + ); +} + +export default Icon; diff --git a/src/icons/Comment2.tsx b/src/icons/Comment2.tsx new file mode 100644 index 000000000..6b799b46a --- /dev/null +++ b/src/icons/Comment2.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/icons/Flag.tsx b/src/icons/Flag.tsx new file mode 100644 index 000000000..da413d998 --- /dev/null +++ b/src/icons/Flag.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/icons/Liked.tsx b/src/icons/Liked.tsx new file mode 100644 index 000000000..b6b2b3711 --- /dev/null +++ b/src/icons/Liked.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + + + + + + + + ); +} + +export default Icon; diff --git a/src/icons/Lock2.tsx b/src/icons/Lock2.tsx new file mode 100644 index 000000000..78710489e --- /dev/null +++ b/src/icons/Lock2.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/icons/ModeratorBadge.tsx b/src/icons/ModeratorBadge.tsx new file mode 100644 index 000000000..c1776e55f --- /dev/null +++ b/src/icons/ModeratorBadge.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/icons/Pencil2.tsx b/src/icons/Pencil2.tsx new file mode 100644 index 000000000..25a7e74d2 --- /dev/null +++ b/src/icons/Pencil2.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/icons/Trash2.tsx b/src/icons/Trash2.tsx new file mode 100644 index 000000000..c17c77800 --- /dev/null +++ b/src/icons/Trash2.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +function Icon(props: React.SVGProps) { + return ( + + + + ); +} + +export default Icon; diff --git a/src/icons/index.ts b/src/icons/index.ts index 0b823029d..c9c6629fc 100644 --- a/src/icons/index.ts +++ b/src/icons/index.ts @@ -42,7 +42,14 @@ export { default as ArrowLeftCircle } from './ArrowLeftCircle'; export { default as ArrowLeftCircle2 } from './ArrowLeftCircle2'; export { default as ArrowRightCircle } from './ArrowRightCircle'; export { default as EyeIcon } from './Eye'; - +export { default as LikedIcon } from './Liked'; +export { default as Lock2Icon } from './Lock2'; +export { default as Comment2Icon } from './Comment2'; +export { default as ArrowRight2Icon } from './ArrowRight2'; +export { default as Trash2Icon } from './Trash2'; +export { default as ModeratorBadgeIcon } from './ModeratorBadge'; +export { default as FlagIcon } from './Flag'; +export { default as Pencil2Icon } from './Pencil2'; // files export { default as AudioFile } from './files/Audio'; diff --git a/src/index.ts b/src/index.ts index 01b66c8cc..25fbb1455 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,16 @@ export { default as AmityPostEngagementBar } from '~/social/components/Engagemen export { default as AmityExpandableText } from '~/social/components/Comment/CommentText'; export { useSDK as useAmitySDK } from '~/core/hooks/useSDK'; +// v4 +export { + DraftsPage as AmityCreateStoryPage, + StoryPage as AmityViewStoryPage, +} from '~/social/v4/pages'; +export { + CommentTray as AmityCommentTrayComponent, + StoryTab as AmityStoryTabComponent, +} from '~/social/v4/components'; + // import AmityComment from './components/Comment'; // import AmityCommentComposeBar from './components/CommentComposeBar'; // import AmityCommentLikeButton from './components/CommentLikeButton'; diff --git a/src/social/components/Comment/index.tsx b/src/social/components/Comment/index.tsx index f7e7b0e32..8a6ecb9e1 100644 --- a/src/social/components/Comment/index.tsx +++ b/src/social/components/Comment/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState, useEffect } from 'react'; +import React, { memo, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { confirm } from '~/core/components/Confirm'; @@ -37,6 +37,7 @@ import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import useCommentFlaggedByMe from '~/social/hooks/useCommentFlaggedByMe'; import useCommentPermission from '~/social/hooks/useCommentPermission'; import useCommentSubscription from '~/social/hooks/useCommentSubscription'; +import useStory from '~/social/hooks/useStory'; import { ERROR_RESPONSE } from '~/social/constants'; const REPLIES_PER_PAGE = 5; @@ -89,6 +90,7 @@ interface CommentProps { const Comment = ({ commentId, readonly }: CommentProps) => { const comment = useComment(commentId); const post = usePost(comment?.referenceId); + const commentAuthor = useUser(comment?.userId); const commentAuthorAvatar = useFile(commentAuthor?.avatarFileId); const { userRoles } = useSDK(); @@ -125,7 +127,7 @@ const Comment = ({ commentId, readonly }: CommentProps) => { // } // }, [(comment?.data as Amity.ContentDataText)?.text, text]); - if (post == null || comment == null) return ; + if (post == null && comment == null) return ; const handleReportComment = async () => { return toggleFlagComment(); @@ -233,9 +235,7 @@ const Comment = ({ commentId, readonly }: CommentProps) => { }); }; - if (comment == null) { - return null; - } + if (comment == null) return null; if (comment?.isDeleted) { return isReplyComment ? ( @@ -285,14 +285,16 @@ const Comment = ({ commentId, readonly }: CommentProps) => { ) : ( {renderedComment} - + {comment.children.length > 0 && ( + + )} {isReplying && ( void; - postId: string; + postId?: string; + storyId?: string; } const CommentComposeBar = ({ @@ -39,6 +41,7 @@ const CommentComposeBar = ({ postId, }: CommentComposeBarProps) => { const post = usePost(postId); + const { currentUserId } = useSDK(); const user = useUser(currentUserId); const avatarFileUrl = useImage({ fileId: user?.avatarFileId, imageSize: 'small' }); @@ -50,12 +53,11 @@ const CommentComposeBar = ({ const { formatMessage } = useIntl(); const commentInputRef = useRef(null); + useEffect(() => { commentInputRef.current?.focus(); }, []); - if (post == null) return ; - const addComment = () => { if (text === '') return; diff --git a/src/social/components/CommentLikeButton/UICommentLikeButton.tsx b/src/social/components/CommentLikeButton/UICommentLikeButton.tsx index b0760a603..daf280038 100644 --- a/src/social/components/CommentLikeButton/UICommentLikeButton.tsx +++ b/src/social/components/CommentLikeButton/UICommentLikeButton.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import StyledCommentLikeButton from './styles'; import { ReactionRepository } from '@amityco/ts-sdk'; diff --git a/src/social/components/CommentLikeButton/index.tsx b/src/social/components/CommentLikeButton/index.tsx index b9fa0c255..9a1e67988 100644 --- a/src/social/components/CommentLikeButton/index.tsx +++ b/src/social/components/CommentLikeButton/index.tsx @@ -4,7 +4,6 @@ import useComment from '~/social/hooks/useComment'; import UICommentLikeButton from './UICommentLikeButton'; import useUserReactionSubscription from '~/social/hooks/useUserReactionSubscription'; import useCommunityReactionSubscription from '~/social/hooks/useCommunityReactionSubscription'; -import useCommentSubscription from '~/social/hooks/useCommentSubscription'; interface CommentLikeButtonProps { commentId?: string; diff --git a/src/social/components/CommentLikeButton/styles.tsx b/src/social/components/CommentLikeButton/styles.tsx index db51cec4d..e8fcc405c 100644 --- a/src/social/components/CommentLikeButton/styles.tsx +++ b/src/social/components/CommentLikeButton/styles.tsx @@ -16,10 +16,9 @@ export const StyledLikeButton = styled(SecondaryButton)` } `; -export const BaseLikeIcon = styled(ThumbsUp).attrs<{ icon?: ReactNode }>({ - width: 16, - height: 16, -})``; +export const BaseLikeIcon = styled(ThumbsUp)<{ icon?: ReactNode }>` + font-size: 16px; +`; export const IsLikedLikeIcon = styled(BaseLikeIcon)<{ icon?: ReactNode }>` ${isLikedStyle} diff --git a/src/social/components/CommentList/index.tsx b/src/social/components/CommentList/index.tsx index 54fdd7c4b..216400f97 100644 --- a/src/social/components/CommentList/index.tsx +++ b/src/social/components/CommentList/index.tsx @@ -3,7 +3,7 @@ import { useIntl } from 'react-intl'; import Comment from '~/social/components/Comment'; -import { TabIcon, TabIconContainer } from './styles'; +import { NoCommentsContainer, TabIcon, TabIconContainer } from './styles'; import LoadMoreWrapper from '../LoadMoreWrapper'; import usePostSubscription from '~/social/hooks/usePostSubscription'; import { SubscriptionLevels } from '@amityco/ts-sdk'; @@ -56,6 +56,14 @@ const CommentList = ({ ) : null; + if (comments.length === 0 && referenceType === 'story' && !isReplyComment) { + return ( + + {formatMessage({ id: 'storyViewer.commentSheet.empty' })} + + ); + } + if (comments.length === 0) return null; return ( diff --git a/src/social/components/CommentList/styles.tsx b/src/social/components/CommentList/styles.tsx index ce7e260c3..b50a2ba33 100644 --- a/src/social/components/CommentList/styles.tsx +++ b/src/social/components/CommentList/styles.tsx @@ -7,3 +7,13 @@ export const TabIconContainer = styled.div` display: flex; margin-right: 8px; `; + +export const NoCommentsContainer = styled.div` + ${({ theme }) => theme.typography.body}; + color: ${({ theme }) => theme.palette.base.shade2}; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; +`; diff --git a/src/social/components/CommunityInfo/UICommunityInfo.tsx b/src/social/components/CommunityInfo/UICommunityInfo.tsx index 756d06a7b..68dc74926 100644 --- a/src/social/components/CommunityInfo/UICommunityInfo.tsx +++ b/src/social/components/CommunityInfo/UICommunityInfo.tsx @@ -5,7 +5,7 @@ import { FormattedMessage, useIntl } from 'react-intl'; import Button from '~/core/components/Button'; import { PendingPostsBanner } from '~/social/components/CommunityInfo/PendingPostsBanner'; -import { backgroundImage as communityCoverPlaceholder } from '~/icons/CommunityCoverPicture'; + import { Count, Container, @@ -26,6 +26,7 @@ import { import { useCustomComponent } from '~/core/providers/CustomComponentsProvider'; import millify from 'millify'; import { isNonNullable } from '~/helpers/utils'; +import { StoryTab } from '~/social/v4/components/StoryTab'; interface UICommunityInfoProps { communityId: string; @@ -44,8 +45,15 @@ interface UICommunityInfoProps { onClickLeaveCommunity: (communityId: string) => void; canLeaveCommunity: boolean; canReviewPosts: boolean; + isStorySyncing: boolean; + haveStories: boolean; + haveStoryPermission: boolean; + isStoryErrored: boolean; + isSeen: boolean; name: string; postSetting: ValueOf; + setStoryFile: React.Dispatch>; + onClickStory: (communityId: string) => void; } const UICommunityInfo = ({ @@ -58,6 +66,11 @@ const UICommunityInfo = ({ isJoined, isOfficial, isPublic, + isStorySyncing, + isSeen, + isStoryErrored, + haveStories, + haveStoryPermission, avatarFileUrl, canEditCommunity, onEditCommunity, @@ -67,12 +80,14 @@ const UICommunityInfo = ({ canReviewPosts, name, postSetting, + setStoryFile, + onClickStory, }: UICommunityInfoProps) => { const { formatMessage } = useIntl(); return ( - + )} + onClickStory(communityId)} + onChange={setStoryFile} + /> + {isJoined && canEditCommunity && (