Construire / Stilizare Front-end

Interfața aplicației poate fi construită cu orice tehnologie cunoașteți (ReactJS, Angular, Vue, chiar plain HTML). For the sake of example și pentru a nu ne pierde în detalii, în această secțiune voi folosi componente vizuale preluate din surse publice (librării de componente deja stilizate cu ajutorul TailwindCSS) și le voi modifica sumar. În înregistrarile video voi descrie procesul de dezvoltare într-un mod mai detaliat.

1. Definirea scopului aplicației

Vom crea o aplicație web care va trimite mail-uri care conțin mesaje traduse în una sau mai multe limbi straine. De asemenea, aplicația va stoca mesajele inițiale și le va afișa într-o listă.

Mesajele sunt stocate într-o baza de date în Cloud gestionată de Google Cloud Platform, mesajele sunt traduse din back-end cu ajutorul serviciului Cloud Translation API, iar mail-urile sunt trimise cu prin intermediului serviciului cloud pus la dispoziție de Sendgrid.

2. Definirea componentelor React principale

Pentru a păstra aplicația cât mai simplă, aceasta va avea doar 3 componente principale: o componenta pentru Antet (Header.jsx), o componenta care gestionează afișare listei de mesaje (MessagesList.jsx) și o componentă care gestionează trimiterea de mesaje (MessagesSubmit.jsx)

  • În folderul "components" vom crea 3 fișiere .jsx pentru cele 3 componente

2.1. Header.jsx

  • Creăm un Antet simplu (Header.jsx)

// Header.jsx
import React from 'react';

function Header() {
    return ( 
        <header className='h-14 bg-lime-700 flex justify-center'>
            <span className='self-center text-white text-bold text-xl'>Mail Translator</span>
        </header>
     );
}

export default Header;

2.2. MessagesList.jsx

  • Creăm componenta MessagesList.jsx cu date hardcoded în constanta "messages"

  • Observăm că folosim o componentă importată din pachetul "heroicons/react" (UserIcon). Așadar, npm install @heroicons/react

// MessagesList.jsx
import React, { useEffect, useState } from 'react';
import { UserIcon } from '@heroicons/react/solid'
import axios from 'axios';

const messages = [
    {
        entryID: 1,
        senderName: 'John Doe',
        receiverMail: 'john@mail.com',
        messageContent: 'Hello, how are you?',
    },
];

function MessagesList() {
    return (
        <div id="MessagesList">
            <div className='text-2xl font-bold mb-4'>Latest messages</div>
            <ul className="-mb-8 max-h-96 overflow-auto">
                {messages.length ? messages.map((message, messageIdx) => (
                    <li key={message.entryID}>
                        <div className="relative pb-8">
                            {messageIdx !== messages.length - 1 ? (
                                <span className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200" aria-hidden="true" />
                            ) : null}
                            <div className="relative flex space-x-3">
                                <span
                                    className={'h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white bg-green-500'}>
                                    <UserIcon className="h-5 w-5 text-white" aria-hidden="true" />
                                </span>
                                <div className="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
                                    <p className="text-sm text-gray-500">
                                        <span className="font-medium text-gray-900">
                                            {message.senderName}
                                        </span>
                                        <span className="font-medium">
                                            {` sent a mail to ${message.receiverMail}: ${message.messageContent}`}
                                        </span>
                                    </p>
                                </div>
                            </div>
                        </div>
                    </li>
                )) :
                    <span>No messages yet</span>
                }
            </ul>
        </div>
    );
}

export default MessagesList;
  • Mesajele pe care ne dorim să le afișăm sunt stocate într-o bază de date externă și le putem accesa prin intermediul Back-end-ului. Pentru a putea actualiza interfața aplicației în momentul sosirii datelor, vom folosi funcțiile useEffect și useState puse la dispoziție de React Hooks.

  • Astfel, în interiorul componentei MessagesList() definim un state și îl modificăm cu datele cerute la momentul creării componentei de către browser.

// MessagesList.jsx
    const [messages, setMessages] = useState([]);

    useEffect(() => {
        const fetchData = async () => {
            const result = await axios.get(
                `${process.env.REACT_APP_API_URL}/messages`,
            );

            if (result.data.messages) {
                let messagesArray = result.data.messages;
                messagesArray.reverse();
                setMessages(result.data.messages);
            }
        };

        fetchData();
    }, []);
  • Observăm că am stocat calea către serverul Back-end în fisierul .env

În momentul publicării aplicațiilor (Front și Back), calea către serverul back-end se va schimba. Având în vedere că fișierul .env nu este urmărit de git, vom putea cu ușurință să specificăm noua cale creând un fișier env personalizat pentru aplicația aflată în producție.

// .env
REACT_APP_WEB_URL="http://localhost:3000"
REACT_APP_API_URL="http://localhost:8080"
  • Codul complet al componentei MessagesList.jsx

// MessagesList.jsx
import React, { useEffect, useState } from 'react';
import { UserIcon } from '@heroicons/react/solid'
import axios from 'axios';

// const messages = [
//     {
//         entryID: 1,
//         senderName: 'John Doe',
//         receiverMail: 'john@mail.com',
//         messageContent: 'Hello, how are you?',
//     },
// ];

