Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
setupFilesAfterFramework: ['./src/setupTests.ts'],
};
33,282 changes: 19,086 additions & 14,196 deletions package-lock.json

Large diffs are not rendered by default.

64 changes: 34 additions & 30 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,40 @@
"name": "postbox",
"version": "0.1.0",
"private": true,
"engines": {
"node": ">=18"
},
"dependencies": {
"@babel/core": "7.22.9",
"@babel/plugin-syntax-flow": "7.14.5",
"@babel/plugin-transform-react-jsx": "7.14.9",
"@testing-library/dom": "7.21.4",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "11.2.7",
"@testing-library/user-event": "12.8.3",
"@types/jest": "26.0.24",
"@types/lodash": "4.14.195",
"@types/node": "12.20.55",
"@types/react": "16.14.60",
"@types/react-dom": "16.9.24",
"@types/react-redux": "7.1.20",
"@types/react-router-dom": "^5.3.2",
"@types/react-test-renderer": "16.9.12",
"antd": "4.24.16",
"lodash": "^4.17.21",
"puppeteer": "^22.13.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-redux": "7.2.9",
"react-router-dom": "5.3.4",
"react-scripts": "5.0.0",
"react-test-renderer": "17.0.2",
"redux": "4.2.1",
"redux-saga": "1.2.2",
"typescript": "4.7.4",
"web-vitals": "^0.2.4",
"workbox-core": "^7.1.0",
"@babel/core": "7.29.0",
"@babel/plugin-syntax-flow": "7.28.6",
"@babel/plugin-transform-react-jsx": "7.28.6",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.2",
"@testing-library/user-event": "14.6.1",
"@types/jest": "30.0.0",
"@types/lodash": "4.17.24",
"@types/node": "25.7.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-redux": "7.1.34",
"@types/react-router-dom": "^5.3.3",
"@types/react-test-renderer": "19.1.0",
"ajv": "^8.20.0",
"antd": "6.3.7",
"lodash": "^4.18.1",
"puppeteer": "^24.43.1",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-redux": "9.2.0",
"react-router-dom": "^7.15.0",
"react-scripts": "5.0.1",
"react-test-renderer": "^19.2.6",
"redux": "5.0.1",
"redux-saga": "1.4.3",
"typescript": "6.0.3",
"web-vitals": "^5.2.0",
"workbox-core": "^7.4.1",
"yield": "^0.0.6-8"
},
"scripts": {
Expand All @@ -58,4 +62,4 @@
"last 1 safari version"
]
}
}
}
46 changes: 13 additions & 33 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,28 @@
import React from 'react';
import { Provider } from 'react-redux';
import { HashRouter as Router, Redirect, Route, withRouter, Switch, BrowserRouter } from 'react-router-dom';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import './App.css';
import Dashboard from './pages/dashboard';
import Login from './pages/login';
import {configureStore} from './store';
import {} from './components/emails/view'

const store = configureStore();

