init commit,
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { IonIcon } from "@ionic/react";
|
||||
import { checkmarkDone, star } from "ionicons/icons";
|
||||
|
||||
export const ChatBottomDetails = ({ message }) => (
|
||||
<span className="chat-bottom-details" id={`chatTime_${message.id}`}>
|
||||
<span>{message.date}</span>
|
||||
{message.sent && (
|
||||
<IonIcon
|
||||
icon={checkmarkDone}
|
||||
color="primary"
|
||||
style={{ fontSize: "0.8rem" }}
|
||||
/>
|
||||
)}
|
||||
{message.starred && <IonIcon icon={star} />}
|
||||
</span>
|
||||
);
|
@@ -0,0 +1,48 @@
|
||||
import { IonIcon, IonItem } from "@ionic/react";
|
||||
import { checkmarkDone } from "ionicons/icons";
|
||||
import { ContactStore } from "../store";
|
||||
import { getContacts } from "../store/Selectors";
|
||||
|
||||
const ChatItem = ({ chat }) => {
|
||||
const contacts = ContactStore.useState(getContacts);
|
||||
const { chats, contact_id } = chat;
|
||||
const { read, date, preview, received } = chats[chats.length - 1];
|
||||
const contact = contacts.filter((c) => c.id === contact_id)[0];
|
||||
const notificationCount = chats.filter((chat) => chat.read === false).length;
|
||||
|
||||
return (
|
||||
<div className="chat-row" id="chat-row">
|
||||
<img src={contact.avatar} alt="avatar" />
|
||||
|
||||
<IonItem
|
||||
className="chat-content-container"
|
||||
routerLink={`/view-chat/${contact.id}`}
|
||||
detail={false}
|
||||
>
|
||||
<div className="chat-content">
|
||||
<div className="chat-name-date">
|
||||
<h2>{contact.name}</h2>
|
||||
</div>
|
||||
<p className="ion-text-wrap">
|
||||
{read && received && (
|
||||
<IonIcon icon={checkmarkDone} color="primary" />
|
||||
)}
|
||||
{preview}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="chat-details">
|
||||
<p className={`chat-date ${notificationCount > 0 && "chat-unread"}`}>
|
||||
{date}
|
||||
</p>
|
||||
|
||||
{notificationCount > 0 && (
|
||||
<div className="chat-notification">{notificationCount}</div>
|
||||
)}
|
||||
</div>
|
||||
</IonItem>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatItem;
|
@@ -0,0 +1,20 @@
|
||||
const Quote = ({ message, contact, repliedMessage }) => (
|
||||
<div className="in-chat-reply-to-container">
|
||||
<h1>{contact.name}</h1>
|
||||
<p>{repliedMessage.preview}</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ChatRepliedQuote = ({ message, contact, repliedMessage }) => {
|
||||
if (message.reply && repliedMessage) {
|
||||
return (
|
||||
<Quote
|
||||
message={message}
|
||||
contact={contact}
|
||||
repliedMessage={repliedMessage}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from "@ionic/react";
|
||||
import { ContactStore } from "../store";
|
||||
import { getContacts } from "../store/Selectors";
|
||||
|
||||
import "./ContactModal.scss";
|
||||
|
||||
const ContactModal = ({ close }) => {
|
||||
const contacts = ContactStore.useState(getContacts);
|
||||
|
||||
return (
|
||||
<div style={{ height: "100%" }}>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>New Chat</IonTitle>
|
||||
<IonButtons slot="end">
|
||||
<IonButton fill="clear" onClick={close}>
|
||||
Cancel
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent>
|
||||
<IonList>
|
||||
{contacts.map((contact) => {
|
||||
return (
|
||||
<IonItem
|
||||
key={`contact_${contact.id}`}
|
||||
lines="full"
|
||||
className="contact-item"
|
||||
>
|
||||
<img src={contact.avatar} alt="contact avatar" />
|
||||
<IonLabel>
|
||||
<h1>{contact.name}</h1>
|
||||
<p>Available</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
);
|
||||
})}
|
||||
</IonList>
|
||||
</IonContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactModal;
|
@@ -0,0 +1,14 @@
|
||||
.contact-item {
|
||||
img {
|
||||
border-radius: 500px;
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
||||
ion-label {
|
||||
h1 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
CreateAnimation,
|
||||
IonButton,
|
||||
IonCol,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonRow,
|
||||
} from "@ionic/react";
|
||||
import { closeCircleOutline } from "ionicons/icons";
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
const ReplyTo = ({
|
||||
contact,
|
||||
replyToMessage = false,
|
||||
replyToAnimationRef,
|
||||
setReplyToMessage,
|
||||
messageSent,
|
||||
}) => {
|
||||
const [cancellingReplyTo, setCancellingReplyTo] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
messageSent && cancelReplyTo();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [messageSent]);
|
||||
|
||||
const slideAnimation = {
|
||||
property: "transform",
|
||||
fromValue: "translateY(100px)",
|
||||
toValue: "translateY(0px)",
|
||||
};
|
||||
|
||||
const replyToAnimation = {
|
||||
duration: 300,
|
||||
direction: !cancellingReplyTo ? "normal" : "reverse",
|
||||
iterations: "1",
|
||||
fromTo: [slideAnimation],
|
||||
easing: "ease-in-out",
|
||||
};
|
||||
|
||||
// Cancel the reply-to
|
||||
const cancelReplyTo = async () => {
|
||||
setCancellingReplyTo(true);
|
||||
await replyToAnimationRef.current.animation.play();
|
||||
setCancellingReplyTo(false);
|
||||
setReplyToMessage(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<CreateAnimation ref={replyToAnimationRef} {...replyToAnimation}>
|
||||
<IonRow className="ion-align-items-center chat-reply-to-row" id="replyTo">
|
||||
<IonCol size="10" className="chat-reply-to-container">
|
||||
<IonLabel className="chat-reply-to-name">{contact}</IonLabel>
|
||||
<IonLabel className="chat-reply-to-message">
|
||||
{replyToMessage.preview}
|
||||
</IonLabel>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="1">
|
||||
<IonButton fill="clear" onClick={cancelReplyTo}>
|
||||
<IonIcon size="large" icon={closeCircleOutline} color="primary" />
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</CreateAnimation>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplyTo;
|
@@ -0,0 +1,22 @@
|
||||
import { Route } from "react-router-dom";
|
||||
|
||||
const SubPages = (props) => {
|
||||
return (
|
||||
<>
|
||||
{props.routes.map((route, i) => {
|
||||
const RouteComponent = route.component;
|
||||
|
||||
return (
|
||||
<Route
|
||||
key={i}
|
||||
path={route.path}
|
||||
render={(props) => <RouteComponent {...props} />}
|
||||
exact={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubPages;
|
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs,
|
||||
IonRouterOutlet,
|
||||
} from "@ionic/react";
|
||||
import { Redirect, Route } from "react-router-dom";
|
||||
|
||||
const Tabs = (props) => {
|
||||
return (
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
{props.tabs.map((tab, i) => {
|
||||
const TabComponent = tab.component;
|
||||
|
||||
if (tab.isTab) {
|
||||
return (
|
||||
<Route
|
||||
key={`tab_route_${i}`}
|
||||
path={tab.path}
|
||||
render={(props) => <TabComponent {...props} />}
|
||||
exact={true}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Route
|
||||
key={`child_tab_route_${i}`}
|
||||
path={tab.path}
|
||||
render={(props) => <TabComponent {...props} />}
|
||||
exact={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</IonRouterOutlet>
|
||||
|
||||
<IonTabBar slot={props.position}>
|
||||
{props.tabs.map((tab, i) => {
|
||||
if (tab.isTab) {
|
||||
return (
|
||||
<IonTabButton
|
||||
key={`tab_button_${i + 1}`}
|
||||
tab={`tab_${i + 1}`}
|
||||
href={tab.path}
|
||||
>
|
||||
<IonIcon icon={tab.icon} />
|
||||
{tab.label && <IonLabel>{tab.label}</IonLabel>}
|
||||
</IonTabButton>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tabs;
|
Reference in New Issue
Block a user