init commit,

This commit is contained in:
louiscklaw
2025-05-28 09:55:51 +08:00
commit efe70ceb69
8042 changed files with 951668 additions and 0 deletions

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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 "";
}
};

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;