-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
1,557 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
module.exports = { | ||
parser: '@typescript-eslint/parser', // Specifies the ESLint parser | ||
extends: [ | ||
'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react | ||
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin | ||
+'prettier/@typescript-eslint', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier | ||
+'plugin:prettier/recommended', // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. | ||
], | ||
parserOptions: { | ||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features | ||
sourceType: 'module', // Allows for the use of imports | ||
ecmaFeatures: { | ||
jsx: true, // Allows for the parsing of JSX | ||
}, | ||
}, | ||
rules: { | ||
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs | ||
// e.g. "@typescript-eslint/explicit-function-return-type": "off", | ||
}, | ||
settings: { | ||
react: { | ||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use | ||
}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
semi: true, | ||
trailingComma: 'all', | ||
singleQuote: true, | ||
printWidth: 120, | ||
tabWidth: 4, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
import React from 'react'; | ||
import { render } from '@testing-library/react'; | ||
import App from './App'; | ||
import styled from 'styled-components'; | ||
|
||
test('renders learn react link', () => { | ||
const { getByText } = render(<App />); | ||
const linkElement = getByText(/learn react/i); | ||
expect(linkElement).toBeInTheDocument(); | ||
const { getByText } = render(<App />); | ||
const linkElement = getByText(/learn react/i); | ||
expect(linkElement).toBeInTheDocument(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,51 @@ | ||
import React from 'react'; | ||
import logo from './logo.svg'; | ||
import React, { useEffect, useState } from 'react'; | ||
import 'normalize.css'; | ||
import './App.css'; | ||
import styled from 'styled-components/macro'; | ||
|
||
function App() { | ||
return ( | ||
<div className="App"> | ||
<header className="App-header"> | ||
<img src={logo} className="App-logo" alt="logo" /> | ||
<p> | ||
Edit <code>src/App.tsx</code> and save to reload. | ||
</p> | ||
<a | ||
className="App-link" | ||
href="https://reactjs.org" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
Learn React | ||
</a> | ||
</header> | ||
</div> | ||
); | ||
} | ||
//Components | ||
import { LeftSection } from './components/ContactsSection/Side'; | ||
import { RightSection } from './components/ConversationSection/ConversationView'; | ||
|
||
//Interfaces | ||
import { Conversation } from './api/conversations'; | ||
|
||
//Services | ||
import * as conversationService from './api/conversations.service'; | ||
|
||
const App: React.FunctionComponent = () => { | ||
const [loading, setLoading] = useState(false); | ||
const [loadedConversations, setLoadedConversations] = useState<Conversation[]>(); | ||
const [selectedConversation, setSelectedConversation] = useState<Conversation>(); | ||
useEffect(() => { | ||
async function loadConversations() { | ||
const conversations = await conversationService.getConversations(); | ||
console.log(conversations); | ||
setLoadedConversations(conversations); | ||
setSelectedConversation(conversations[0]); | ||
const selectedConversation = React.createContext<Conversation>(conversations[0]); | ||
setLoading(false); | ||
} | ||
|
||
setLoading(true); | ||
loadConversations(); | ||
}, []); | ||
return ( | ||
//TODO: make styled component | ||
<div className="App"> | ||
<Root> | ||
{loadedConversations ? <LeftSection conversations={loadedConversations} /> : 'Loading...'} | ||
|
||
{selectedConversation ? <RightSection {...selectedConversation} /> : 'Loading'} | ||
</Root> | ||
</div> | ||
); | ||
}; | ||
|
||
export default App; | ||
|
||
const Root = styled.div` | ||
display: flex; | ||
overflow: hidden; | ||
max-height: 100vh; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import * as faker from 'faker'; | ||
import { Participant, Conversation, Message } from './conversations'; | ||
|
||
const KEY = 'conversations'; | ||
let conversationsList: Conversation[] = []; | ||
|
||
export { getConversations, getById }; | ||
|
||
const getConversations = () => { | ||
const conversations: null | string = localStorage.getItem(KEY); | ||
if (conversations) { | ||
conversationsList = JSON.parse(conversations); | ||
return Promise.resolve<Conversation[]>(JSON.parse(conversations)); | ||
} else { | ||
const conversationsList = _createConversations(); | ||
localStorage.setItem(KEY, JSON.stringify(conversationsList)); | ||
return Promise.resolve<Conversation[]>(conversationsList); | ||
} | ||
}; | ||
|
||
const getById = (conversationId: string) => { | ||
const conversationIndex = conversationsList.findIndex((conversation) => conversationId === conversation.id); | ||
if (conversationIndex !== -1) { | ||
return Promise.resolve<Conversation>(conversationsList[conversationIndex]); | ||
} else { | ||
return Promise.reject('Cannot find conversation'); | ||
} | ||
}; | ||
|
||
const _createConversations = () => { | ||
const createMessages = (phoneNumbers: string[]) => { | ||
const readAtOptions = [faker.date.recent(), null]; | ||
const contentOptions = [faker.lorem.sentences(Math.floor(Math.random() * 6 + 1)), faker.image.imageUrl()]; | ||
return Array(30) | ||
.fill(null) | ||
.map(() => { | ||
const message: Message = { | ||
readAt: faker.random.arrayElement(readAtOptions), | ||
sentAt: faker.date.recent(), | ||
by: phoneNumbers[Math.floor(Math.random() * phoneNumbers.length)], | ||
content: faker.random.arrayElement(contentOptions), | ||
type: '', | ||
}; | ||
if (message.content.includes('http://lorempixel.com/')) { | ||
message.type = 'image'; | ||
} else { | ||
message.type = 'text'; | ||
} | ||
return message; | ||
}); | ||
}; | ||
|
||
const createParticipants = () => { | ||
const participantsAmount = Math.floor(Math.random() * 3 + 1); | ||
return Array(participantsAmount) | ||
.fill(null) | ||
.map(() => ({ | ||
phoneNumber: faker.phone.phoneNumber(), | ||
firstName: faker.name.firstName(), | ||
lastName: faker.name.lastName(), | ||
imageUrl: faker.image.avatar(), | ||
})); | ||
}; | ||
const createConversation = () => { | ||
const participants: Participant[] = createParticipants(); | ||
const messages: Message[] = createMessages(participants.map((participant) => participant.phoneNumber)); | ||
const conversation: Conversation = { | ||
id: faker.random.uuid(), | ||
title: faker.name.firstName(), | ||
imageUrl: faker.image.avatar(), | ||
participants: participants, | ||
messages: messages, | ||
}; | ||
console.log(conversation); | ||
return conversation; | ||
}; | ||
const conversations = Array(20) | ||
.fill(null) | ||
.map(() => createConversation()); | ||
console.log(conversations); | ||
return conversations; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
export interface ConversationsList { | ||
conversations: Conversation[]; | ||
} | ||
|
||
export interface Conversation { | ||
id: string; | ||
title: string; | ||
imageUrl: string; | ||
messages: Message[]; | ||
participants: Participant[]; | ||
} | ||
|
||
export interface ConversationPreview { | ||
imageUrl: string; | ||
title: string; | ||
unreadCount: number; | ||
time: Date; | ||
content: string; | ||
} | ||
export interface Message { | ||
readAt: Date | null; | ||
sentAt: Date; | ||
by: string; //phone number | ||
content: string; | ||
type: string; | ||
} | ||
|
||
export interface Participant { | ||
phoneNumber: string; | ||
firstName: string; | ||
lastName: string; | ||
imageUrl: string; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
|
||
interface AvatarProps { | ||
imageUrl: string; | ||
size: string; | ||
} | ||
export const Avatar = styled.div<AvatarProps>` | ||
background-image: url(${(props) => props.imageUrl}); | ||
width: ${(props) => props.size}; | ||
border-radius: 100vh; | ||
height: ${(props) => props.size}; | ||
background-size: cover; | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from 'react'; | ||
import styled from 'styled-components'; | ||
|
||
//Components | ||
import { ConversationListItem } from './ConversationListItem'; | ||
|
||
//Interfaces | ||
import { Conversation } from '../../api/conversations'; | ||
|
||
interface ConversationListProps { | ||
conversations: Conversation[]; | ||
} | ||
export const ConversationList: React.FC<ConversationListProps> = ({ conversations }) => { | ||
const ConversationList = conversations.map((conversation, index) => { | ||
let unreadCount = conversation.messages.reduce((totalUnread, message) => { | ||
return message.readAt ? totalUnread : totalUnread + 1; //if readAt === null then unread++ | ||
}, 0); | ||
|
||
const lastMessage = conversation.messages[conversation.messages.length - 1]; | ||
|
||
// text > message preview | ||
return ( | ||
<ConversationListItem | ||
key={index} | ||
imageUrl={conversation.imageUrl} | ||
title={conversation.title} | ||
unreadCount={unreadCount} | ||
content={lastMessage.content} | ||
time={lastMessage.sentAt} | ||
/> | ||
); | ||
}); | ||
return <Root>{ConversationList}</Root>; | ||
}; | ||
|
||
const Root = styled.div` | ||
overflow-y: scroll; | ||
height: 100vh; | ||
width: 415px; | ||
`; |
Oops, something went wrong.