function MessagesList() {
    const [messages, setMessages] = useState([]);

    useEffect(() => {
        const fetchData = async () => {
            const result = await axios.get(
                `${process.env.REACT_APP_API_URL}/messages`,
            );

            if (result.data.messages) {
                let messagesArray = result.data.messages;
                messagesArray.reverse();
                setMessages(result.data.messages);
            }
        };

        fetchData();
    }, []);

    return (
        <div id="MessagesList">
            <div className='text-2xl font-bold mb-4'>Latest messages</div>
            <ul className="-mb-8 max-h-96 overflow-auto">
                {messages.length ? messages.map((message, messageIdx) => (
                    <li key={message.entryID}>
                        <div className="relative pb-8">
                            {messageIdx !== messages.length - 1 ? (
                                <span className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200" aria-hidden="true" />
                            ) : null}
                            <div className="relative flex space-x-3">
                                <span
                                    className={'h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white bg-green-500'}>
                                    <UserIcon className="h-5 w-5 text-white" aria-hidden="true" />
                                </span>
                                <div className="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
                                    <p className="text-sm text-gray-500">
                                        <span className="font-medium text-gray-900">
                                            {message.senderName}
                                        </span>
                                        <span className="font-medium">
                                            {` sent a mail to ${message.receiverMail}: ${message.messageContent}`}
                                        </span>
                                    </p>
                                </div>
                            </div>
                        </div>
                    </li>
                )) :
                    <span>No messages yet</span>
                }
            </ul>
        </div>
    );
}

export default MessagesList;

2.3. MessagesSubmit.jsx

  • Componenta MessagesSubmit.jsx constă într-un formular și mai multe butoane care vor trimite câte un apel către Back-end pentru a traduce mesajul introdus și a trimite un mail.

  • Limbile disponibile le-am stocat într-un array în fișierul de constante. De asemenea, am stocat adresa de pe care se vor trimite mail-urile tot în fișierul de constante

Pentru ușurință, limbile introduse în LANGUAGES_ARRAY au același format cu cele cerute de Back-end Translate API

// constants.js
export const LANGUAGES_ARRAY = [
    'ALL',
    'ENGLISH',
    'SPANISH',
    'FRENCH',
    'GERMAN',
    'ITALIAN',
    'PORTUGUESE',
    'ROMANIAN',
    'SWEDISH',
    'DUTCH',
    'FINNISH',
    'NORWEGIAN',
    'POLISH',
    'HUNGARIAN',
];

export const DEFAULT_MAIL = "insertyourmailhere";
  • Codul complet al componentei MessagesSubmit.jsx

// MessagesSubmit.jsx
import React from 'react';
import axios from 'axios';
import { LANGUAGES_ARRAY, DEFAULT_MAIL } from '../utils/constants';

function MessagesSubmit() {

    const handleMessageSend = async (e) => {
        const language = e.target.value;
        const senderName = document.getElementById('senderName').value;
        const receiverMail = document.getElementById('receiverMail').value;
        const messageContent = document.getElementById('messageContent').value;

        try {
            let response = await axios.post(
                `${process.env.REACT_APP_API_URL}/messages/foreign`,
                {
                    language,
                    senderName,
                    senderMail: DEFAULT_MAIL,
                    receiverMail,
                    messageContent
                });

                if(response.status === 200) {
                    alert(`Your original messages was in ${response.data.translationData.originalLanguage}. \nMessage sent: ${response.data.translationData.translatedText}`);
                }
        }
        catch (error) {
            alert('Something went wrong');
            console.log(error);
        }
    }

    return (
        <div id="MessagesSubmit">
            <div className='text-2xl font-bold mb-4'>Submit your message</div>
            <form className="w-full max-w-lg">
                <div className="flex flex-wrap -mx-3 mb-6">
                    <div className="w-full md:w-1/2 px-3 mb-6 md:mb-0">
                        <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="senderName">
                            Your name
                        </label>
                        <input className="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" id="senderName" type="text" placeholder="John" />
                    </div>
                    <div className="w-full md:w-1/2 px-3">
                    <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="receiverMail">
                            Receiver mail
                        </label>
                        <input className="appearance-none block w-full bg-gray-200 text-gray-700 border rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white" id="receiverMail" type="text" placeholder="jane@mail.com" />
                    </div>
                </div>
                <div className="flex flex-wrap -mx-3 mb-6">
                    <div className="w-full md:w-full px-3 mb-6 md:mb-0">
                        <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" htmlFor="messageContent">
                            Your message
                        </label>
                        <textarea
                            rows={4}
                            name="comment"
                            id="messageContent"
                            className="shadow-md focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-800 rounded-md p-5"
                            placeholder={'Say hello!'} />
                    </div>
                </div>
            </form>

            {/* Create a button for each language from LANGUAGES_ARRAY */}
            {LANGUAGES_ARRAY.map((language, index) => {
                return (
                    <button
                        key={index}
                        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded m-2 capitalize"
                        onClick={handleMessageSend}
                        value={language}>
                        {language.toLowerCase()}
                    </button>
                )
            })}
        </div>
    );
}

export default MessagesSubmit;

2.4. Wrapping up in MainPage.jsx

Acum că avem cele 3 componente principale, le putem atașa în MainPage.jsx cu un minim de stilizare

// MainPage.jsx
import React from 'react';

import Header from './Header';
import MessagesList from './MessagesList';
import MessagesSubmit from './MessagesSubmit';

function MainPage() {
    return (
        <div id="MainPage">
            <Header />
                <div className="flex max-w-7xl m-auto px-14 py-24">
                    <div className='w-1/2 pr-5'>
                        <MessagesList />
                    </div>
                    <div className='w-1/2 pl-5'>
                        <MessagesSubmit />
                    </div>
                </div>
        </div>
    );
}

export default MainPage;

3. Rezultat

După ce am introdus un nume, un mail și un mesaj, putem apăsa pe unul dintre butoanele de trimitere pentru a expedia un mail. Date despre mesajul original vor apărea în lista din stânga după un refresh, iar mesajul tradus va apărea printr-un window alert. (pentru a păstra exemplele cât mai simple și compacte)

Suntem acum pregătiți să publicăm aplicațiile

Last updated