From c34f0f8f38393e51c646c0afc7018bbaa63374f2 Mon Sep 17 00:00:00 2001 From: Tania Roche Date: Fri, 13 Mar 2026 11:20:37 +0000 Subject: [PATCH 1/4] Conversation screen layout and styling --- app/assets/icons/arrow-upward.tsx | 12 + app/assets/icons/attach-file.tsx | 12 + app/assets/icons/index.ts | 4 + app/src/__tests__/utils/formatting.test.ts | 98 +- app/src/components/avatar/Avatar.tsx | 2 +- .../chat/ChatConversationFooter.tsx | 62 ++ app/src/components/chat/ChatMessage.tsx | 66 ++ app/src/components/details/DetailsHeader.tsx | 27 +- .../screens/account/UserSettingsScreen.tsx | 1 - .../screens/chat/ChatConversationScreen.tsx | 60 +- app/src/screens/chat/ChatScreen.tsx | 22 +- app/src/screens/chat/chatData.ts | 901 ++++++++++++++++++ app/src/screens/chat/messageData.ts | 462 +++++++++ app/src/styles/components/button.ts | 41 +- app/src/styles/components/chat.ts | 101 ++ app/src/styles/components/common.ts | 3 + app/src/styles/components/index.ts | 1 + app/src/styles/components/input.ts | 10 +- app/src/styles/components/listItem.ts | 68 +- app/src/types/chat.ts | 45 +- app/src/types/lists.ts | 1 + app/src/utils/formatting.ts | 17 +- 22 files changed, 1928 insertions(+), 88 deletions(-) create mode 100644 app/assets/icons/arrow-upward.tsx create mode 100644 app/assets/icons/attach-file.tsx create mode 100644 app/src/components/chat/ChatConversationFooter.tsx create mode 100644 app/src/components/chat/ChatMessage.tsx create mode 100644 app/src/screens/chat/chatData.ts create mode 100644 app/src/screens/chat/messageData.ts create mode 100644 app/src/styles/components/chat.ts diff --git a/app/assets/icons/arrow-upward.tsx b/app/assets/icons/arrow-upward.tsx new file mode 100644 index 00000000..2d660c24 --- /dev/null +++ b/app/assets/icons/arrow-upward.tsx @@ -0,0 +1,12 @@ +import { Path } from 'react-native-svg'; + +const ArrowUpwardOutlined = ({ color }: { color: string }) => ( + <> + + +); + +export default ArrowUpwardOutlined; diff --git a/app/assets/icons/attach-file.tsx b/app/assets/icons/attach-file.tsx new file mode 100644 index 00000000..3199cbd1 --- /dev/null +++ b/app/assets/icons/attach-file.tsx @@ -0,0 +1,12 @@ +import { Path } from 'react-native-svg'; + +const AttachFileOutlined = ({ color }: { color: string }) => ( + <> + + +); + +export default AttachFileOutlined; diff --git a/app/assets/icons/index.ts b/app/assets/icons/index.ts index 37a0e84b..f03ab4d1 100644 --- a/app/assets/icons/index.ts +++ b/app/assets/icons/index.ts @@ -5,7 +5,9 @@ import AddBusinessOutlined from './add-business'; import AlternateEmailOutlined from './alternate-email'; import AltRouteOutlined from './alt-route'; import ApiOutlined from './api'; +import ArrowUpwardOutlined from './arrow-upward'; import AssignmentTurnedInOutlined from './assignment-turned-in'; +import AttachFileOutlined from './attach-file'; import BadgeOutlined from './badge'; import BalanceOutlined from './balance'; import CategoryOutlined from './category'; @@ -56,7 +58,9 @@ export const OutlinedIcons = { 'alternate-email': AlternateEmailOutlined, 'alt-route': AltRouteOutlined, api: ApiOutlined, + 'arrow-upward': ArrowUpwardOutlined, 'assignment-turned-in': AssignmentTurnedInOutlined, + 'attach-file': AttachFileOutlined, badge: BadgeOutlined, balance: BalanceOutlined, category: CategoryOutlined, diff --git a/app/src/__tests__/utils/formatting.test.ts b/app/src/__tests__/utils/formatting.test.ts index 6d8b35a5..392051c6 100644 --- a/app/src/__tests__/utils/formatting.test.ts +++ b/app/src/__tests__/utils/formatting.test.ts @@ -11,6 +11,7 @@ import { formatUtcDate, calculateRelativeDate, getDatePartsForLocale, + formatDateForChat, } from '@/utils/formatting'; const EMPTY_STRING = ''; @@ -358,28 +359,24 @@ describe('getDatePartsForLocale', () => { expect(getDatePartsForLocale('invalid-date', enLocale)).toBeNull(); }); - it('returns correct date and time parts for English locale', () => { + it('returns correct date parts for English locale', () => { const result = getDatePartsForLocale(isoDate, enLocale); expect(result).not.toBeNull(); if (!result) return; - expect(result.hour).toBe('15'); - expect(result.minute).toBe('45'); expect(result.weekday).toBe('Tue'); expect(result.day).toBe('10'); expect(result.month).toBe('Mar'); expect(result.year).toBe('2026'); }); - it('returns correct date and time parts for French locale', () => { + it('returns correct date parts for French locale', () => { const result = getDatePartsForLocale(isoDate, frLocale); expect(result).not.toBeNull(); if (!result) return; - expect(result.hour).toBe('15'); - expect(result.minute).toBe('45'); expect(result.weekday).toBe('mar.'); expect(result.day).toBe('10'); expect(result.month).toBe('mars'); @@ -393,11 +390,96 @@ describe('getDatePartsForLocale', () => { expect(result).not.toBeNull(); if (!result) return; - expect(result.hour).toBe('08'); - expect(result.minute).toBe('05'); expect(result.weekday).toBe('Fri'); expect(result.day).toBe('25'); expect(result.month).toBe('Dec'); expect(result.year).toBe('2026'); }); }); + +describe('formatDateForChat', () => { + const locale = 'en-GB'; + + const NOW = new Date('2026-03-10T12:00:00Z'); + + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(NOW); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + describe('invalid input', () => { + it('returns EMPTY_STRING when isoDate is undefined', () => { + expect(formatDateForChat(undefined, locale)).toBe(EMPTY_STRING); + }); + + it('returns EMPTY_STRING when isoDate is empty string', () => { + expect(formatDateForChat('', locale)).toBe(EMPTY_STRING); + }); + + it('returns EMPTY_STRING when isoDate is invalid', () => { + expect(formatDateForChat('invalid-date', locale)).toBe(EMPTY_STRING); + }); + }); + + describe('within last 24 hours', () => { + it('returns time (HH:mm)', () => { + const iso = '2026-03-10T09:30:00Z'; + + expect(formatDateForChat(iso, locale)).toBe('09:30'); + }); + }); + + describe('within last 7 days', () => { + it('returns weekday', () => { + const iso = '2026-03-08T10:00:00Z'; + + const result = formatDateForChat(iso, locale); + + expect(result).toBe('Sun'); + }); + }); + + describe('within current year but older than 7 days', () => { + it('returns DD Mon', () => { + const iso = '2026-02-01T10:00:00Z'; + + expect(formatDateForChat(iso, locale)).toBe('01 Feb'); + }); + + it('pads day with leading zero', () => { + const iso = '2026-01-05T10:00:00Z'; + + expect(formatDateForChat(iso, locale)).toBe('05 Jan'); + }); + }); + + describe('older than current year', () => { + it('returns DD Mon YYYY', () => { + const iso = '2025-12-25T10:00:00Z'; + + expect(formatDateForChat(iso, locale)).toBe('25 Dec 2025'); + }); + }); + + describe('different locales', () => { + it('formats weekday using locale rules', () => { + const iso = '2026-03-08T10:00:00Z'; + + const result = formatDateForChat(iso, 'fr-FR'); + + expect(result).toBe('dim.'); + }); + + it('formats month using locale rules', () => { + const iso = '2026-02-01T10:00:00Z'; + + const result = formatDateForChat(iso, 'fr-FR'); + + expect(result).toBe('01 févr.'); + }); + }); +}); diff --git a/app/src/components/avatar/Avatar.tsx b/app/src/components/avatar/Avatar.tsx index 5486478e..dbb651fc 100644 --- a/app/src/components/avatar/Avatar.tsx +++ b/app/src/components/avatar/Avatar.tsx @@ -95,7 +95,7 @@ const Avatar: React.FC = ({ diff --git a/app/src/components/chat/ChatConversationFooter.tsx b/app/src/components/chat/ChatConversationFooter.tsx new file mode 100644 index 00000000..1cb5819e --- /dev/null +++ b/app/src/components/chat/ChatConversationFooter.tsx @@ -0,0 +1,62 @@ +import { OutlinedIcons } from '@assets/icons'; +import { View, TextInput, StyleSheet } from 'react-native'; + +import OutlinedIcon from '@/components/common/OutlinedIcon'; +import { buttonStyle, Color, inputStyle, chatStyle } from '@/styles'; + +type Props = { + value: string; + onChangeText: (text: string) => void; + onSend: () => void; +}; + +const ChatConversationFooter = ({ value, onChangeText, onSend }: Props) => { + return ( + + + + + + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + textInputWrapper: chatStyle.textInputWrapper, + iconContainer: chatStyle.iconContainer, + inputContainer: chatStyle.inputContainer, + input: { + ...inputStyle.container, + ...inputStyle.inputChat, + }, + buttonPrimaryIconOnly: { + ...buttonStyle.primary, + ...buttonStyle.primaryIconOnly, + }, +}); + +export default ChatConversationFooter; diff --git a/app/src/components/chat/ChatMessage.tsx b/app/src/components/chat/ChatMessage.tsx new file mode 100644 index 00000000..e5144540 --- /dev/null +++ b/app/src/components/chat/ChatMessage.tsx @@ -0,0 +1,66 @@ +import { useMemo } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; + +import OutlinedIcon from '../common/OutlinedIcon'; + +import Avatar from '@/components/avatar/Avatar'; +import { chatMessageStyle } from '@/styles/components'; +import { Color } from '@/styles/tokens'; +import type { Message, MessageType } from '@/types/chat'; +import { formatDateForChat, getTime } from '@/utils/formatting'; + +type Props = { + message: Message; + currentUserId: string; + locale: string; +}; + +const ChatMessage = ({ message, currentUserId, locale }: Props) => { + const isOwn = message.identity.id === currentUserId; + const type: MessageType = isOwn ? 'own' : 'other'; + const messageDate = formatDateForChat(message.audit.created?.at, locale); + const messageTime = getTime(message.audit.created?.at || ''); + + const styles = useMemo( + () => + StyleSheet.create({ + /* eslint-disable react-native/no-unused-styles */ + container: chatMessageStyle[type].container, + messageWrapper: chatMessageStyle.messageWrapper, + textContainer: chatMessageStyle[type].textContainer, + text: chatMessageStyle[type].text, + info: chatMessageStyle[type].info, + infoText: chatMessageStyle.infoText, + avatarWrapper: chatMessageStyle.avatarWrapper, + }), + [type], + ); + + const avatarId = message.identity.id; + const avatarPath = message.identity?.icon; + + return ( + + {!isOwn && ( + + + + )} + + + {!isOwn && {message.sender.identity.name}} + {messageDate !== messageTime && {messageDate}} + {messageTime} + + {isOwn && } + + + + {message.content} + + + + ); +}; + +export default ChatMessage; diff --git a/app/src/components/details/DetailsHeader.tsx b/app/src/components/details/DetailsHeader.tsx index 8b384604..9f3baa70 100644 --- a/app/src/components/details/DetailsHeader.tsx +++ b/app/src/components/details/DetailsHeader.tsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { View, Text, StyleSheet } from 'react-native'; @@ -14,6 +15,7 @@ const DetailsHeader = ({ title, subtitle, statusText, + variant = 'default', headerTitleTestId, headerStatusTestId, }: ListItemWithStatusProps) => { @@ -23,6 +25,20 @@ const DetailsHeader = ({ const hasImage = Boolean(imagePath); const status = getStatus(statusText, statusList); + const styles = useMemo( + () => + StyleSheet.create({ + /* eslint-disable react-native/no-unused-styles */ + screenHeader: listItemStyle.detailsHeaderContainer[variant], + topRow: listItemStyle.detailsHeaderTopRow[variant], + avatarWrapper: listItemStyle.detailsHeaderAvatarWrapper[variant], + textWrapper: listItemStyle.textContainer, + title: listItemStyle.detailsHeaderTitle, + subtitle: listItemStyle.detailsHeaderSubtitle, + }), + [variant], + ); + return ( @@ -42,20 +58,11 @@ const DetailsHeader = ({ - {status && ( + {status && statusText && ( )} ); }; -const styles = StyleSheet.create({ - screenHeader: listItemStyle.detailsHeaderContainer, - topRow: listItemStyle.detailsHeaderTopRow, - textWrapper: listItemStyle.textContainer, - title: listItemStyle.detailsHeaderTitle, - subtitle: listItemStyle.detailsHeaderSubtitle, - avatarWrapper: listItemStyle.textAndImage.avatarWrapper, -}); - export default DetailsHeader; diff --git a/app/src/screens/account/UserSettingsScreen.tsx b/app/src/screens/account/UserSettingsScreen.tsx index e041385e..48685d2b 100644 --- a/app/src/screens/account/UserSettingsScreen.tsx +++ b/app/src/screens/account/UserSettingsScreen.tsx @@ -73,7 +73,6 @@ const styles = StyleSheet.create({ }, sectionHeader: screenStyle.sectionHeader, buttonPrimary: { - ...buttonStyle.common, ...buttonStyle.primaryLight, ...buttonStyle.fullWidth, }, diff --git a/app/src/screens/chat/ChatConversationScreen.tsx b/app/src/screens/chat/ChatConversationScreen.tsx index a93debb9..d3812ce7 100644 --- a/app/src/screens/chat/ChatConversationScreen.tsx +++ b/app/src/screens/chat/ChatConversationScreen.tsx @@ -1,14 +1,62 @@ -import { RouteProp, useRoute } from '@react-navigation/native'; -import { Text } from 'react-native'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FlatList, KeyboardAvoidingView, Platform, StyleSheet } from 'react-native'; -import type { RootStackParamList } from '@/types/navigation'; +import { messages } from './messageData'; -type ChatConversationRouteProp = RouteProp; +import ChatConversationFooter from '@/components/chat/ChatConversationFooter'; +import ChatMessage from '@/components/chat/ChatMessage'; +import DetailsHeader from '@/components/details/DetailsHeader'; +import { screenStyle } from '@/styles'; const ChatConversationScreen = () => { - const { id } = useRoute().params; + const [inputText, setInputText] = useState(''); - return Chat for user {id}; + // TODO: replace when API is ready + const currentUserId = 'USR-2267-7838'; + const { i18n } = useTranslation(); + + const sendMessage = () => { + if (!inputText.trim()) return; + setInputText(''); + }; + + return ( + + {/* TODO: replace with real data when API is ready */} + + item.id} + keyboardShouldPersistTaps="handled" + renderItem={({ item }) => ( + + )} + /> + + + ); }; +const styles = StyleSheet.create({ + container: screenStyle.containerFlex, + flatList: { + ...screenStyle.containerFlex, + ...screenStyle.padding, + }, +}); + export default ChatConversationScreen; diff --git a/app/src/screens/chat/ChatScreen.tsx b/app/src/screens/chat/ChatScreen.tsx index 44e5aea4..01009065 100644 --- a/app/src/screens/chat/ChatScreen.tsx +++ b/app/src/screens/chat/ChatScreen.tsx @@ -1,23 +1,29 @@ import { useNavigation } from '@react-navigation/native'; import type { StackNavigationProp } from '@react-navigation/stack'; -import { TouchableOpacity, Text } from 'react-native'; +import { data } from './chatData'; + +import ListViewChat from '@/components/list/ListViewChat'; +import { ChatItem } from '@/types/chat'; import type { RootStackParamList } from '@/types/navigation'; const ChatScreen = () => { const navigation = useNavigation>(); + // TODO: warp into loading / error handling component when API is ready return ( - { + { navigation.navigate('chatConversation', { - id: 'USR-123', + id, }); }} - activeOpacity={0.7} - > - USR: 123 - + /> ); }; diff --git a/app/src/screens/chat/chatData.ts b/app/src/screens/chat/chatData.ts new file mode 100644 index 00000000..f8b90d3e --- /dev/null +++ b/app/src/screens/chat/chatData.ts @@ -0,0 +1,901 @@ +export const data = [ + { + id: 'CHT-0907-6973-2140', + name: 'Test Group - Vendor', + revision: 1, + type: 'Group', + participants: [ + { + id: 'CHP-8745-6195-2902', + revision: 1, + chat: { + id: 'CHT-0907-6973-2140', + name: 'Test Group - Vendor', + revision: 1, + type: 'Group', + }, + contact: { + id: 'CTT-0294-3021', + name: 'Darren Drumm', + revision: 1, + email: 'darren.drumm@softwareone.com', + identity: { + id: 'USR-7703-9615', + name: 'Darren Drumm', + revision: 3, + }, + }, + identity: { + id: 'USR-7703-9615', + name: 'Darren Drumm', + revision: 3, + }, + account: { + id: 'ACC-4714-5418', + name: 'Microsoft', + revision: 1, + type: 'Vendor', + status: 'Active', + }, + muted: false, + status: 'Active', + unreadMessageCount: 1, + }, + { + id: 'CHP-9901-7302-2345', + revision: 3, + chat: { + id: 'CHT-0907-6973-2140', + name: 'Test Group - Vendor', + revision: 1, + type: 'Group', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-3801-3447-8769', + revision: 1, + content: 'a test message with very very very long text that needs to be truncated', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 1000, + }, + ], + lastMessage: { + id: 'CMG-3801-3447-8769', + revision: 1, + chat: { + id: 'CHT-0907-6973-2140', + name: 'Test Group - Vendor', + revision: 1, + type: 'Group', + }, + sender: { + id: 'CHP-9901-7302-2345', + revision: 3, + chat: { + id: 'CHT-0907-6973-2140', + name: 'Test Group - Vendor', + revision: 1, + type: 'Group', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-3801-3447-8769', + revision: 1, + content: 'a test message with very very very long text that needs to be truncated', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'a test message with very very very long text that needs to be truncated', + visibility: 'Public', + isDeleted: false, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-10T13:10:37.212Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + }, + { + id: 'CHT-2749-6237-9222', + revision: 1, + type: 'Direct', + participants: [ + { + id: 'CHP-1501-9758-7338', + revision: 1, + chat: { + id: 'CHT-2749-6237-9222', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-2736-2051', + name: 'microsoft licenses address', + revision: 1, + email: 'msft@medpt.com', + identity: { + id: 'USR-2421-6365', + name: 'microsoft licenses address', + revision: 1, + }, + }, + identity: { + id: 'USR-2421-6365', + name: 'microsoft licenses address', + revision: 1, + }, + account: { + id: 'ACC-2180-7619', + name: 'Medpoint Digital, Inc.', + revision: 1, + type: 'Client', + status: 'Active', + }, + muted: false, + status: 'Active', + unreadMessageCount: 1, + }, + { + id: 'CHP-8024-2154-4959', + revision: 5, + chat: { + id: 'CHT-2749-6237-9222', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-6449-4326-9246', + revision: 1, + content: 'test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 2, + }, + ], + lastMessage: { + id: 'CMG-6449-4326-9246', + revision: 1, + chat: { + id: 'CHT-2749-6237-9222', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-8024-2154-4959', + revision: 5, + chat: { + id: 'CHT-2749-6237-9222', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-6449-4326-9246', + revision: 1, + content: 'test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'test', + visibility: 'Public', + isDeleted: false, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-09T12:54:25.055Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + }, + { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + participants: [ + { + id: 'CHP-2317-4035-2836', + revision: 1, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + contact: { + id: 'CTT-0869-8652', + name: 'Maciej Biernat', + revision: 1, + email: 'maciej.biernat@softwareone.com', + identity: { + id: 'USR-1835-2941', + name: 'Maciej Biernat', + icon: '/v1/accounts/users/USR-1835-2941/icon', + revision: 8, + }, + }, + identity: { + id: 'USR-1835-2941', + name: 'Maciej Biernat', + icon: '/v1/accounts/users/USR-1835-2941/icon', + revision: 8, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + unreadMessageCount: 6, + }, + { + id: 'CHP-4483-3016-2902', + revision: 1, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + contact: { + id: 'CTT-5259-9165', + name: 'Halil Karaca', + revision: 1, + email: 'halil.karaca@softwareone.com', + identity: { + id: 'USR-8314-3681', + name: 'Halil Karaca', + revision: 1, + }, + }, + identity: { + id: 'USR-8314-3681', + name: 'Halil Karaca', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + unreadMessageCount: 6, + }, + { + id: 'CHP-5307-6784-3140', + revision: 14, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-1083-9603-6187', + revision: 1, + content: 'test for recent message', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + { + id: 'CHP-5576-4281-4209', + revision: 1, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + contact: { + id: 'CTT-5591-7535', + name: 'Magdalena Komuda', + revision: 1, + email: 'Magdalena.Komuda@softwareone.com', + identity: { + id: 'USR-8905-2920', + name: 'Magdalena Komuda', + revision: 1, + }, + }, + identity: { + id: 'USR-8905-2920', + name: 'Magdalena Komuda', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + unreadMessageCount: 6, + }, + { + id: 'CHP-6078-8516-3871', + revision: 1, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + contact: { + id: 'CTT-8104-3524', + name: 'Balint Polgar', + revision: 1, + email: 'balint.polgar@softwareone.com', + identity: { + id: 'USR-0556-8733', + name: 'Balint Polgar', + icon: '/v1/accounts/users/USR-0556-8733/icon', + revision: 18, + }, + }, + identity: { + id: 'USR-0556-8733', + name: 'Balint Polgar', + icon: '/v1/accounts/users/USR-0556-8733/icon', + revision: 18, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + unreadMessageCount: 6, + }, + { + id: 'CHP-8092-8347-2652', + revision: 16, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + contact: { + id: 'CTT-4634-2964', + name: 'MIchal Walczak', + revision: 1, + email: 'michal.walczak@softwareone.com', + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + }, + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-1083-9603-6187', + revision: 1, + content: 'test for recent message', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + ], + lastMessage: { + id: 'CMG-1083-9603-6187', + revision: 1, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + sender: { + id: 'CHP-5307-6784-3140', + revision: 14, + chat: { + id: 'CHT-9231-8917-3633', + name: 'Mobile Dev Test Chat - Group One', + revision: 2, + type: 'Group', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-1083-9603-6187', + revision: 1, + content: 'test for recent message', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'test for recent message', + visibility: 'Public', + isDeleted: false, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-02-09T10:50:26.430Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + }, + { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + participants: [ + { + id: 'CHP-4768-7226-5802', + revision: 9, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4634-2964', + name: 'MIchal Walczak', + revision: 1, + email: 'michal.walczak@softwareone.com', + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + }, + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4912-0427-1898', + revision: 1, + content: 'You should be able to see that chat, as I have invited you too', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 1, + }, + { + id: 'CHP-6694-2147-1686', + revision: 17, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 100, + }, + ], + lastMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-6694-2147-1686', + revision: 17, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'update test', + visibility: 'Public', + isDeleted: false, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2025-03-09T10:46:50.968Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + }, + { + id: 'CHT-3390-6074-5983', + revision: 1, + type: 'Direct', + participants: [ + { + id: 'CHP-1678-4601-5308', + revision: 1, + chat: { + id: 'CHT-3390-6074-5983', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + unreadMessageCount: 0, + }, + { + id: 'CHP-5456-0462-7816', + revision: 1, + chat: { + id: 'CHT-3390-6074-5983', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-0869-8652', + name: 'Maciej Biernat', + revision: 1, + email: 'maciej.biernat@softwareone.com', + identity: { + id: 'USR-1835-2941', + name: 'Maciej Biernat', + icon: '/v1/accounts/users/USR-1835-2941/icon', + revision: 8, + }, + }, + identity: { + id: 'USR-1835-2941', + name: 'Maciej Biernat', + icon: '/v1/accounts/users/USR-1835-2941/icon', + revision: 8, + }, + account: { + id: 'ACC-1360-4582', + name: 'Adastra', + icon: '/v1/accounts/accounts/ACC-1360-4582/icon', + revision: 1, + type: 'Client', + status: 'Active', + }, + muted: false, + status: 'Active', + unreadMessageCount: 0, + }, + ], + }, +]; diff --git a/app/src/screens/chat/messageData.ts b/app/src/screens/chat/messageData.ts new file mode 100644 index 00000000..c23fcf19 --- /dev/null +++ b/app/src/screens/chat/messageData.ts @@ -0,0 +1,462 @@ +export const messages: any = [ + { + id: 'CMG-4912-0427-1898', + revision: 1, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-6694-2147-1686', + revision: 21, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'You should be able to see that chat, as I have invited you too', + visibility: 'Public', + isDeleted: false, + links: [], + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-05T15:24:58.200Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + { + id: 'CMG-2109-4320-3483', + revision: 1, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-6694-2147-1686', + revision: 21, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'also, they are random avatars except the first one... ', + visibility: 'Public', + isDeleted: false, + links: [], + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-05T15:24:41.616Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + { + id: 'CMG-6293-5431-6796', + revision: 1, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-6694-2147-1686', + revision: 21, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: + 'Just created group chat with 5+ users... and only seeing 3 avatars... hm... maybe bug', + visibility: 'Public', + isDeleted: false, + links: [], + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-05T15:23:06.420Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + { + id: 'CMG-7791-1345-9401', + revision: 1, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-6694-2147-1686', + revision: 21, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4262-4296', + name: 'Tania Roche', + revision: 1, + email: 'tania.roche@softwareone.com', + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'he he :) first message on our own SWO chat!', + visibility: 'Public', + isDeleted: false, + links: [], + identity: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-05T15:22:29.602Z', + by: { + id: 'USR-2267-7838', + name: 'Tania Roche', + revision: 1, + }, + }, + updated: {}, + }, + }, + { + id: 'CMG-3243-2050-3705', + revision: 1, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-4768-7226-5802', + revision: 10, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4634-2964', + name: 'MIchal Walczak', + revision: 1, + email: 'michal.walczak@softwareone.com', + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + }, + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: 'in theory works for me \\:P', + visibility: 'Public', + isDeleted: false, + links: [], + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-05T15:03:42.982Z', + by: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + }, + updated: {}, + }, + }, + { + id: 'CMG-0116-4988-6292', + revision: 1, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + sender: { + id: 'CHP-4768-7226-5802', + revision: 10, + chat: { + id: 'CHT-4253-7222-5702', + revision: 1, + type: 'Direct', + }, + contact: { + id: 'CTT-4634-2964', + name: 'MIchal Walczak', + revision: 1, + email: 'michal.walczak@softwareone.com', + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + }, + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + account: { + id: 'ACC-1032-0145', + name: 'SoftwareOne', + icon: '/v1/accounts/accounts/ACC-1032-0145/icon', + revision: 4, + type: 'Operations', + status: 'Enabled', + }, + muted: false, + status: 'Active', + lastReadMessage: { + id: 'CMG-4348-8387-0436', + revision: 1, + content: 'update test', + visibility: 'Public', + isDeleted: false, + }, + unreadMessageCount: 0, + }, + content: + 'When an unknown printer took a galley of type and scrambled it to make a type specimen book.', + visibility: 'Public', + isDeleted: false, + links: [], + identity: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + audit: { + deleted: {}, + madePublic: {}, + madePrivate: {}, + created: { + at: '2026-03-12T15:03:35.336Z', + by: { + id: 'USR-2022-1452', + name: 'Michal Walczak', + icon: '/v1/accounts/users/USR-2022-1452/icon', + revision: 26, + }, + }, + updated: {}, + }, + }, +]; diff --git a/app/src/styles/components/button.ts b/app/src/styles/components/button.ts index cc505dbd..1b585609 100644 --- a/app/src/styles/components/button.ts +++ b/app/src/styles/components/button.ts @@ -1,13 +1,18 @@ -import { Color, BorderRadius, Spacing, Shadow, Typography } from '../tokens'; +import type { ViewStyle } from 'react-native'; + +import { Color, BorderRadius, Spacing, Typography } from '../tokens'; + +const buttonCommon: ViewStyle = { + alignItems: 'center', + justifyContent: 'center', + borderRadius: BorderRadius.round, + paddingVertical: Spacing.spacing2, + paddingHorizontal: Spacing.spacing3, +}; export const buttonStyle = { - common: { - alignItems: 'center', - justifyContent: 'center', - borderRadius: BorderRadius.round, - paddingVertical: Spacing.spacing2, - }, primaryLight: { + ...buttonCommon, backgroundColor: Color.brand.white, color: Color.brand.primary, }, @@ -17,30 +22,22 @@ export const buttonStyle = { fontWeight: Typography.fontWeight.regular, }, primary: { + ...buttonCommon, backgroundColor: Color.brand.primary, - borderRadius: BorderRadius.xs, - paddingVertical: Spacing.spacing2, - paddingHorizontal: Spacing.spacing3, - alignItems: 'center' as const, - justifyContent: 'center' as const, - ...Shadow.sm, + }, + primaryIconOnly: { + paddingVertical: Spacing.spacingSmall6, + paddingHorizontal: Spacing.spacingSmall6, }, secondary: { + ...buttonCommon, backgroundColor: Color.alerts.info1, borderWidth: 1, borderColor: Color.alerts.info1, borderRadius: BorderRadius.md, - paddingVertical: Spacing.spacing2, - paddingHorizontal: Spacing.spacing3, - alignItems: 'center' as const, - justifyContent: 'center' as const, }, authPrimary: { - height: 48, - borderRadius: BorderRadius.xl, - justifyContent: 'center' as const, - alignItems: 'center' as const, - paddingHorizontal: Spacing.spacing3, + ...buttonCommon, backgroundColor: Color.brand.primary, marginBottom: Spacing.spacing2, }, diff --git a/app/src/styles/components/chat.ts b/app/src/styles/components/chat.ts new file mode 100644 index 00000000..fbb2c481 --- /dev/null +++ b/app/src/styles/components/chat.ts @@ -0,0 +1,101 @@ +import type { ViewStyle, TextStyle } from 'react-native'; + +import { BorderRadius, Color, Spacing, Typography } from '../tokens'; + +const AVATAR_OFFSET_HORIZONTAL = 48; +const AVATAR_OFFSET_VERTICAL = 22; + +const containerCommon: ViewStyle = { + flexDirection: 'row', + gap: Spacing.spacing2, + alignItems: 'flex-start', +}; + +const chatTextCommon: TextStyle = { + fontSize: Typography.fontSize.font2, + fontWeight: Typography.fontWeight.regular, + lineHeight: Typography.lineHeight.height3, +}; + +const textContainerCommon: ViewStyle = { + padding: Spacing.spacing2, + borderRadius: BorderRadius.sm, + marginBottom: Spacing.spacing2, +}; + +const infoCommon: ViewStyle = { + flexDirection: 'row', + gap: Spacing.spacing1, + marginBottom: Spacing.spacingSmall4, +}; + +export const chatStyle = { + detailsHeaderContainer: { + paddingBottom: Spacing.spacing0, + }, + detailsHeaderTopRow: { + paddingBottom: Spacing.spacing1, + }, + textInputWrapper: { + flex: 1, + }, + iconContainer: { + paddingBottom: Spacing.spacingSmall6, + }, + inputContainer: { + flexDirection: 'row', + alignItems: 'flex-end', + gap: Spacing.spacing2, + padding: Spacing.spacing2, + borderTopWidth: 1, + borderColor: Color.gray.gray2, + backgroundColor: Color.brand.white, + }, +} as const; + +export const chatMessageStyle = { + avatarWrapper: { + paddingTop: AVATAR_OFFSET_VERTICAL, + }, + messageWrapper: { + flexGrow: 1, + flexShrink: 1, + }, + infoText: { + color: Color.gray.gray4, + }, + own: { + container: { + ...containerCommon, + }, + textContainer: { + ...textContainerCommon, + backgroundColor: Color.brand.primary, + marginLeft: AVATAR_OFFSET_HORIZONTAL, + }, + text: { + ...chatTextCommon, + color: Color.brand.white, + }, + info: { + ...infoCommon, + justifyContent: 'flex-end', + }, + }, + other: { + container: { + ...containerCommon, + }, + textContainer: { + ...textContainerCommon, + backgroundColor: Color.gray.gray2, + }, + text: { + ...chatTextCommon, + color: Color.brand.type, + }, + info: { + ...infoCommon, + }, + }, +} as const; diff --git a/app/src/styles/components/common.ts b/app/src/styles/components/common.ts index 80a669a5..b2162d3c 100644 --- a/app/src/styles/components/common.ts +++ b/app/src/styles/components/common.ts @@ -29,6 +29,9 @@ export const screenStyle = { contentFillContainer: { flexGrow: 1, }, + containerFlex: { + flex: 1, + }, sectionHeader: { fontSize: Typography.fontSize.font2, textTransform: 'uppercase', diff --git a/app/src/styles/components/index.ts b/app/src/styles/components/index.ts index 799301bc..c35d1d3e 100644 --- a/app/src/styles/components/index.ts +++ b/app/src/styles/components/index.ts @@ -18,3 +18,4 @@ export { linkStyle } from './link'; export { textStyle } from './text'; export { chipStyle } from './chip'; export { badgeStyle } from './badge'; +export { chatStyle, chatMessageStyle } from './chat'; diff --git a/app/src/styles/components/input.ts b/app/src/styles/components/input.ts index a6b297f6..4cc8bdb1 100644 --- a/app/src/styles/components/input.ts +++ b/app/src/styles/components/input.ts @@ -4,10 +4,10 @@ export const inputStyle = { container: { borderWidth: 1, borderColor: Color.gray.gray4, - borderRadius: BorderRadius.sm, + borderRadius: BorderRadius.xl, backgroundColor: Color.brand.white, paddingHorizontal: Spacing.spacing2, - paddingVertical: Spacing.spacing2, + paddingVertical: Spacing.spacing1, }, transparent: { borderWidth: 1, @@ -53,4 +53,10 @@ export const inputStyle = { color: Color.brand.danger, marginTop: Spacing.spacing1, }, + inputFullWidth: { + flexGrow: 1, + }, + inputChat: { + maxHeight: 100, + }, } as const; diff --git a/app/src/styles/components/listItem.ts b/app/src/styles/components/listItem.ts index 949e82e8..6e4367a4 100644 --- a/app/src/styles/components/listItem.ts +++ b/app/src/styles/components/listItem.ts @@ -1,8 +1,32 @@ +import type { ViewStyle } from 'react-native'; + import { Color, Spacing, Typography, BorderRadius } from '../tokens'; import { badgeStyle } from './badge'; import { separatorStyle } from './separator'; +const avatarWrapperCommon: ViewStyle = { + borderRadius: 11, + overflow: 'hidden', + marginRight: Spacing.spacingSmall12, +}; + +const detailsHeaderContainerCommon: ViewStyle = { + backgroundColor: Color.brand.white, + paddingHorizontal: Spacing.spacing2, + paddingBottom: Spacing.spacing2, + flexDirection: 'column', + borderBottomWidth: 1, + borderBottomColor: Color.gray.gray2, +}; + +const detailsHeaderTopRowCommon: ViewStyle = { + flexDirection: 'row', + alignItems: 'center', + paddingBottom: 16, + paddingTop: 4, +}; + export const listItemStyle = { container: { flexDirection: 'row', @@ -42,9 +66,12 @@ export const listItemStyle = { }, textAndImage: { avatarWrapper: { - borderRadius: 11, - overflow: 'hidden', - marginRight: Spacing.spacingSmall12, + ...avatarWrapperCommon, + }, + avatarWrapperChat: { + borderRadius: BorderRadius.round, + borderColor: Color.gray.gray2, + borderWidth: 1, }, contentWrapper: { paddingVertical: Spacing.spacing1, @@ -107,18 +134,33 @@ export const listItemStyle = { }, }, detailsHeaderContainer: { - backgroundColor: Color.brand.white, - paddingHorizontal: Spacing.spacing2, - paddingBottom: Spacing.spacing2, - flexDirection: 'column', - borderBottomWidth: 1, - borderBottomColor: separatorStyle.nonOpaque.borderColor, + default: { + ...detailsHeaderContainerCommon, + }, + chat: { + ...detailsHeaderContainerCommon, + paddingBottom: 0, + }, }, detailsHeaderTopRow: { - flexDirection: 'row', - alignItems: 'center', - paddingBottom: 16, - paddingTop: 4, + default: { + ...detailsHeaderTopRowCommon, + }, + chat: { + ...detailsHeaderTopRowCommon, + paddingBottom: 8, + }, + }, + detailsHeaderAvatarWrapper: { + default: { + ...avatarWrapperCommon, + }, + chat: { + ...avatarWrapperCommon, + borderRadius: BorderRadius.round, + borderColor: Color.gray.gray2, + borderWidth: 1, + }, }, detailsHeaderTitle: { fontSize: Typography.fontSize.font5, diff --git a/app/src/types/chat.ts b/app/src/types/chat.ts index 261177e6..4cf53eab 100644 --- a/app/src/types/chat.ts +++ b/app/src/types/chat.ts @@ -2,6 +2,20 @@ import type { ListItemCommonProps } from '@/types/lists'; export type ChatType = 'Direct' | 'Group' | 'Channel' | 'Case'; +export type MessageType = 'own' | 'other'; + +export type Audit = { + created?: { + at: string; + }; +}; + +export type Chat = { + id: string; + type: ChatType; + revision: number; +}; + export type Identity = { id: string; name: string; @@ -23,6 +37,7 @@ export type Account = { icon?: string; type: string; status: string; + revision?: number; }; export type ChatParticipant = { @@ -31,16 +46,23 @@ export type ChatParticipant = { contact?: Contact; account?: Account; unreadMessageCount: number; + revision?: number; +}; + +export type Sender = ChatParticipant & { + chat: Chat; + muted: boolean; + status: string; + lastReadMessage: LastMessage; }; export type LastMessage = { id: string; content: string; - audit?: { - created?: { - at: string; - }; - }; + audit?: Audit; + revision?: number; + visibility?: string; + isDeleted: boolean; }; export type ChatItem = { @@ -66,3 +88,16 @@ export type ListItemChatProps = ListItemCommonProps & { avatars: AvatarItem[]; isVerified: boolean; }; + +export type Message = { + id: string; + revision: number; + chat: Chat; + sender: Sender; + identity: Identity; + content: string; + visibility: 'Public' | 'Private'; + isDeleted: boolean; + links: unknown[]; + audit: Audit; +}; diff --git a/app/src/types/lists.ts b/app/src/types/lists.ts index 58ab6c3f..1656f599 100644 --- a/app/src/types/lists.ts +++ b/app/src/types/lists.ts @@ -15,6 +15,7 @@ export type ListItemWithStatusProps = ListItemCommonProps & { title: string; subtitle?: string; statusText: string; + variant?: 'default' | 'chat'; }; export type ListItemConfig = { diff --git a/app/src/utils/formatting.ts b/app/src/utils/formatting.ts index a4c5cd4c..110ba657 100644 --- a/app/src/utils/formatting.ts +++ b/app/src/utils/formatting.ts @@ -79,15 +79,7 @@ export const getDatePartsForLocale = ( timeZone: 'UTC', }).formatToParts(date); - const timeParts = new Intl.DateTimeFormat(locale, { - hour: '2-digit', - minute: '2-digit', - timeZone: 'UTC', - }).formatToParts(date); - return { - hour: timeParts.find((p) => p.type === 'hour')?.value, - minute: timeParts.find((p) => p.type === 'minute')?.value, weekday: dateParts.find((p) => p.type === 'weekday')?.value, day: dateParts.find((p) => p.type === 'day')?.value, month: dateParts.find((p) => p.type === 'month')?.value, @@ -187,25 +179,26 @@ export const formatDateForChat = (isoDate: string | undefined, locale: string): const diffMs = now.getTime() - date.getTime(); const parts = getDatePartsForLocale(isoDate, locale); + const time = getTime(isoDate); if (!parts) { return EMPTY_STRING; } - const { hour, minute, weekday, day, month, year } = parts; + const { weekday, day, month, year } = parts; - if (!hour || !minute || !weekday || !day || !month || !year) { + if (!weekday || !day || !month || !year) { return EMPTY_STRING; } // within last 24 hours - HH:MM if (diffMs < MS_PER_DAY) { - return `${hour}:${minute}`; + return time; } // within last 7 days - Weekday if (diffMs < DAYS_PER_WEEK * MS_PER_DAY) { - return weekday || EMPTY_STRING; + return weekday; } // within current year - DD Mon From 25edb701f0e7e134bec7711babc38a0acc0d35db Mon Sep 17 00:00:00 2001 From: Tania Roche Date: Fri, 13 Mar 2026 11:30:05 +0000 Subject: [PATCH 2/4] Fixing merge conflicts - adding code back --- app/src/screens/chat/ChatScreen.tsx | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/app/src/screens/chat/ChatScreen.tsx b/app/src/screens/chat/ChatScreen.tsx index c2322518..9f12492e 100644 --- a/app/src/screens/chat/ChatScreen.tsx +++ b/app/src/screens/chat/ChatScreen.tsx @@ -4,14 +4,43 @@ import { useEffect } from 'react'; import { data } from './chatData'; -import { useSignalR } from '@/context/SignalRContext'; import ListViewChat from '@/components/list/ListViewChat'; +import { useSignalR } from '@/context/SignalRContext'; import { ChatItem } from '@/types/chat'; import type { RootStackParamList } from '@/types/navigation'; const ChatScreen = () => { const navigation = useNavigation>(); + const { subscribe, unsubscribe, addMessageListener, isConnected } = useSignalR(); + + // TODO Subscribe to chat entities when connected here just for testing purposes, to be removed + useEffect(() => { + if (!isConnected) { + return; + } + + const subscriptions = [ + { moduleName: 'Helpdesk', entityName: 'Chat' }, + { moduleName: 'Helpdesk', entityName: 'ChatMessage' }, + { moduleName: 'Helpdesk', entityName: 'ChatParticipant' }, + ]; + + void subscribe(subscriptions); + + return () => { + void unsubscribe(subscriptions); + }; + }, [isConnected, subscribe, unsubscribe]); + + useEffect(() => { + const removeListener = addMessageListener(() => { + // Handle incoming messages logic here, to be implemented + }); + + return removeListener; + }, [addMessageListener]); + // TODO: warp into loading / error handling component when API is ready return ( Date: Fri, 13 Mar 2026 11:41:08 +0000 Subject: [PATCH 3/4] Fixing lint errors caused by any type in mock data --- app/src/screens/chat/messageData.ts | 4 +++- app/src/types/chat.ts | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/screens/chat/messageData.ts b/app/src/screens/chat/messageData.ts index c23fcf19..aee0bd88 100644 --- a/app/src/screens/chat/messageData.ts +++ b/app/src/screens/chat/messageData.ts @@ -1,4 +1,6 @@ -export const messages: any = [ +import type { Message } from '@/types/chat'; + +export const messages: Message[] = [ { id: 'CMG-4912-0427-1898', revision: 1, diff --git a/app/src/types/chat.ts b/app/src/types/chat.ts index 4cf53eab..a867cbb9 100644 --- a/app/src/types/chat.ts +++ b/app/src/types/chat.ts @@ -7,7 +7,12 @@ export type MessageType = 'own' | 'other'; export type Audit = { created?: { at: string; + by: unknown; }; + updated?: unknown; + deleted?: unknown; + madePublic?: unknown; + madePrivate?: unknown; }; export type Chat = { From 0126d034d95b6f9f6d4069aee019e049a588158d Mon Sep 17 00:00:00 2001 From: Tania Roche Date: Fri, 13 Mar 2026 14:22:53 +0000 Subject: [PATCH 4/4] Addressing PR comments --- app/package-lock.json | 38 +++++++++++++++---- .../chat/ChatConversationFooter.tsx | 5 ++- app/src/i18n/en/chat.json | 5 +++ app/src/i18n/en/index.ts | 4 ++ app/src/styles/components/listItem.ts | 10 ++--- 5 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 app/src/i18n/en/chat.json diff --git a/app/package-lock.json b/app/package-lock.json index 17dbc2c3..592382a5 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -2044,6 +2044,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2055,6 +2056,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2065,6 +2067,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -5084,6 +5087,7 @@ "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -5868,6 +5872,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -6445,6 +6450,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6458,6 +6464,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6471,6 +6478,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6484,6 +6492,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6497,6 +6506,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6510,6 +6520,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6523,6 +6534,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6536,6 +6548,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6549,6 +6562,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6562,6 +6576,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6575,6 +6590,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6588,6 +6604,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6601,6 +6618,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6614,6 +6632,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6627,6 +6646,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6640,6 +6660,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -6656,6 +6677,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6669,6 +6691,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6682,6 +6705,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7203,13 +7227,6 @@ } } }, - "node_modules/@wdio/reporter/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, "node_modules/@wdio/reporter/node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -7226,6 +7243,13 @@ "node": ">=4.2.0" } }, + "node_modules/@wdio/reporter/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, "node_modules/@wdio/runner": { "version": "9.24.0", "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-9.24.0.tgz", diff --git a/app/src/components/chat/ChatConversationFooter.tsx b/app/src/components/chat/ChatConversationFooter.tsx index 1cb5819e..ebb77703 100644 --- a/app/src/components/chat/ChatConversationFooter.tsx +++ b/app/src/components/chat/ChatConversationFooter.tsx @@ -1,4 +1,5 @@ import { OutlinedIcons } from '@assets/icons'; +import { useTranslation } from 'react-i18next'; import { View, TextInput, StyleSheet } from 'react-native'; import OutlinedIcon from '@/components/common/OutlinedIcon'; @@ -11,6 +12,8 @@ type Props = { }; const ChatConversationFooter = ({ value, onChangeText, onSend }: Props) => { + const { t } = useTranslation(); + return ( @@ -23,7 +26,7 @@ const ChatConversationFooter = ({ value, onChangeText, onSend }: Props) => {