diff --git a/samples/react-contoso/.env.sample b/samples/react-contoso/.env.sample index 0ff9e3de73..f1fe447a3b 100644 --- a/samples/react-contoso/.env.sample +++ b/samples/react-contoso/.env.sample @@ -1,6 +1,8 @@ BROWSER="none" SKIP_PREFLIGHT_CHECK=true -FAST_REFRESH = false +FAST_REFRESH=false REACT_APP_SITE_NAME="Contoso Dashboard" -REACT_APP_CLIENT_ID="ed072e38-e76e-45ae-ab76-073cb95495bb" +REACT_APP_CLIENT_ID="00000000-0000-0000-0000-000000000000" +REACT_APP_URL_AZURE_FUNCTION="https://contoso.azurewebsites.net" +REACT_APP_BACKEND_CLIENT_ID="00000000-0000-0000-0000-000000000000" diff --git a/samples/react-contoso/README.md b/samples/react-contoso/README.md index 07438b6e26..e682dd8545 100644 --- a/samples/react-contoso/README.md +++ b/samples/react-contoso/README.md @@ -7,7 +7,7 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo In the root directory for the repository: ``` -yarn && yarn build && yarn watch:react` +yarn && yarn build && yarn watch:react ``` This will: @@ -18,4 +18,4 @@ This will: Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits. -You will also see any lint errors in the console. \ No newline at end of file +You will also see any lint errors in the console. diff --git a/samples/react-contoso/src/index.tsx b/samples/react-contoso/src/index.tsx index 96a6976d8b..eb52edd4e2 100644 --- a/samples/react-contoso/src/index.tsx +++ b/samples/react-contoso/src/index.tsx @@ -3,6 +3,7 @@ import { App } from './App'; import { mergeStyles } from '@fluentui/react'; import { Msal2Provider } from '@microsoft/mgt-msal2-provider'; import { Providers, LoginType } from '@microsoft/mgt-element'; +import { brokerSettings } from '@microsoft/mgt-chat'; // Inject some global styles mergeStyles({ @@ -14,6 +15,9 @@ mergeStyles({ } }); +brokerSettings.functionHost = process.env.REACT_APP_URL_AZURE_FUNCTION!; +brokerSettings.appId = process.env.REACT_APP_BACKEND_CLIENT_ID!; + Providers.globalProvider = new Msal2Provider({ clientId: process.env.REACT_APP_CLIENT_ID!, loginType: LoginType.Redirect, @@ -21,6 +25,12 @@ Providers.globalProvider = new Msal2Provider({ scopes: [ 'Bookmark.Read.All', 'Calendars.Read', + 'Chat.Create', + 'Chat.Read', + 'Chat.ReadBasic', + 'Chat.ReadWrite', + 'ChatMember.ReadWrite', + 'ChatMessage.Send', 'ExternalItem.Read.All', 'Files.Read', 'Files.Read.All', diff --git a/samples/react-contoso/src/pages/ChatPage.tsx b/samples/react-contoso/src/pages/ChatPage.tsx new file mode 100644 index 0000000000..353d2f0dd6 --- /dev/null +++ b/samples/react-contoso/src/pages/ChatPage.tsx @@ -0,0 +1,137 @@ +import * as React from 'react'; +import { PageHeader } from '../components/PageHeader'; +import { Get } from '@microsoft/mgt-react'; +import { Loading } from '../components/Loading'; +import { + shorthands, + makeStyles, + mergeClasses, + Button, + Dialog, + DialogTrigger, + DialogSurface, + DialogBody, + DialogTitle +} from '@fluentui/react-components'; +import { Chat as GraphChat } from '@microsoft/microsoft-graph-types'; +import { Chat, NewChat } from '@microsoft/mgt-chat'; +import ChatListTemplate from './Chats/ChatListTemplate'; + +const useStyles = makeStyles({ + container: { + display: 'flex', + flexDirection: 'row' + }, + panels: { + ...shorthands.padding('10px') + }, + main: { + display: 'flex', + flexDirection: 'column', + flexWrap: 'nowrap', + width: '300px', + minWidth: '300px', + ...shorthands.overflow('auto'), + maxHeight: '80vh', + borderRightColor: 'var(--neutral-stroke-rest)', + borderRightStyle: 'solid', + borderRightWidth: '1px' + }, + side: { + display: 'flex', + flexDirection: 'column', + flexWrap: 'nowrap', + width: '80%', + ...shorthands.overflow('auto'), + maxHeight: '80vh', + height: '100%' + }, + newChat: { + paddingBottom: '10px', + marginRight: '0px', + marginLeft: 'auto' + }, + dialog: { + display: 'block' + } +}); + +export const ChatPage: React.FunctionComponent = () => { + const styles = useStyles(); + const [selectedChat, setSelectedChat] = React.useState(); + const [isNewChatOpen, setIsNewChatOpen] = React.useState(false); + + const chatSelected = (e: GraphChat) => { + if (e.id !== selectedChat?.id && isNewChatOpen) { + setIsNewChatOpen(false); + } + setSelectedChat(e); + }; + + return ( + <> + + +
+
+
+ + + + + + + New Chat + { + setIsNewChatOpen(false); + }} + hideTitle={true} + > + + + +
+ +
+
{selectedChat && }
+
+ + ); +}; + +interface ChatListProps { + onChatSelected: (e: GraphChat) => void; + chatSelected: GraphChat | undefined; +} + +const ChatList = React.memo((props: ChatListProps) => { + const getPreviousDate = (months: number) => { + const date = new Date(); + date.setMonth(date.getMonth() - months); + return date.toISOString(); + }; + + return ( + + + + + ); +}); diff --git a/samples/react-contoso/src/pages/Chats/ChatItem.tsx b/samples/react-contoso/src/pages/Chats/ChatItem.tsx new file mode 100644 index 0000000000..e7029683d4 --- /dev/null +++ b/samples/react-contoso/src/pages/Chats/ChatItem.tsx @@ -0,0 +1,161 @@ +import { Persona, makeStyles, mergeClasses, shorthands } from '@fluentui/react-components'; +import { Providers } from '@microsoft/mgt-element'; +import { MgtTemplateProps, Person, ViewType } from '@microsoft/mgt-react'; +import { Chat, AadUserConversationMember } from '@microsoft/microsoft-graph-types'; +import React, { useCallback, useEffect, useState } from 'react'; +import { PeopleCommunityRegular, CalendarMonthRegular } from '@fluentui/react-icons'; + +const useStyles = makeStyles({ + chat: { + paddingLeft: '5px', + paddingRight: '5px', + display: 'flex', + alignItems: 'center', + height: '50px', + cursor: 'pointer', + ':hover': { + backgroundColor: 'var(--colorNeutralBackground1Hover)' + } + }, + active: { + backgroundColor: 'var(--colorNeutralBackground1Selected)' + }, + person: { + '--person-avatar-size-small': '40px', + '& .fui-Persona__primaryText': { + fontSize: 'var(--fontSizeBase300);' + }, + '& .fui-Persona__secondaryText': { + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + width: '200px', + display: 'inline-block', + ...shorthands.overflow('hidden') + } + }, + messagePreview: { + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + width: '200px', + display: 'inline-block', + ...shorthands.overflow('hidden') + } +}); + +export interface ChatInteractionProps { + onSelected: (selected: Chat) => void; + selectedChat?: Chat; +} + +interface ChatItemProps { + chat: Chat; + isSelected?: boolean; +} + +const getMessagePreview = (chat: Chat) => { + return chat?.lastMessagePreview?.body?.contentType === 'text' ? chat?.lastMessagePreview?.body?.content : '...'; +}; + +const ChatItem = ({ chat, isSelected, onSelected }: ChatItemProps & ChatInteractionProps) => { + const styles = useStyles(); + const [myId, setMyId] = useState(); + + useEffect(() => { + const getMyId = async () => { + const me = await Providers.me(); + setMyId(me.id); + }; + if (!myId) { + void getMyId(); + } + }, [myId]); + + const getOtherParticipantId = useCallback( + (chat: Chat) => { + const member = chat.members?.find(m => (m as AadUserConversationMember).userId !== myId); + + if (member) { + console.log('member', member); + return (member as AadUserConversationMember).userId as string; + } else if (chat.members?.length === 1 && (chat.members[0] as AadUserConversationMember).userId === myId) { + return myId; + } + + return undefined; + }, + [myId] + ); + + const getGroupTitle = useCallback((chat: Chat) => { + let groupTitle: string | undefined = ''; + if (chat.topic) { + groupTitle = chat.topic; + } else { + groupTitle = chat.members + ?.map(member => { + return member.displayName?.split(' ')[0]; + }) + .join(', '); + } + + return groupTitle; + }, []); + + return ( + <> + {myId && ( +
+ {chat.chatType === 'oneOnOne' && ( + onSelected(chat)} + className={styles.person} + > + + + )} + {chat.chatType === 'group' && ( +
onSelected(chat)}> + , initials: null }} + className={styles.person} + /> + +
+ )} + {chat.chatType === 'meeting' && ( +
onSelected(chat)}> + , initials: null }} + name={getGroupTitle(chat)} + secondaryText={getMessagePreview(chat)} + /> +
+ )} +
+ )} + + ); +}; + +const MessagePreview = (props: MgtTemplateProps & ChatItemProps) => { + const styles = useStyles(); + + return ( + <> + {getMessagePreview(props.chat)} + + ); +}; + +export default ChatItem; diff --git a/samples/react-contoso/src/pages/Chats/ChatListTemplate.tsx b/samples/react-contoso/src/pages/Chats/ChatListTemplate.tsx new file mode 100644 index 0000000000..5cdb58be57 --- /dev/null +++ b/samples/react-contoso/src/pages/Chats/ChatListTemplate.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { MgtTemplateProps } from '@microsoft/mgt-react'; +import { Chat } from '@microsoft/microsoft-graph-types'; +import ChatItem, { ChatInteractionProps } from './ChatItem'; +import { Chat as GraphChat } from '@microsoft/microsoft-graph-types'; + +const ChatListTemplate = (props: MgtTemplateProps & ChatInteractionProps) => { + const { value } = props.dataContext; + const chats: Chat[] = value; + const [selectedChat, setSelectedChat] = useState(props.selectedChat || chats[0]); + + const onChatSelected = React.useCallback( + (e: GraphChat) => { + setSelectedChat(e); + props.onSelected(selectedChat); + }, + [setSelectedChat, selectedChat, props] + ); + + // Set the selected chat to the first chat in the list + // Fires only the first time the component is rendered + React.useEffect(() => { + onChatSelected(selectedChat); + }); + + const isChatActive = (chat: Chat) => { + if (selectedChat) { + return selectedChat && chat.id === selectedChat?.id; + } + + return false; + }; + + console.log('chats', chats); + return ( +
+ {chats.map((c, index) => ( + + ))} +
+ ); +}; + +export default ChatListTemplate; diff --git a/samples/react-contoso/src/services/Navigation.tsx b/samples/react-contoso/src/services/Navigation.tsx index 75888dda8d..6aeda4cb8b 100644 --- a/samples/react-contoso/src/services/Navigation.tsx +++ b/samples/react-contoso/src/services/Navigation.tsx @@ -5,7 +5,8 @@ import { TextBulletListSquareRegular, CalendarMailRegular, DocumentRegular, - TagMultipleRegular + TagMultipleRegular, + ChatRegular } from '@fluentui/react-icons'; import { DashboardPage } from '../pages/DashboardPage'; import { OutlookPage } from '../pages/OutlookPage'; @@ -13,6 +14,7 @@ import { SearchPage } from '../pages/SearchPage'; import { HomePage } from '../pages/HomePage'; import { FilesPage } from '../pages/FilesPage'; import { TaxonomyPage } from '../pages/TaxonomyPage'; +import { ChatPage } from '../pages/ChatPage'; export const getNavigation = (isSignedIn: boolean) => { let navItems: NavigationItem[] = []; @@ -68,6 +70,16 @@ export const getNavigation = (isSignedIn: boolean) => { exact: true }); + navItems.push({ + name: 'Chat', + url: '/chat', + icon: , + key: 'chat', + requiresLogin: true, + component: , + exact: true + }); + navItems.push({ name: 'Search', url: '/search', diff --git a/yarn.lock b/yarn.lock index 1ad0e98a74..dc7470c90e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10388,13 +10388,13 @@ "@typescript-eslint/types" "5.6.0" "@typescript-eslint/visitor-keys" "5.6.0" -"@typescript-eslint/scope-manager@5.61.0": - version "5.61.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.61.0.tgz#b670006d069c9abe6415c41f754b1b5d949ef2b2" - integrity sha512-W8VoMjoSg7f7nqAROEmTt6LoBpn81AegP7uKhhW5KzYlehs8VV0ZW0fIDVbcZRcaP3aPSW+JZFua+ysQN+m/Nw== +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== dependencies: - "@typescript-eslint/types" "5.61.0" - "@typescript-eslint/visitor-keys" "5.61.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" "@typescript-eslint/type-utils@5.59.0": version "5.59.0" @@ -10406,13 +10406,13 @@ debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/type-utils@5.61.0": - version "5.61.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.61.0.tgz#e90799eb2045c4435ea8378cb31cd8a9fddca47a" - integrity sha512-kk8u//r+oVK2Aj3ph/26XdH0pbAkC2RiSjUYhKD+PExemG4XSjpGFeyZ/QM8lBOa7O8aGOU+/yEbMJgQv/DnCg== +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== dependencies: - "@typescript-eslint/typescript-estree" "5.61.0" - "@typescript-eslint/utils" "5.61.0" + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" debug "^4.3.4" tsutils "^3.21.0" @@ -10441,10 +10441,10 @@ resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.6.0.tgz" integrity sha512-OIZffked7mXv4mXzWU5MgAEbCf9ecNJBKi+Si6/I9PpTaj+cf2x58h2oHW5/P/yTnPkKaayfjhLvx+crnl5ubA== -"@typescript-eslint/types@5.61.0": - version "5.61.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.61.0.tgz#e99ff11b5792d791554abab0f0370936d8ca50c0" - integrity sha512-ldyueo58KjngXpzloHUog/h9REmHl59G1b3a5Sng1GfBo14BkS3ZbMEb3693gnP1k//97lh7bKsp6/V/0v1veQ== +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== "@typescript-eslint/typescript-estree@5.3.1": version "5.3.1" @@ -10511,13 +10511,13 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.61.0": - version "5.61.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.61.0.tgz#4c7caca84ce95bb41aa585d46a764bcc050b92f3" - integrity sha512-Fud90PxONnnLZ36oR5ClJBLTLfU4pIWBmnvGwTbEa2cXIqj70AEDEmOmpkFComjBZ/037ueKrOdHuYmSFVD7Rw== +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== dependencies: - "@typescript-eslint/types" "5.61.0" - "@typescript-eslint/visitor-keys" "5.61.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -10564,17 +10564,17 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@5.61.0": - version "5.61.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.61.0.tgz#5064838a53e91c754fffbddd306adcca3fe0af36" - integrity sha512-mV6O+6VgQmVE6+xzlA91xifndPW9ElFW8vbSF0xCT/czPXVhwDewKila1jOyRwa9AE19zKnrr7Cg5S3pJVrTWQ== +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.61.0" - "@typescript-eslint/types" "5.61.0" - "@typescript-eslint/typescript-estree" "5.61.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" eslint-scope "^5.1.1" semver "^7.3.7" @@ -10618,12 +10618,12 @@ "@typescript-eslint/types" "5.6.0" eslint-visitor-keys "^3.0.0" -"@typescript-eslint/visitor-keys@5.61.0": - version "5.61.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.61.0.tgz#c79414fa42158fd23bd2bb70952dc5cdbb298140" - integrity sha512-50XQ5VdbWrX06mQXhy93WywSFZZGsv3EOjq+lqp6WC2t+j3mb6A9xYVdrRxafvK88vg9k9u+CT4l6D8PEatjKg== +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== dependencies: - "@typescript-eslint/types" "5.61.0" + "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" "@uifabric/foundation@^7.10.16", "@uifabric/foundation@^7.10.3":