function App() {

//If already login then redirect to dashboard
return (
<Provider store={store}>
<div className="App">
<BrowserRouter>
{/* <Router> */}
<Switch>
<Route
exact
path="/"
render={props => {
return <Redirect {...props} to="/login" />;
}}
/>
<Route
exact
path="/dashboard"
render={props => {
return <Redirect {...props} to="/dashboard/inbox/list" />;
}}
/>
<Route exact path="/login">
<Login />
</Route>
<Route path="/dashboard/:emailAction" >
<Dashboard />
</Route>
</Switch>
{/* </Router> */}
</BrowserRouter>
</div>
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate to="/login" replace />} />
<Route path="/dashboard" element={<Navigate to="/dashboard/inbox/list" replace />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard/*" element={<Dashboard />} />
</Routes>
</BrowserRouter>
</div>
</Provider>
);
}

export default App;
export default App;
98 changes: 47 additions & 51 deletions src/components/emails/compose-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,67 @@
import React, { Dispatch } from 'react';
import React, { Dispatch } from 'react';
import { Select, Form, Input, Button } from 'antd';
import {EmailCreation, User} from '../../types';
import { EmailCreation, User } from '../../types';
import { connect } from 'react-redux';
import {sendEmail} from '../../store/emails/actions';
import {ActionTypes} from '../../store/emails/types';

import { sendEmail } from '../../store/emails/actions';
import { ActionTypes } from '../../store/emails/types';

interface ComposeModal {
sendEmail : (data:EmailCreation) => void,
modalInfo ?: any,
emailSuccessMessage : string,
currentUser : User
sendEmail: (data: EmailCreation) => void;
modalInfo?: any;
emailSuccessMessage: string;
currentUser: User;
}

function ComposeModal({sendEmail, currentUser}:ComposeModal) {
const {email} = currentUser;

const onFinish = (values:EmailCreation) => {
if(!values.body.trim()){
if(!window.confirm("Do you want to proceed without body ?")){
function ComposeModal({ sendEmail, currentUser }: ComposeModal) {
// ✅ Guard against undefined currentUser during initial render
const email = currentUser?.email;

const onFinish = (values: EmailCreation) => {
if (!values.body?.trim()) {
if (!window.confirm('Do you want to proceed without body ?')) {
return false;
}
}
sendEmail({...values, sender : email});
// ✅ Only send if email is available
if (email) {
sendEmail({ ...values, sender: email });
}
};

return (
<Form name="compose-modal-form" onFinish={(values:EmailCreation) => onFinish(values)}>
<Form.Item label="To" name={'to'} rules={[{ required: true }]}>
<Select mode="tags" style={{ width: '100%' }} placeholder="Tags Mode">
</Select>
</Form.Item>
<Form.Item label="Cc" name={'cc'}>
<Select mode="tags" style={{ width: '100%' }} placeholder="Tags Mode">
</Select>
</Form.Item>
<Form.Item name={'subject'} label="Subject" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name={'body'} label="">
<Input.TextArea />
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit" id="send-email-action">
Submit
</Button>
</Form.Item>
</Form>)
<Form name="compose-modal-form" onFinish={(values: EmailCreation) => onFinish(values)}>
<Form.Item label="To" name="to" rules={[{ required: true }]}>
<Select mode="tags" style={{ width: '100%' }} placeholder="Tags Mode" />
</Form.Item>
<Form.Item label="Cc" name="cc">
<Select mode="tags" style={{ width: '100%' }} placeholder="Tags Mode" />
</Form.Item>
<Form.Item name="subject" label="Subject" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="body" label="">
<Input.TextArea />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" id="send-email-action">
Submit
</Button>
</Form.Item>
</Form>
);
}


const mapStateToProps = ({email, auth}:any) => {

const mapStateToProps = ({ email, auth }: any) => {
return {
currentUser : auth.currentUser,
emailSuccessMessage : email.successMessage
}
}
currentUser: auth.currentUser,
emailSuccessMessage: email.successMessage,
};
};

const mapDispatchToProps = (dispatch:Dispatch<ActionTypes>) => {
const mapDispatchToProps = (dispatch: Dispatch<ActionTypes>) => {
return {
sendEmail : (data:EmailCreation) => dispatch(sendEmail(data)),
sendEmail: (data: EmailCreation) => dispatch(sendEmail(data)),
};
};


export default connect(
mapStateToProps,
mapDispatchToProps
)(ComposeModal);
export default connect(mapStateToProps, mapDispatchToProps)(ComposeModal);
104 changes: 51 additions & 53 deletions src/components/emails/list.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,66 @@
import React, { useState, Dispatch, useEffect } from 'react';
import React, { useState, Dispatch } from 'react';
import { Table, Button } from 'antd';
import { Redirect, useParams, useHistory } from 'react-router-dom';
import { useParams, useNavigate } from 'react-router-dom';
import { connect } from 'react-redux';
import {includes} from 'lodash';
import { includes } from 'lodash';
import './list.css';
import {ParamTypes} from '../../types';
import {emailListColumns, emailListFirstColumn} from './config';
import {deleteEmail} from '../../store/emails/actions';
import {ActionTypes} from '../../store/emails/types';
import { emailListColumns, emailListFirstColumn } from './config';
import { deleteEmail } from '../../store/emails/actions';
import { ActionTypes } from '../../store/emails/types';

function EmailList({emails, deleteEmail}:any) {
const rhist:any = useHistory();
let { emailAction } = useParams<ParamTypes>();
const firstColumn = emailListFirstColumn[emailAction] || {};
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const onSelectChange = (selectedRowKeys:any) => {
setSelectedRowKeys(selectedRowKeys);
}
function EmailList({ emails, deleteEmail }: any) {
const navigate = useNavigate();
let { emailAction } = useParams<{ emailAction: string }>();
const firstColumn = emailListFirstColumn[emailAction!] || {};
const [selectedRowKeys, setSelectedRowKeys] = useState([]);

const deleteAnEmail = () => {
const emailList = emails
.filter((email:any, index:number) => includes(selectedRowKeys, index))
.map(({emailUuid}:any) => emailUuid)
deleteEmail(emailList);
setSelectedRowKeys([]);
}


const onSelectChange = (selectedRowKeys: any) => {
setSelectedRowKeys(selectedRowKeys);
};

const deleteAnEmail = () => {
const emailList = emails
.filter((email: any, index: number) => includes(selectedRowKeys, index))
.map(({ emailUuid }: any) => emailUuid);
deleteEmail(emailList);
setSelectedRowKeys([]);
};

return (
<>
<Button onClick={() => deleteAnEmail()} className='delete-action' disabled={!selectedRowKeys.length}>Delete</Button>
<Table rowSelection={{
selectedRowKeys,
onChange: onSelectChange,
}}
rowClassName = {({readClass}, index) => (readClass || "")}
rowKey={({index}) => index}
onRow={({emailUuid}, rowIndex) => {
return {
onClick: event => rhist.push(`/dashboard/${emailAction}/view/${emailUuid}`)
}
}}
columns={[firstColumn, ...emailListColumns]} dataSource={emails} />
</>
);
return (
<>
<Button
onClick={() => deleteAnEmail()}
className='delete-action'
disabled={!selectedRowKeys.length}
>
Delete
</Button>
<Table
rowSelection={{ selectedRowKeys, onChange: onSelectChange }}
rowClassName={({ readClass }, index) => readClass || ""}
rowKey={({ index }) => index}
onRow={({ emailUuid }, rowIndex) => ({
onClick: event => navigate(`/dashboard/${emailAction}/view/${emailUuid}`)
})}
columns={[firstColumn, ...emailListColumns]}
dataSource={emails}
/>
</>
);
}

const mapDispatchToProps = (dispatch: Dispatch<ActionTypes>) => {
return {
deleteEmail: (emailUuids: string[]) => dispatch(deleteEmail(emailUuids)),
};
};


const mapDispatchToProps = (dispatch:Dispatch<ActionTypes>) => {
const mapStateToProps = ({ email, auth }: any) => {
return {
deleteEmail : (emailUuids:string[]) => dispatch(deleteEmail(emailUuids)),
currentUser: auth.currentUser,
emails: email.emails,
};
};

const mapStateToProps = ({email, auth}:any) => {
return {
currentUser : auth.currentUser,
emails : email.emails,
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(EmailList);
export default connect(mapStateToProps, mapDispatchToProps)(EmailList);
2 changes: 1 addition & 1 deletion src/components/emails/ops.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {ActionTypes, ModalProps} from '../../store/modal/types';

interface EmailOps {
openModal : (options:ModalProps) => void,
selectedAction : string
selectedAction: string | undefined;
}

function EmailOps({openModal, selectedAction}: EmailOps) {
Expand Down
Loading