Skip to content

Commit

Permalink
Merge branches 'main' and 'main' of github.com:okbrandon/ft_transcend…
Browse files Browse the repository at this point in the history
…ence
  • Loading branch information
evnsh committed Nov 14, 2024
2 parents a312c92 + 881eb3c commit e02ef33
Show file tree
Hide file tree
Showing 44 changed files with 1,218 additions and 240 deletions.
322 changes: 312 additions & 10 deletions frontend/public/locales/EN/translation.json

Large diffs are not rendered by default.

326 changes: 314 additions & 12 deletions frontend/public/locales/ES/translation.json

Large diffs are not rendered by default.

326 changes: 314 additions & 12 deletions frontend/public/locales/FR/translation.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion frontend/src/components/Auth/FakeCaptcha.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const FakeCaptcha = ({ formData, setShowFakeCaptcha, setErrorSignUp }) => {
if (inputValue === correctText) {
apiSignup(formData)
.then(() => {
addNotification('success', 'Account created successfully. Please verify your email.');
addNotification('success', t('auth.fakeCaptcha.successMessage'));
navigate("/signin");
})
.catch(err => {
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/Auth/TwoFactorAuthSignIn.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import axios from "axios";
Expand All @@ -20,15 +20,16 @@ const TwoFactorAuthSignIn = ({ username, password, setIsTwoFactorAuth, available
const [otpSent, setOtpSent] = useState(false);
const [error, setError] = useState("");
const { t } = useTranslation();
const tRef = useRef(t);

useEffect(() => {
if (!otpSent) return;
addNotification('info', t('auth.twoFactor.successMessage'));
addNotification('info', tRef.current('auth.twoFactor.successMessage'));
const timeout = setTimeout(() => {
setOtpSent(false);
}, 5000);
return () => clearTimeout(timeout);
}, [otpSent, addNotification, t]);
}, [otpSent, addNotification]);

const handlePlatform = platform => {
if (otpSent) return;
Expand Down
25 changes: 13 additions & 12 deletions frontend/src/components/Chat/DirectMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import DisplayChatMessages from './tools/DisplayChatMessages';
import useClickOutside from './tools/hooks/useClickOutside';
import { getRelationFromUsername } from '../../scripts/relation';
import { useChat } from '../../context/ChatContext';
import { useTranslation } from 'react-i18next';

export const DirectMessage = ({
isOpen,
Expand Down Expand Up @@ -53,6 +54,8 @@ export const DirectMessage = ({
const realConvo = conversations.find(c => c.conversationID === conversationID);
const otherUser = realConvo.participants.find(id => id.userID !== userID);

const { t } = useTranslation();

useEffect(() => {
if (!relations) return;

Expand All @@ -77,13 +80,13 @@ export const DirectMessage = ({

if (!other || !other.userID) return;
if (other.userID === userID) {
addNotification('error', 'You cannot block yourself');
addNotification('error', t('chat.block.selfBlock'));
return;
}

API.put('users/@me/relationships', { user: other.userID, type: 2 })
.then(() => {
addNotification('warning', `User ${username} blocked.`);
addNotification('warning', t('chat.block.blocked', { username: `${username}` }));
setIsRefetch(true);
handleCloseChat();
})
Expand All @@ -96,13 +99,13 @@ export const DirectMessage = ({
const handleInvite = () => {
if (!otherUser || !otherUser.userID) return;
if (otherUser.userID === userID) {
addNotification('error', 'You cannot invite yourself');
addNotification('error', t('chat.invite.selfInvite'));
return;
}

API.post(`users/${otherUser.userID}/challenge`)
.then(() => {
addNotification("success", "Challenge sent");
addNotification("success", t('chat.invite.inviteSent', { username: `${username}` }));
})
.catch(err => {
addNotification("error", `${err?.response?.data?.error || "An error occurred."}`);
Expand Down Expand Up @@ -168,10 +171,9 @@ export const DirectMessage = ({
<Username onClick={toggleDropdown}>{otherUser.displayName || otherUser.username}</Username>
</div>
<Dropdown ref={dropdownRef} $isOpen={isDropdownOpen}>
{/* Brandon translate the buttons below */}
<DropdownItem data-action="profile" onClick={() => handleDropdownAction('profile')}>Profile</DropdownItem>
<DropdownItem data-action="invite" onClick={() => handleDropdownAction('invite')}>Invite</DropdownItem>
<DropdownItem data-action="block" onClick={() => handleDropdownAction('block')}>Block</DropdownItem>
<DropdownItem data-action="profile" onClick={() => handleDropdownAction('profile')}>{t('chat.profile.title')}</DropdownItem>
<DropdownItem data-action="invite" onClick={() => handleDropdownAction('invite')}>{t('chat.invite.challenge.title')}</DropdownItem>
<DropdownItem data-action="block" onClick={() => handleDropdownAction('block')}>{t('chat.block.title')}</DropdownItem>
</Dropdown>
<ActionButtonContainer>
<Arrow ArrowAnimate={!isMinimized} />
Expand All @@ -194,7 +196,7 @@ export const DirectMessage = ({
<ChatInput
id="chat-input"
as="textarea"
placeholder="Type a message..." // Brandon translate this line
placeholder={t('chat.message.placeholder')}
value={content}
onChange={handleInputChange}
onKeyDown={e => {
Expand Down Expand Up @@ -228,9 +230,8 @@ export const DirectMessage = ({
isOpen={isBlockModalOpen}
onClose={() => setIsBlockModalOpen(false)}
onConfirm={handleBlockUser}
// Brandon translate -> "Block User", "message"
title="Block User"
message={`Are you sure you want to block ${username}? You won't be able to see their messages or receive invitations from them.`}
title={t('chat.block.title')}
message={t('chat.block.confirmMessage', { username: `${username}` })}
/>
</>
);
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/Chat/MessagePreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ProfilePicture from './styles/global/ProfilePicture.styled';
import ScrollableComponent from './tools/ScrollableComponent';
import { useChat } from '../../context/ChatContext';
import { useRelation } from '../../context/RelationContext';
import { useTranslation } from 'react-i18next';

const PreviewContainer = styled.div`
padding: 20px;
Expand Down Expand Up @@ -51,6 +52,7 @@ export const MessagePreview = () => {
const { conversations, handleSelectChat, unreadCounts } = useChat();
const { friends } = useRelation();
const userID = localStorage.getItem('userID');
const { t } = useTranslation();

const handleSelectFriend = (friend) => {
const convo = conversations.find((convo) => {
Expand All @@ -70,7 +72,7 @@ export const MessagePreview = () => {

const renderFriendPreview = (friend, index, message, lastMessageUserId = null, convId) => {
if (lastMessageUserId && lastMessageUserId === userID) {
message = 'You: ' + message; // Brandon don't forget to translate this line as well
message = t('chat.message.sentByMe') + message;
}

return (
Expand All @@ -92,7 +94,7 @@ export const MessagePreview = () => {
};

if (friends.length === 0) {
return <NoFriendsMessage>Make some friends so you can chat with them !</NoFriendsMessage>; // Brandon translate this line
return <NoFriendsMessage>{t('chat.message.noFriends')}</NoFriendsMessage>;
}

return (
Expand All @@ -103,7 +105,7 @@ export const MessagePreview = () => {

if (friendExists) {
if (convo.messages.length === 0) {
return renderFriendPreview(other, index, 'Start a new conversation'); // Brandon translate this line
return renderFriendPreview(other, index, t('chat.message.newConversation'));
}
const lastMessage = convo.messages[convo.messages.length - 1];
return renderFriendPreview(other, index, lastMessage.content, lastMessage.sender.userID, convo.conversationID);
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/components/Chat/SearchFriends.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useChat } from '../../context/ChatContext.js';
import { useNotification } from '../../context/NotificationContext';
import API from '../../api/api';
import ConfirmationModal from './tools/ConfirmationModal';
import { useTranslation } from 'react-i18next';

export const SearchFriends = ({ toggleMinimization }) => {
const navigate = useNavigate();
Expand All @@ -23,6 +24,7 @@ export const SearchFriends = ({ toggleMinimization }) => {
const [isBlockModalOpen, setIsBlockModalOpen] = useState(false);
const [selectedFriend, setSelectedFriend] = useState(null);
const userID = localStorage.getItem('userID');
const { t } = useTranslation();

const handleSelectFriend = (friend) => {
const convo = conversations.find((convo) => {
Expand Down Expand Up @@ -57,13 +59,13 @@ export const SearchFriends = ({ toggleMinimization }) => {
const handleBlockUser = () => {
if (!selectedFriend || !selectedFriend.userID) return;
if (selectedFriend.userID === userID) {
addNotification('error', 'You cannot block yourself');
addNotification('error', t('chat.block.selfBlock'));
return;
}

API.put('users/@me/relationships', { user: selectedFriend.userID, type: 2 })
.then(() => {
addNotification('warning', `User ${selectedFriend.username} blocked.`); // should notifications also be translated?
addNotification('warning', t('chat.block.userBlocked', { username: `${selectedFriend.username}` }));
setIsRefetch(true);
})
.catch(err => {
Expand All @@ -79,7 +81,7 @@ export const SearchFriends = ({ toggleMinimization }) => {
<SearchInput
id="search-input"
type="text"
placeholder="Search for Friends..." // Brandon translate this line
placeholder={t('chat.searchBar.placeholder')}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
autoComplete='off'
Expand All @@ -92,10 +94,9 @@ export const SearchFriends = ({ toggleMinimization }) => {
<FriendItem key={index} onClick={() => handleSelectFriend(friend)}>
<Username >{friend.username}</Username>
<ButtonContainer>
{/* Brandon translate these buttons */}
<ActionButton color="#6a0dad" onClick={(e) => handleProfile(friend, e)}>Profile</ActionButton>
<ActionButton color="#9AE66E" onClick={(e) => handleInvite(friend, e)}>Invite</ActionButton>
<ActionButton color="#EE4266" onClick={(e) => handleBlock(friend, e)}>Block</ActionButton>
<ActionButton color="#6a0dad" onClick={(e) => handleProfile(friend, e)}>{t('chat.profile.title')}</ActionButton>
<ActionButton color="#9AE66E" onClick={(e) => handleInvite(friend, e)}>{t('chat.invite.challenge.title')}</ActionButton>
<ActionButton color="#EE4266" onClick={(e) => handleBlock(friend, e)}>{t('chat.block.title')}</ActionButton>
</ButtonContainer>
</FriendItem>
))}
Expand All @@ -107,9 +108,8 @@ export const SearchFriends = ({ toggleMinimization }) => {
isOpen={isBlockModalOpen}
onClose={() => setIsBlockModalOpen(false)}
onConfirm={handleBlockUser}
// Brandon translate these lines below
title="Block User"
message={`Are you sure you want to block ${selectedFriend?.username}? You won't be able to see their messages or receive invitations from them.`}
title={t('chat.block.title')}
message={t('chat.block.confirmMessage', { username: `${selectedFriend?.username}` })}
/>
</>
);
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/Chat/tools/ChatHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import React from 'react';
import Arrow from './Arrow.js';
import { ChatNotificationPopUp, Header } from '../styles/Chat/ChatContainer.styled.js'
import { useChat } from '../../../context/ChatContext.js';
import { useTranslation } from 'react-i18next';

const ChatHeader = ({ toggleMinimization, arrowState }) => {
const { unreadCounts } = useChat();
const unreadCount = Object.values(unreadCounts).reduce((acc, count) => acc + count, 0);
const { t } = useTranslation();

// Brandon translate this line -> "Messages"
return (
<Header onClick={toggleMinimization}>
{unreadCount > 0 && <ChatNotificationPopUp>{unreadCount}</ChatNotificationPopUp>}
Messages
{t('chat.message.title')}
<Arrow ArrowAnimate={arrowState} />
</Header>
);
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/Chat/tools/ConfirmationModal.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';

const ModalOverlay = styled.div`
position: fixed;
Expand Down Expand Up @@ -58,17 +59,18 @@ const MessageContent = styled.p`
`;

const ConfirmationModal = ({ isOpen, onClose, onConfirm, title, message }) => {
const { t } = useTranslation();

if (!isOpen) return null;

// Brandon translate -> "Cancel" and "Confirm"
return (
<ModalOverlay>
<ModalContent>
<ModalTitle>{title}</ModalTitle>
<MessageContent>{message}</MessageContent>
<ModalButtons>
<CancelButton onClick={onClose}>Cancel</CancelButton>
<ConfirmButton onClick={onConfirm}>Confirm</ConfirmButton>
<CancelButton onClick={onClose}>{t('chat.confirmModal.cancelButton')}</CancelButton>
<ConfirmButton onClick={onConfirm}>{t('chat.confirmModal.confirmButton')}</ConfirmButton>
</ModalButtons>
</ModalContent>
</ModalOverlay>
Expand Down
37 changes: 23 additions & 14 deletions frontend/src/components/Chat/tools/DisplayChatMessages.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import API from '../../../api/api.js';
import { useNavigate } from 'react-router-dom';
import Card from '../../../styles/shared/Card.styled.js';
import { useTournament } from '../../../context/TournamentContext.js';
import { useTranslation, Trans } from 'react-i18next';

const DisplayChatMessages = ({ realConvo, userID, messagesEndRef, otherUser }) => {
const { addNotification } = useNotification();
const { registerForTournament } = useTournament();
const navigate = useNavigate();
const { t } = useTranslation();

const formatTimestamp = (timestamp) => {
const date = new Date(timestamp);
Expand All @@ -31,39 +33,40 @@ const DisplayChatMessages = ({ realConvo, userID, messagesEndRef, otherUser }) =
const handleAcceptTournamentInvite = async (tournamentID) => {
try {
await API.post(`/tournaments/${tournamentID}/invite/accept`);
addNotification('success', t('chat.invite.inviteAccepted'));
navigate(`/tournaments/${tournamentID}`);
registerForTournament(tournamentID);
} catch (error) {
console.log(error);
addNotification('error', error?.response?.data?.error || 'Error accepting tournament invite');
addNotification('error', error?.response?.data?.error || t('chat.invite.errorAccept'));
}
}

const handleAcceptChallengeInvite = async (challengerID, inviteID) => {
try {
await API.post(`/users/${challengerID}/challenge/${inviteID}/accept`);
addNotification('success', 'Challenge invite accepted');
addNotification('success', t('chat.invite.inviteAccepted'));
navigate('/game-challenge');
} catch (error) {
console.log(error);
addNotification('error', error?.response?.data?.error || 'Error accepting challenge invite');
addNotification('error', error?.response?.data?.error || t('chat.invite.errorAccept'));
}
}

const handleDenyChallengeInvite = async (challengerID, inviteID) => {
try {
await API.post(`/users/${challengerID}/challenge/${inviteID}/deny`);
addNotification('success', 'Challenge invite denied');
addNotification('success', t('chat.invite.inviteDeclined'));
} catch (error) {
console.log(error);
addNotification('error', error?.response?.data?.error || 'Error denying challenge invite');
addNotification('error', error?.response?.data?.error || t('chat.invite.errorDecline'));
}
}

if (!realConvo || realConvo.messages.length === 0) {
return (
<NewConversationMessage>
It's your first time chatting with {otherUser}. Say hi, don't be shy!
{t('chat.message.noMessages', { username: `${otherUser}` })}
</NewConversationMessage>
);
} else {
Expand All @@ -82,9 +85,13 @@ const DisplayChatMessages = ({ realConvo, userID, messagesEndRef, otherUser }) =
<BubbleDetails>
<Card $width={'270px'} $height={'200px'}>
<div className='bg'>
<h3>Tournament</h3>
<p>I invite you to join <b>{message.tournamentInvite.tournament.name}</b></p>
<button onClick={() => handleAcceptTournamentInvite(message.tournamentInvite.tournament.tournamentID)}>Join</button>
<h3>{t('chat.invite.tournament.title')}</h3>
<p>
<Trans i18nKey="chat.invite.tournament.content" components={[<strong key="first"/>]} values={{
tournamentName: `${message.tournamentInvite.tournament.name}`
}} />
</p>
<button onClick={() => handleAcceptTournamentInvite(message.tournamentInvite.tournament.tournamentID)}>{t('chat.invite.tournament.acceptButton')}</button>
</div>
<div className='blob'/>
</Card>
Expand All @@ -97,11 +104,13 @@ const DisplayChatMessages = ({ realConvo, userID, messagesEndRef, otherUser }) =
<BubbleDetails>
<Card $width={'290px'} $height={'250px'}>
<div className='bg'>
<h3>Challenge</h3>
<p>I challenge you to play against <b>me</b></p>
<h3>{t('chat.invite.challenge.title')}</h3>
<p>
<Trans i18nKey="chat.invite.challenge.content" components={[<strong key="first"/>]} />
</p>
<div className='button-container'>
<button onClick={() => handleAcceptChallengeInvite(message.sender.userID, message.challengeInvite.inviteID)}>Accept</button>
<button onClick={() => handleDenyChallengeInvite(message.sender.userID, message.challengeInvite.inviteID)}>Decline</button>
<button onClick={() => handleAcceptChallengeInvite(message.sender.userID, message.challengeInvite.inviteID)}>{t('chat.invite.challenge.acceptButton')}</button>
<button onClick={() => handleDenyChallengeInvite(message.sender.userID, message.challengeInvite.inviteID)}>{t('chat.invite.challenge.declineButton')}</button>
</div>
</div>
<div className='blob'/>
Expand All @@ -114,7 +123,7 @@ const DisplayChatMessages = ({ realConvo, userID, messagesEndRef, otherUser }) =
<MessageWrapper key={index} $isHost={message.sender.userID === userID}>
{message.sender.userID === userID ? (
<BubbleDetails>
{!isSameSender && <MessageUsername $isHost={false}>You</MessageUsername>}
{!isSameSender && <MessageUsername $isHost={false}>{t('chat.message.sentByMe')}</MessageUsername>}
<HostBubble data-time={formatTimestamp(message.createdAt)} $isRounded={isSameSender}>
{message.content}
</HostBubble>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Footer/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const Footer = () => {
fourthAuthor: "Hanmin"
}} />
</p>
<p className="privacy-policy" onClick={() => navigate('/privacy-policy')}>Privacy Policy</p>
<p className="privacy-policy" onClick={() => navigate('/privacy-policy')}>{t('footer.privacyPolicy')}</p>
<p>&copy; {t('footer.copyright')}</p>
</FooterContainer>
);
Expand Down
Loading

0 comments on commit e02ef33

Please sign in to comment.