diff --git a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx index dd20c3c2d..a85eacd2c 100644 --- a/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx +++ b/packages/examples/sdk-frontend-react/src/app/ChatUITest/ChatViewComponent.tsx @@ -15,7 +15,7 @@ const ChatViewComponentTest = () => {

Chat UI Test page

{/* {console.log('in close')}} /> */} - console.log("BOIIII RETURNNNSSSSS")} chatId='4ac5ab85c9c3d57adbdf2dba79357e56b2f9ef0256befe750d9f93af78d2ca68' limit={10} isConnected={true} /> + console.log("BOIIII RETURNNNSSSSS")} chatId='5b3dba72949b77c48fe12cf87ffab91309ed9d1d78dec1db17c254020d2ffe2b' limit={10} isConnected={true} /> ); diff --git a/packages/uiweb/package.json b/packages/uiweb/package.json index 6645e2d69..7dfa3a9c3 100644 --- a/packages/uiweb/package.json +++ b/packages/uiweb/package.json @@ -25,6 +25,8 @@ "livekit-client": "^1.13.3", "moment": "^2.29.4", "react-icons": "^4.10.1", + "react-easy-crop": "^4.1.4", + "react-image-file-resizer": "^0.4.7", "react-toastify": "^9.1.3", "react-twitter-embed": "^4.0.4", "uuid": "^9.0.1" @@ -33,6 +35,6 @@ "@pushprotocol/restapi": "^1.2.15", "@pushprotocol/socket": "^0.5.0", "react": ">=16.8.0", - "styled-components": "^5.3.5" + "styled-components": "^6.0.8" } } diff --git a/packages/uiweb/src/lib/components/chat/ChatProfile/GroupInfoModal.tsx b/packages/uiweb/src/lib/components/chat/ChatProfile/GroupInfoModal.tsx index e1c8d3f62..e738232d1 100644 --- a/packages/uiweb/src/lib/components/chat/ChatProfile/GroupInfoModal.tsx +++ b/packages/uiweb/src/lib/components/chat/ChatProfile/GroupInfoModal.tsx @@ -31,17 +31,11 @@ import addIcon from '../../../icons/addicon.svg'; import DismissAdmin from '../../../icons/dismissadmin.svg'; import AddAdmin from '../../../icons/addadmin.svg'; import Remove from '../../../icons/remove.svg'; -import { shortenText } from '../../../helpers'; +import { copyToClipboard, shortenText } from '../../../helpers'; import TokenGatedIcon from '../../../icons/TokenGatedIcon.svg'; import ConditionsComponent from '../CreateGroup/ConditionsComponent'; -import { ConditionArray } from '../exportedTypes'; import { ACCESS_TYPE_TITLE, OPERATOR_OPTIONS_INFO } from '../constants'; -import * as PushAPI from '@pushprotocol/restapi'; -import { Rule } from '../types'; -import { - GroupRulesType, - getRuleInfo, -} from '../helpers/getRulesToCondtionArray'; +import { getRuleInfo } from '../helpers/getRulesToCondtionArray'; const UPDATE_KEYS = { REMOVE_MEMBER: 'REMOVE_MEMBER', @@ -95,10 +89,14 @@ const PendingMembers = ({ {showPendingRequests && ( -
{groupInfo?.pendingMembers && @@ -120,7 +118,7 @@ const PendingMembers = ({ /> ))} -
+ )} ); @@ -148,7 +146,7 @@ export const ConditionsInformation = ({ return null; }; - console.log(groupRules); + return (
(
- + {ACCESS_TYPE_TITLE[key as keyof typeof ACCESS_TYPE_TITLE]?.heading} - {getOperator(key as keyof typeof groupRules) ? ( + { OPERATOR_OPTIONS_INFO[ @@ -193,14 +193,17 @@ export const ConditionsInformation = ({ } + ) : null} (false); const [memberList, setMemberList] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [copyText, setCopyText] = useState(''); const [selectedMemberAddress, setSelectedMemberAddress] = useState< string | null >(null); @@ -485,13 +489,56 @@ const GroupInformation = ({ return (
+ + + Chat ID + +
{ + copyToClipboard(groupInfo?.chatId); + setCopyText('copied'); + }} + onMouseEnter={() => { + setCopyText('click to copy'); + }} + onMouseLeave={() => { + setCopyText(''); + }} + > + + {shortenText(groupInfo?.chatId, 8, true)} + + {!!copyText && ( + + {copyText} + + )} +
+
Group Description {groupInfo?.groupDescription} @@ -557,36 +604,35 @@ const GroupInformation = ({ )}
-
-
- {groupInfo?.members && - groupInfo?.members?.length > 0 && - groupInfo?.members.map((item, index) => ( - - ))} -
-
+ {groupInfo?.members && + groupInfo?.members?.length > 0 && + groupInfo?.members.map((item, index) => ( + + ))} + {showAddMoreWalletModal && ( (null); - const { updateGroup } = useUpdateGroup(); const isMobile = useMediaQuery(device.mobileL); const dropdownRef = useRef(null); useClickAway(dropdownRef, () => setSelectedMemberAddress(null)); - type UpdateGroupType = { - adminList: Array; - memberList: Array; - }; - - const handleUpdateGroup = async (options: UpdateGroupType) => { - const { adminList, memberList } = options || {}; - const updateResponse = await updateGroup({ - groupInfo, - memberList, - adminList, - }); - return { updateResponse }; - }; - const onClose = (): void => { setModal(false); }; @@ -706,11 +736,16 @@ export const GroupInfoModal = ({ />
- + {groupInfo?.groupName} {groupInfo?.members?.length} Members @@ -737,7 +772,7 @@ const GroupHeader = styled.div` `; const GroupDescription = styled.div` - margin-top: 34px; + margin-top: 25px; display: flex; flex-direction: column; width: 100%; @@ -837,4 +872,18 @@ const ConditionSection = styled(Section)<{ theme: IChatTheme }>` } `; +const ProfileSection = styled(Section)<{ theme: IChatTheme }>` + &::-webkit-scrollbar-thumb { + background: ${(props) => props.theme.scrollbarColor}; + border-radius: 10px; + } + &::-webkit-scrollbar-button { + height: 40px; + } + + &::-webkit-scrollbar { + width: 4px; + } +`; + //auto update members when an user accepts not done diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/AutoImageClipper.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/AutoImageClipper.tsx new file mode 100644 index 000000000..a0cc9b49b --- /dev/null +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/AutoImageClipper.tsx @@ -0,0 +1,148 @@ +// React + Web3 Essentials +import React, { Fragment, useCallback, useState } from "react"; + +// External Packages +import Cropper from "react-easy-crop"; +import styledComponents from "styled-components"; +import Resizer from "react-image-file-resizer"; + +type CropType = { + x: number; + y: number; +} + +type CroppedAreaPixels = { + height: number; + width: number; + x: number; + y: number; +} + +const AutoImageClipper = (props: { imageSrc: any; onImageCropped: any; width: any; height: any; }) => { + const { imageSrc, onImageCropped, width, height } = props; + const [crop, setCrop] = useState({ x: 0, y: 0 }); + const [zoom, setZoom] = useState(1); + const [croppedAreaPixels, setCroppedAreaPixels] = useState(null); + const [croppedImage, setCroppedImage] = useState(''); + const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => { + setCroppedAreaPixels(croppedAreaPixels); + }, []); + + React.useEffect(() => { + async function showCroppedImage() { + try { + if (imageSrc) { + const croppedImage = await getCroppedImg(imageSrc, croppedAreaPixels!); + const image = await resizeFile(croppedImage); + onImageCropped(image); + } else { + return "Nothing"; + } + } catch (e) { + console.error(e); + } + } + showCroppedImage(); + }, [crop]); + + async function getCroppedImg(imageSrc:string, pixelCrop: { height: any; width: any; x: any; y: any; }) { + const image = await createImage(imageSrc); + const canvas = document.createElement("canvas"); + canvas.width = pixelCrop?.width; + canvas.height = pixelCrop?.height; + const ctx = canvas.getContext("2d"); + const fileName = "none.jpg"; + + ctx!.drawImage( + image as CanvasImageSource, + pixelCrop.x, + pixelCrop.y, + pixelCrop.width, + pixelCrop.height, + 0, + 0, + pixelCrop.width, + pixelCrop.height + ); + + // As Base64 string + // return canvas.toDataURL('image/jpeg'); + + // As a blob + return new Promise((resolve, reject) => { + canvas.toBlob((file) => { + // resolve(URL.createObjectURL(file)); + resolve( + new File([file!], fileName, { + type: "image/jpeg", + lastModified: Date.now() + }) + ); + }, "image/jpeg"); + }); + } + + const resizeFile = (file: any) => { + return new Promise((resolve) => { + Resizer.imageFileResizer( + file, + 128, + 128, + "JPEG", + 80, + 0, + (uri) => { + resolve(uri); + setCroppedImage(uri as unknown as string); + }, + "base64" + ); + }); + }; + + const createImage = (url: string) => { + return new Promise((resolve, reject) => { + const image = new Image(); + image.addEventListener("load", () => resolve(image)); + image.addEventListener("error", (error) => reject(error)); + image.setAttribute("crossOrigin", "anonymous"); // needed to avoid cross-origin issues on CodeSandbox + image.src = url; + }); + }; + + const onZoomChange = (zoom: React.SetStateAction) => { + setZoom(zoom); + }; + return ( + + + + + + ); +}; + +const Container = styledComponents.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + `; + +export default AutoImageClipper; diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx index 49add35d8..847018da8 100644 --- a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx @@ -27,6 +27,7 @@ import { import { Image } from '../../../config/styles'; import { ProfilePicture, device } from '../../../config'; import { CriteriaValidationErrorType } from '../types'; +import AutoImageClipper from './AutoImageClipper'; export const CREATE_GROUP_STEP_KEYS = { INPUT_DETAILS: 1, @@ -71,11 +72,14 @@ export const CreateGroupModal: React.FC = ({ }, [activeComponent]); const useDummyGroupInfo = false; - const [groupInputDetails, setGroupInputDetails] = useState({ - groupName: useDummyGroupInfo ? 'This is duumy group name' : '', - groupDescription: useDummyGroupInfo ? 'This is dummy group description for testing' : '', - groupImage: useDummyGroupInfo ? ProfilePicture : '' - }) + const [groupInputDetails, setGroupInputDetails] = + useState({ + groupName: useDummyGroupInfo ? 'This is duumy group name' : '', + groupDescription: useDummyGroupInfo + ? 'This is dummy group description for testing' + : '', + groupImage: useDummyGroupInfo ? ProfilePicture : '', + }); const renderComponent = () => { switch (activeComponent) { @@ -167,6 +171,8 @@ const CreateGroupDetail = ({ useState({}); const fileUploadInputRef = useRef(null); const isMobile = useMediaQuery(device.mobileL); + const [isImageUploaded, setIsImageUploaded] = useState(false); + const [imageSrc, setImageSrc] = useState(); const handleChange = (e: Event) => { if (!(e.target instanceof HTMLInputElement)) { @@ -179,15 +185,22 @@ const CreateGroupDetail = ({ (e.target as HTMLInputElement).files && ((e.target as HTMLInputElement).files as FileList).length ) { + setIsImageUploaded(true); + setGroupInputDetails({ + groupDescription, + groupName, + groupImage: '', + }); const reader = new FileReader(); reader.readAsDataURL(e.target.files[0]); reader.onloadend = function () { - setGroupInputDetails({ - groupDescription, - groupName, - groupImage: reader.result as string, - }); + setImageSrc(reader.result as string); + // setGroupInputDetails({ + // groupDescription, + // groupName, + // groupImage: reader.result as string, + // }); }; } }; @@ -220,7 +233,6 @@ const CreateGroupDetail = ({ }); return; } - } if (handleNext) { @@ -245,22 +257,37 @@ const CreateGroupDetail = ({ - {!groupImage && ( + {isImageUploaded ? ( + groupImage ? ( + + group image + + ) : ( + + setGroupInputDetails({ + groupDescription, + groupName, + groupImage: croppedImage, + }) + } + width={undefined} + height={undefined} + /> + ) + ) : ( )} - {groupImage && ( - - group image - - )} + { //use the theme const UploadContainer = styled.div` width: fit-content; + min-width:128px; + min-height:128px; cursor: pointer; align-self: center; `; const ImageContainer = styled.div<{ theme: IChatTheme }>` margin-top: 10px; - width: fit-content; cursor: pointer; border-radius: 32px; background: ${(props) => props.theme.backgroundColor.modalHoverBackground}; - padding: 40px; + width: 128px; + cursor: pointer; + height: 128px; + max-height: 128px; + display:flex; + align-items:center; + justify-content:center; `; const UpdatedImageContainer = styled.div` margin-top: 10px; - width: 112px; + width: 128px; cursor: pointer; - height: 112px; + height: 128px; overflow: hidden; + max-height: 128px; border-radius: 32px; `; diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx index ba70ae396..45b7f09a7 100644 --- a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupType.tsx @@ -28,13 +28,13 @@ import { ProfilePicture } from '../../../config'; const GROUP_TYPE_OPTIONS: Array = [ { - heading: 'Open', - subHeading: 'Anyone can join', + heading: 'Public', + subHeading: 'Anyone can view chats, even without joining', value: 'open', }, { - heading: 'Encrypted', - subHeading: 'Users must join group to view', + heading: 'Private', + subHeading: 'Encrypted Chats, Users must join group to view', value: 'encrypted', }, ]; diff --git a/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts b/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts index fab522e9c..c24da7465 100644 --- a/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts +++ b/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts @@ -18,8 +18,8 @@ export const getRuleInfo = ( } const [chatRules, entryRules] = [ - getRulesToCondtionArray(rules.entry), getRulesToCondtionArray(rules.chat), + getRulesToCondtionArray(rules.entry), ]; return { diff --git a/packages/uiweb/src/lib/components/chat/reusables/OptionButtons.tsx b/packages/uiweb/src/lib/components/chat/reusables/OptionButtons.tsx index 54762aeaf..6a64adb36 100644 --- a/packages/uiweb/src/lib/components/chat/reusables/OptionButtons.tsx +++ b/packages/uiweb/src/lib/components/chat/reusables/OptionButtons.tsx @@ -48,6 +48,8 @@ const OptionDescripton = ({ heading, subHeading,value }: OptionDescription) => { color={theme.textColor?.modalSubHeadingText} fontWeight="400" fontSize="12px" + width='132px' + lineHeight='130%' > {subHeading}