Assistant: 
User: system prompt: 
You are a highly intelligent AI assistant that specializes in generating UI components for Flet in Python based on simple user prompts. Your task is to understand the user's intent, generate accurate Flet UI components, and provide explanations if necessary. Here's how you should proceed:

Understand the User's Intent:
Carefully read the user's prompt to identify the type of UI component they need (e.g., buttons, sliders, containers, or custom layouts). Always clarify if the user’s request seems vague.
Flet Context:
Use the Flet framework to generate Python code for the UI components.
Ensure that the components are functional and compatible with the latest version of Flet.
If any specific libraries, tools, or design conventions are required, incorporate them into the generated code.
Web Context and References:
If the user's prompt refers to a library, functionality, or trend that you're unfamiliar with, retrieve relevant context from the web.
Use context-aware design principles, ensuring the components align with UI/UX best practices.
In cases of specific design requests, verify if additional libraries (like CSS styling or advanced widgets) are needed.
Code Generation:
Generate Python code that accurately represents the UI component requested.
Ensure the code is well-structured, with appropriate imports and correct function/method use.
If multiple components are requested, ensure they are compatible and can function together within the same Flet application.
Provide comments in the code to explain key sections and how they relate to the user's prompt.
Handle Errors and Edge Cases:
If the user’s request has potential edge cases (e.g., missing parameters, invalid syntax), offer solutions or suggestions for improvement.
Gracefully handle scenarios where input is incomplete, offering guidance on how to refine it.

 user prompt: create an app like twitter using flet where i can post tweet and they get added into a scrollable column of tweets with username and buttons to like a
 web context: Connecting users to Stream Feeds and Creating a tweet - Twitter Clone Part 1ProductsSolutionsDevelopersCompanyPricingContactLog inBackProductsChat MessagingBuild real-time chat messaging in less timeVideo & AudioReliable in-app video calling and livestreamingActivity FeedsGrow engagement with enterprise-ready feedsAdvanced Add-Ons and IntegrationsAI ModerationFind, monitor, and resolve harmful content easilyAI ChatBot IntegrationIntegrate AI into your Stream chat messaging appSolutions->AI Chat BotCollaborationDatingEducationFinancialGamingHealthcareMarketplaceOn-DemandSocialSportsVirtual EventsResourcesCase StudiesWhy Stream is loved by it's customersEnterpriseStream's industry-leading 99.999% uptime SLADocumentationChat MessagingEverything developers need to build with StreamVideo & AudioYour guide to a seamless integrationActivity FeedsStep by step guidance to streamline developmentResourcesStack OverflowFollow us on Stack OverflowYoutubeFollow us on YouTubeGitHubFollow us on GitHubCompanyAbout UsLearn more about our mission and teamCareersExplore open opportunitiesBlogRead tutorials, announcements, and moreCase StudiesWhy Stream is loved by it's customersEnterpriseStream's industry-leading 99.999% uptime SLAPress & AwardsStay Updated on Stream NewsPricingChat MessagingVideo & AudioActivity FeedsContact SalesContact SupportSubmit Bug ReportStart Coding FreeBlogTutorialsCase StudiesCompanyDesignEngineeringOpen SourceProductBackSearchthe blog...Twitter Clone Part 1: Connecting Users to Stream Feeds and Creating a TweetTutorialsFeeds34min readIn this tutorial, you will learn how to implement Stream activity feeds in creating a Twitter clone with the React SDK.Dillion M.PublishedJune 14, 2022In this article, the first part of theBuild a Twitter Clone series, you will be creating a Twitter clone, which connects and authenticates a selected user with your Stream app. For this tutorial, you will make the layout and add support for creating tweets in the clone usingStream activity feeds.Let us get started.Developer SetupTo start building the clone, you need to install dependencies from the npm library. To install dependencies, you need to haveNode installedon your system. Alternatively, you can useyarn, but I will be using npm in this article.I usednode version 16.13.1for this tutorial, so I recommend you use the same to code along.Setting Up your Stream DashboardA Stream Feeds App handles all the backend services for managing feeds, such as creating activities, adding reactions to activities, following and unfollowing activities, etc.Create A Feeds App on Your Stream DashboardTo create a feeds app on Stream, you need a Stream account. If you do not have one, head over to theregister pagefor a free trial account or register for aMaker Accountfor free indefinite access to Stream.To create a Stream app for feeds:Go to yourStream dashboardafter logging inSelectCreate AppEnter a name for the app (for this tutorial, I will usestreamer, the fancy name for the clone)Set yourServer LocationsSet theEnvironmenttoDevelopmentAnd finally, selectCreate App.After creating the app, select Activity Feeds from the sidebar:Here is the overview of the Feeds dashboard:You have theApp ID,API Key, andAPI Secret. You will need these values later when you want to connect to Stream from your client app.Create Feed GroupsCurrently, there are no feed groups in your app:A feed group is used for grouping similar activities together. For example, in this tutorial, you will have a:"timeline" feed groupfor activities made by users that a user follows"user" feed groupfor activities made by a user"notification" feed groupfor notification activities originating from follow or reaction actionsFor thetimelineandusergroup, use aflatfeed type, and anotificationgroup with anotificationfeed type.With Stream set up, you can now create the client application.Create Your Twitter Clone ApplicationWe will usecreate-react-app (CRA)to create the React application. On your terminal, run:bash12npx create-react-app streamercdstreamerThis command will create a starter React project. Now, you need to install the required dependencies. These dependencies are broken into two categories.Stream's dependenciesgetstream: official JavaScript client for Stream Feedsreact-activity-feed: built on thegetstreamlibrary for providing React components to integrate activity feeds into your application.Other dependenciesreact-router-dom: for adding routes for different pages in the application. You will use it to add pages for the starter login page, home page, profile page, and notifications pageclassnames: utility library for dynamically combining classesdate-fns: for formatting dates in a readable mannerstyled-components: for CSS-in-JS stylesnanoid: for generating unique IDs. You will use this to generate IDs for tweetsInstall the dependencies by running:bash1npminstallgetstream react-activity-feed react-router-dom classnames date-fns styled-components nanoidIf you come across a dependency resolution error forreactandreact-activity-feedsimilar to this:You can add the--forceflag to the npm install command. This will ignore the resolution error. The error above occurs because CRA installs the latest versions ofreactandreact-dom(which is version 18, released recently), but Stream’s dependencies haven’t been updated to support React v18 yet. In this project, we won’t be using specific React v18 features.Folder Structure of the ApplicationTo keep your code organized and so you can follow this tutorial correctly, you should use the following folder structure for this application.After starting the project with CRA, you should get this:├── README.md├── package-lock.json├── package.json├── node_modules├── public| ├── favicon.ico| ├── index.html| ├── logo192.png| ├── logo512.png| ├── manifest.json| └── robots.txt└── src├── App.css├── App.js├── App.test.js├── index.css├── index.js├── logo.svg├── reportWebVitals.js└── setupTests.jsYou will need new folders to improve the structure. Create the following folders:src/components: where the components--the building blocks in your application--will be createdsrc/pages: where the page components (profile, notifications, etc.) will be createdsrc/hooks: where the custom hooks you create in this tutorial will livesrc/utils: where the utilities will liveWith these folders created, you should have the following structure:├── README.md├── package-lock.json├── package.json├── public| ├── favicon.ico| ├── index.html| ├── logo192.png| ├── logo512.png| ├── manifest.json| └── robots.txt└── src├── App.css├── App.js├── App.test.js├──components/├──hooks/├── index.css├── index.js├── logo.svg├── reportWebVitals.js├── setupTests.js├──utils/└──pages/Create Starter Page for Selecting UsersThe starter page for this application shows different demo users that a user can select from to use Streamer:Ideally, there should be a login form that sends requests to a backend server, which authenticates the user's credentials with the database. For demonstration purposes, we will stick with demo users.Add Demo UsersCreate a new file calledsrc/users.jsand paste the following code:js123456789101112131415161718192021222324252627282930313233343536373839constusers=[{id:'iamdillion',name:'Dillion',image:'https://dillionmegida.com/img/deee.jpg',bio:'Just here, doing my thing. Developer advocate at @getstream_io',token:'ENTER TOKEN FOR iamdillion',},{id:'getstream_io',name:'Stream',image:'https://avatars.githubusercontent.com/u/8597527?s=200&v=4',bio:'Deploy activity feeds and chat at scale with Stream – an API driven platform powering over a billion end users. Get started at http://getstream.io.',token:'ENTER TOKEN FOR getstream_io',},{id:'jake',name:'Jake',image:'https://picsum.photos/300/300',bio:'Just Jake, nothing much',token:'ENTER TOKEN FOR jake',},{id:'joe',name:'Joe',image:'https://picsum.photos/200/200',bio:'How are you?',token:'ENTER TOKEN FOR joe',},{id:'mike',name:'Mike',image:'https://picsum.photos/400/400',bio:'I am mike here. I do things on #react and #javascript',token:'ENTER TOKEN FOR mike',},]exportdefaultusersThis is an array of users. Each user object has anidwhich is a required property to connect the user to Stream feeds. Thisidwill also be used as the Streamer username of each user. Each object also has aname,image, andbioproperty.In a live application, the token should also be generated from the backend server using theAPI KeyandSecret Keyof your Stream app, but for tutorial purposes, you can manually generate tokens ongenerator.getstream.iousing the user'sidand your application'sAPI KeyandSecret Key. When you generate a token for a user, replace it in theusers.jsfile.Stream usesUser Tokensto authenticate users--to confirm that users have access to your Stream application.Create a Storage UtilityNext, create a storage utility insrc/utils/storage.js. This utility handles storing and retrieving data from local storage. Add the following code to this file:js1234exportconstsaveToStorage=(key,value)=>window.localStorage.setItem(key,value)exportconstgetFromStorage=(key)=>window.localStorage.getItem(key)You will use this utility to save the selected useridfrom the start page. This way, the user will not have to choose a user on every refresh.Add Global Default StylesYou need to add global default styles for buttons, links, and other elements. Replace the content ofsrc/index.csswith the following:css123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657:root{--theme-color:#f91680;--faded-theme-color:#f916803c;}*{box-sizing:border-box;}body{margin:0;background-color:black;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}code{font-family:source-code-pro,Menlo,Monaco,Consolas,'Courier New',monospace;}button{border:none;background:none;cursor:pointer;text-align:left;}button:disabled{cursor:not-allowed;}h1,
h2,
h3,
h4,
h5,
h6,
p{margin:0;}input,
textarea{font-family:inherit;}span{display:block;}a{text-decoration:none;}The--theme-colorvariable will be used in many parts of the application.Create the StartPage ComponentCreate a new filesrc/views/StartPage.jsfor the start page, and paste the following. Start from the imports and styles:js123456789101112131415161718192021222324252627282930313233343536373839404142434445importstyledfrom'styled-components'importusersfrom'../users'import{saveToStorage}from'../utils/storage'constMain=styled.main`background-color: black;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100vh;
  flex-direction: column;

  h1 {
    text-align: center;
    color: white;
    font-size: 20px;
    margin-bottom: 20px;
  }

  .users {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 300px;
    margin: 0 auto;

    &__user {
      display: flex;
      flex-direction: column;
      img {
        width: 50px;
        height: 50px;
        border-radius: 50%;
        margin-bottom: 5px;
      }
      .name {
        margin: 10px auto;
        color: white;
        text-align: center;
      }
    }
  }`And next, the component:js123456789101112131415161718192021222324exportdefaultfunctionStartpage(){constonClickUser=(id)=>{saveToStorage('user',id)window.location.href='/home'}return(<Main><h1>Select a user</h1><div className="users">{users.map((u)=>(<button
            onClick={()=>onClickUser(u.id)}className="users__user"key={u.id}><img src={u.image}alt=""/><span className="name">{u.name}</span></button>))}</div></Main>)}In theStartPagecomponent, you loop through the users on the page, and on clicking a user, you save the user'sidto local storage and navigate to the/homepath.Note:For some of the colors in this application, I randomly selected a close color code, but for some notable colors (like a black background, pink highlights, etc.), I used theEye DropperChrome extension to pick the color from the Twitter page.Next, you have to configureReact Routerto show the start page on the index path.Configure Route for the StartPage ComponentReplace the content ofsrc/App.jswith the following:js12345678910111213import{BrowserRouterasRouter,Route,Routes}from'react-router-dom'importStartPagefrom'./pages/StartPage'exportdefaultfunctionApp(){return(<Router><Routes><Route path="/"element={<StartPage/>}/></Routes></Router>)}Start the development server by running thenpm run startcommand on your terminal. Onhttp://localhost:3000, you will get the users on the screen.When you click on a user on this page, the browser navigates to/home, which should show the home page of the logged-in user.Create the User HomepageIn this section, you will create a homepage for the user.Here is what the result of this section will look like:Add Icon ComponentsA lot of icons are used throughout this project. I got the icons fromremixiconand made them reusable React components. You can find all the iconsin this archived file in the repo. Create a new foldersrc/components/Iconsand save all the icons from the archive there.All icons have asizeandcolorproperty that you can use to customize the icon's look. Some icons also have thefillproperty, which specifies if the icon should be in stroke form or fill form.Connect a User to Stream Feeds in App.jsThe next step is to connect the selected user from the start page to the Feeds App on your Stream dashboard. To connect a user to Stream, you first need to create an instance of your Stream app in your React application. To do this, you use theStreamClientconstructor from thegetstreamlibrary. After creating the instance, then you can connect the user to Stream. And with theStreamAppcomponent from the React SDK, you can provide feed methods and data to other components.In yourApp.jsfile, add the following imports to the existing imports:js12345import{useEffect,useState}from'react'import{StreamClient}from'getstream'import{StreamApp}from'react-activity-feed'importusersfrom'./users'import{getFromStorage}from'./utils/storage'UsinggetFromStorage, you will get the user'sid, and find that user in theusersarray. If such a user exists, then you connect them to Stream. This approach is our own method of authentication for development 😁To connect to your feeds application, you need yourApp IDandAPI Key. You can get these from your dashboard, as shown in the screenshot below:Assign these values to variables inApp.jslike this:js12constAPP_ID='1183905'constAPI_KEY='mx8gc4kmvpec'Before thereturnstatement in theAppcomponent, add these lines of code:js12345678910111213141516171819202122232425functionApp(){constuserId=getFromStorage('user')constuser=users.find((u)=>u.id===userId)||users[0]const[client,setClient]=useState(null)useEffect(()=>{asyncfunctioninit(){constclient=newStreamClient(API_KEY,user.token,APP_ID)awaitclient.user(user.id).getOrCreate({...user,token:''})setClient(client)}init()},[])if(!client)return<></>return(// ...)}First, you get the user'sid. Next, you find the user from theusersarray. If the user does not exist, you set theuservariable as the first user in the array.You also keep track of theclientstate you will use in a second.When the component mounts, you connect the user to Stream. The component must mount first because connecting a user to Stream creates aWebSocket connectionon the browser. TheuseEffecthook with an empty dependency array runs when the component mounts.In theuseEffecthook, you create the app instance using yourAPI_KEY, the user'stoken, and yourAPP_ID. Using the instance, you can define a user by theirid, and add the user to the Stream database if they do not exist already using thegetOrCreatemethod. As the name implies, this method retrieves the user's info from the database, and if the user does not exist, it adds the user to the database. You can find the user feeds in your dashboard explorer:After connecting the user, you update theclientstate. Now, you can use theclientobject. In theAppcomponent, wrap the elements with theStreamAppcomponent like this:js12345678910111213functionApp(){// ...return(<StreamApp token={user.token}appId={APP_ID}apiKey={API_KEY}><Router><Routes><Route path="/"element={<StartPage/>}/></Routes></Router></StreamApp>)}TheStreamAppcomponent provides feed methods andcontext datato the children components to trigger feed functionalities.With the code so far, when you click on a user from the start page, the browser navigates to the/homeroute, and the selected user is connected to Stream. Now, you will create a layout for the home page.Create Common Shareable ComponentsThis project has some common components that you will reuse in many other components. Creating these components separately makes the code more manageable.Create a Loading Indicator ComponentBefore creating the layouts, you need to create a loading indicator component that you will use in other components. Create a new filesrc/components/LoadingIndicator. In that file, paste the following:js1234567891011121314151617181920212223242526272829303132333435363738394041424344454647importstyledfrom'styled-components'constContainer=styled.div`width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  padding-top: 100px;
  background-color: black;

  .circle {
    border: 2px solid #333;
    border-radius: 50%;
    position: relative;
    width: 25px;
    height: 25px;

    &::after {
      content: '';
      position: absolute;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      border-top: 2px solid var(--theme-color);
      border-radius: 50%;
      animation: spin 500ms infinite linear;

      @keyframes spin {
        from {
          transform: rotate(0deg);
        }
        to {
          transform: rotate(360deg);
        }
      }
    }
  }`exportdefaultfunctionLoadingIndicator(){return(<Container><div className="circle"></div></Container>)}This creates an infinite rotating circle.Create a Modal ComponentTheModalcomponent serves as a modal dialog for different elements such as the tweet form, comment form, etc.Create a new filesrc/components/Modal.jsand paste the imports and styles:js1234567891011121314151617181920212223242526272829303132333435363738importclassNamesfrom'classnames'importstyledfrom'styled-components'importClosefrom'./Icons/Close'constContainer=styled.div`position: fixed;
  z-index: 6;
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  padding: 30px 0;
  left: 0;
  top: 0;

  .modal {
    z-index: 2;
    position: relative;

    background-color: black;
    border-radius: 20px;

    .close-btn {
      position: relative;
      left: -10px;
    }
  }`constBackdrop=styled.div`position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  background-color: rgba(255, 255, 255, 0.2);`Next, the component:js12345678910111213exportdefaultfunctionModal({className,children,onClickOutside}){return(<Container><Backdrop onClick={()=>onClickOutside()}/><div className={classNames('modal',className)}><button onClick={onClickOutside}className="close-btn"><Close color="white"size={24}/></button>{children}</div></Container>)}On clicking theBackdropstyled component or the close button, theonClickOutsidefunction is called, which is expected to hide the modal conditionally.Create a ScrollToTop ComponentWhen you navigate to a new page in React using theLinkcomponent fromreact-router-dom, the scroll position would usually retain its position. This component you are about to build will help resolve that by automatically scrolling to the top of the page on every route change.Create a new filesrc/components/ScrollToTop.jswith the following code:js1234567891011121314import{useEffect}from'react'import{useLocation}from'react-router'constScrollToTop=(props)=>{constlocation=useLocation()useEffect(()=>{window.scrollTo(0,0)},[location])return<>{props.children}</>}exportdefaultScrollToTopWhen thelocationobject changes, theuseEffecthook triggers the scroll to top expression.Next, you will add this component inApp.js.js12// other importsimportScrollToTopfrom'./components/ScrollToTop'js1234567891011exportdefaultfunctionApp(){// ...return(<StreamApp token={user.token}appId={APP_ID}apiKey={API_KEY}><Router><ScrollToTop/>// routes</Router></StreamApp>)}Create FollowBtn ComponentThe follow button is used for following and unfollowing users. Create a new filesrc/components/FollowBtn. Add the imports and the styles:js123importclassNamesfrom'classnames'importstyledfrom'styled-components'import{useState}from'react'Next, the UI of the button:js123456789101112131415161718192021exportdefaultfunctionFollowBtn({userId}){const[following,setFollowing]=useState(false)return(<Container><button
        className={classNames(following?'following':'not-following')}onClick={()=>setFollowing(!following)}>{following?(<div className="follow-text"><span className="follow-text__following">Following</span><span className="follow-text__unfollow">Unfollow</span></div>):('Follow')}</button></Container>)}This component is not fully functional as that is not the scope of this part. Part 3 adds more to the code. For now, the component receives theuserIdprop (which it doesn't use yet) and toggles thefollowingstate when clicked.Create a TweetForm componentTheTweetFormcomponent is a shareable form component with the tweet input and a submit button. Create a new filesrc/components/Tweet/TweetForm.js. Import some libraries:js123456789101112importclassNamesfrom'classnames'import{useEffect,useRef,useState}from'react'import{useStreamContext}from'react-activity-feed'importstyledfrom'styled-components'importCalendarfrom'../Icons/Calendar'importEmojifrom'../Icons/Emoji'importGiffrom'../Icons/Gif'importImagefrom'../Icons/Image'importLocationfrom'../Icons/Location'importPollfrom'../Icons/Poll'importProgressRingfrom'../Icons/ProgressRing'TheProgressRingcomponent indicates the text length and shows when the text exceeds the maximum available length.Next, the styles:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125constContainer=styled.div`width: 100%;

  .reply-to {
    font-size: 14px;
    color: #888;
    display: flex;
    margin-left: 55px;
    margin-bottom: 10px;

    &--name {
      margin-left: 4px;
      color: var(--theme-color);
    }
  }`constForm=styled.form`width: 100%;
  display: flex;
  align-items:${({inline})=>(inline?'center':'initial')};

  .user {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    overflow: hidden;
    margin-right: 15px;

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }

  .input-section {
    width: 100%;
    display: flex;
    flex: 1;
    flex-direction:${({inline})=>(inline?'row':'column')};
    align-items:${({inline})=>(inline?'center':'initial')};
    height:${({inline,minHeight})=>(inline?'40px':minHeight)};

    textarea {
      padding-top: 10px;
      background: none;
      border: none;
      padding-bottom: 0;
      font-size: 18px;
      width: 100%;
      flex: 1;
      resize: none;
      outline: none;
      color: white;
    }

    .actions {
      margin-top:${({inline})=>(inline?'0':'auto')};
      display: flex;
      height: 50px;
      align-items: center;

      button {
        &:disabled {
          opacity: 0.5;
        }
      }

      .right {
        margin-left: auto;
        display: flex;
        align-items: center;
      }

      .tweet-length {
        position: relative;

        svg {
          position: relative;
          top: 2px;
        }

        &__text {
          position: absolute;
          color: #888;
          font-size: 14px;
          top: 0;
          bottom: 0;
          left: 0;
          right: 0;
          margin: auto;
          height: max-content;
          width: max-content;

          &.red {
            color: red;
          }
        }
      }

      .divider {
        height: 30px;
        width: 2px;
        border: none;
        background-color: #444;
        margin: 0 18px;
      }

      .submit-btn {
        background-color: var(--theme-color);
        padding: 10px 20px;
        color: white;
        border-radius: 30px;
        margin-left: auto;
        font-weight: bold;
        font-size: 16px;

        &:disabled {
          opacity: 0.6;
        }
      }
    }
  }`The action buttons, though non-functional:js1234567891011121314151617181920212223242526272829303132constactions=[{id:'image',Icon:Image,alt:'Image',},{id:'gif',Icon:Gif,alt:'GIF',},{id:'poll',Icon:Poll,alt:'Poll',},{id:'emoji',Icon:Emoji,alt:'Emoji',},{id:'schedule',Icon:Calendar,alt:'Schedule',},{id:'location',Icon:Location,alt:'Location',},]And for the component, paste this:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051exportdefaultfunctionTweetForm({submitText='Tweet',onSubmit,className,placeholder,collapsedOnMount=false,minHeight=120,shouldFocus=false,replyingTo=null,}){constinputRef=useRef(null)const{client}=useStreamContext()const[expanded,setExpanded]=useState(!collapsedOnMount)const[text,setText]=useState('')useEffect(()=>{if(shouldFocus&&inputRef.current)inputRef.current.focus()},[])constuser=client.currentUser.dataconstMAX_CHARS=280constpercentage=text.length>=MAX_CHARS?100:(text.length/MAX_CHARS)*100constsubmit=async(e)=>{e.preventDefault()if(exceededMax)returnalert('Tweet cannot exceed '+MAX_CHARS+' characters')awaitonSubmit(text)setText('')}constonClick=()=>{setExpanded(true)}constisInputEmpty=!Boolean(text)constcharsLeft=MAX_CHARS-text.lengthconstmaxAlmostReached=charsLeft<=20constexceededMax=charsLeft<0constisReplying=Boolean(replyingTo)}The component receives eight props:submitText: The text on the submit button, which by default is "Tweet"onSubmit: The function called when the submit button is called. This function will be called with thetextargument from the inputclassName: For custom class names passed to this componentplaceholder: Placeholder for the inputcollapsedOnMount: A boolean to specify if the form is collapsed on mount.minHeight: For the minimum height of the formshouldFocus: A boolean to specify if the input should be focused on mountreplyingTo: If the form is a reply to a user, then the user'sidwill be passed here.Thepercentagevariable calculates how many characters the user has typed. This value works with theProgressRingcomponent to indicate how much has been typed and how many characters are left based on the maximum amount.When the form is submitted, and the input exceeds the maximum length, it throws an alert warning.Next, the UI of the form:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081exportdefaultfunctionTweetForm(){//return(<Container>{isReplying&&expanded&&(<span className="reply-to">Replying to<span className="reply-to--name">@{replyingTo}</span></span>)}<Form
        minHeight={minHeight+'px'}inline={!expanded}className={className}onSubmit={submit}><div className="user"><img src={user.image}alt=""/></div><div className="input-section"><textarea
            ref={inputRef}onChange={(e)=>setText(e.target.value)}placeholder={placeholder}value={text}onClick={onClick}/><div className="actions">{expanded&&actions.map((action)=>{return(<button
                    type="button"disabled={action.id==='location'&&'disabled'}key={action.id}><action.Icon size={19}color="var(--theme-color)"/></button>)})}<div className="right">{!isInputEmpty&&(<div className="tweet-length"><ProgressRing
                    stroke={2.2}color={exceededMax?'red':maxAlmostReached?'#ffd400':'var(--theme-color)'}radius={maxAlmostReached?19:14}progress={percentage}/>{maxAlmostReached&&(<span
                      className={classNames('tweet-length__text',exceededMax&&'red')}>{charsLeft}</span>)}</div>)}{!isInputEmpty&&<hr className="divider"/>}<button
                type="submit"className="submit-btn"disabled={isInputEmpty}>{submitText}</button></div></div></div></Form></Container>)}Create the Left SectionBuilding your own app?Get early access to ourLivestreamorVideo Calling APIand launch in days!The left section shows the different navigation links, the "Tweet" button, and the user icon at the bottom.Create a new file calledsrc/components/LeftSide.js. Add the following imports:js12345678910111213141516importclassNamesfrom'classnames'import{useEffect,useState}from'react'import{useStreamContext}from'react-activity-feed'import{Link,useLocation}from'react-router-dom'importstyledfrom'styled-components'importLoadingIndicatorfrom'./LoadingIndicator'importBellfrom'./Icons/Bell'importGroupfrom'./Icons/Group'importHomefrom'./Icons/Home'importHashtagfrom'./Icons/Hashtag'importMailfrom'./Icons/Mail'importBookmarkfrom'./Icons/Bookmark'importUserfrom'./Icons/User'importMorefrom'./Icons/More'importTwitterfrom'./Icons/Twitter'useStreamContextis an exported custom hook from thereact-activity-feedlibrary, which exposes context data from theStreamAppcomponent you added inApp.js. From this hook, you can get the logged-in user details.You will use theuseLocationhook to get information about the URL path, which can be useful for getting the active link.Next, the styles:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147constContainer=styled.div`display: flex;
  flex-direction: column;
  padding: 0 30px;
  height: 100%;

  .header {
    padding: 15px;
  }

  .buttons {
    margin-top: 5px;
    max-width: 200px;

    a,
    button {
      display: block;
      margin-bottom: 12px;
      color: white;
      padding: 10px 15px;
      display: flex;
      align-items: center;
      border-radius: 30px;
      font-size: 18px;
      padding-right: 25px;
      text-decoration: none;
      --icon-size: 25px;

      .btn--icon {
        margin-right: 15px;
        height: var(--icon-size);
        width: var(--icon-size);

        position: relative;
        .notifications-count {
          position: absolute;
          font-size: 11px;
          /* min-width: 14px; */
          background-color: var(--theme-color);
          top: -5px;
          padding: 1px 5px;
          border-radius: 10px;
          left: 0;
          right: 0;
          margin: 0 auto;
          width: max-content;
        }
      }

      &.active {
        font-weight: bold;

        img {
          --size: 27px;
        }
      }

      &:hover {
        background-color: #333;
      }

      &.btn--home {
        position: relative;
        &.new-tweets::after {
          content: '';
          position: absolute;
          width: 5px;
          height: 5px;
          left: 35px;
          top: 7px;
          border-radius: 50%;
          background-color: var(--theme-color);
        }
      }

      &.btn--more {
        svg {
          border: 1px solid #fff;
          border-radius: 50%;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }
    }
  }

  .tweet-btn {
    background-color: var(--theme-color);
    margin-top: 10px;
    border-radius: 30px;
    color: white;
    text-align: center;
    padding: 15px 0;
    font-size: 16px;
  }

  .profile-section {
    margin-top: auto;
    margin-bottom: 20px;
    padding: 10px;
    display: flex;
    text-align: left;
    align-items: center;
    justify-content: space-between;
    border-radius: 30px;

    &:hover {
      background-color: #333;
    }

    .details {
      display: flex;
      align-items: center;
      &__img {
        margin-right: 10px;
        width: 40px;
        border-radius: 50%;
        height: 40px;
        overflow: hidden;

        img {
          width: 100%;
          height: 100%;
        }
      }

      &__text {
        span {
          display: block;
        }

        &__name {
          color: white;
          font-size: 16px;
          font-weight: bold;
        }

        &__id {
          font-size: 14px;
          margin-top: 2px;
          color: #aaa;
        }
      }
    }
  }`Add the following to theLeftSide.jsfile:js12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455exportdefaultfunctionLeftSide({onClickTweet}){constlocation=useLocation()const{userData}=useStreamContext()const[newNotifications,setNewNotifications]=useState(0)if(!userData)return(<Container><LoadingIndicator/></Container>)constmenus=[{id:'home',label:'Home',Icon:Home,link:'/home',},{id:'explore',label:'Explore',Icon:Hashtag,},{id:'communities',label:'Communities',Icon:Group,},{id:'notifications',label:'Notifications',Icon:Bell,link:'/notifications',value:newNotifications,},{id:'messages',label:'Messages',Icon:Mail,},{id:'bookmarks',label:'Bookmarks',Icon:Bookmark,},{id:'profile',label:'Profile',Icon:User,link:`/${userData.id}`,},]}The component receives anonClickTweetmethod prop which is called when the "Tweet" button is clicked.First, you get the user object fromuseStreamContext. Also, you keep track of the notifications state.Note:Part 3 of this series focuses on implementing notifications.You also show theLoadingIndicatorcomponent if theuserDataobject isundefined.And you have the menu list. Now, for the UI:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263functionApp({onClickTweet}){// ...return(<Container><Link to="/"className="header"><Twitter color="white"size={25}/></Link><div className="buttons">{menus.map((m)=>{constisActiveLink=location.pathname===`/${m.id}`||(m.id==='profile'&&location.pathname===`/${userData.id}`)return(<Link
              to={m.link??'#'}className={classNames(`btn--${m.id}new-tweets`,isActiveLink&&'active')}key={m.id}onClick={m.onClick}><div className="btn--icon">{newNotifications&&m.id==='notifications'?(<span className="notifications-count">{newNotifications}</span>):null}<m.Icon fill={isActiveLink}color="white"size={25}/></div><span>{m.label}</span></Link>)})}<button className="btn--more"><div className="btn--icon"><More color="white"size={20}/></div><span>More</span></button></div><button onClick={onClickTweet}className="tweet-btn">Tweet</button><button className="profile-section"><div className="details"><div className="details__img"><img src={userData.image}alt=""/></div><div className="details__text"><span className="details__text__name">{userData.name}</span><span className="details__text__id">@{userData.id}</span></div></div><div><More color="white"/></div></button></Container>)}For the link, you determine if it is active if theidof the item in the menu is the same as thepathnameof the URL. For the profile, you check if thepathnameof the URL is the same as theidof the logged-in user.With the left side done, you can proceed to the right side of the layout. The right side has a "Follow" button, so first, create a shareable follow button component.Create the Right SectionThe right section shows the search input, the "Trends for you" block, and the "Who to follow" block.Create a new filesrc/components/RightSide.js. Add the following imports:js12345678910importclassNamesfrom'classnames'import{useState}from'react'import{useStreamContext}from'react-activity-feed'import{Link}from'react-router-dom'importstyledfrom'styled-components'importusersfrom'../users'importFollowBtnfrom'./FollowBtn'importMorefrom'./Icons/More'importSearchfrom'./Icons/Search'Next, you have the trends demo data:js123456789101112131415161718192021222324252627consttrends=[{title:'iPhone 12',tweetsCount:'11.6k',category:'Technology',},{title:'LinkedIn',tweetsCount:'51.1K',category:'Business & finance',},{title:'John Cena',tweetsCount:'1,200',category:'Sports',},{title:'#Microsoft',tweetsCount:'3,022',category:'Business & finance',},{title:'#DataSciencve',tweetsCount:'18.6k',category:'Technology',},]Now for the component:js12345678910exportdefaultfunctionRightSide(){const[searchText,setSearchText]=useState('')const{client}=useStreamContext()constwhoToFollow=users.filter((u)=>{// filter out currently logged in userreturnu.id!==client.userId})}You keep track of thesearchTextstate and also have thewhoToFollowarray, which is theusersarray with the currently logged-in user filtered out.For the UI, paste the following:js12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576exportdefaultfunctionRightSide(){// ...return(<Container><div className="search-container"><form className="search-form"><div className="search-icon"><Search color="rgba(85,85,85,1)"/></div><input
            onChange={(e)=>setSearchText(e.target.value)}value={searchText}/><button
            className={classNames(!Boolean(searchText)&&'hide','submit-btn')}type="button"onClick={()=>setSearchText('')}>X</button></form></div><div className="trends"><h2>Trendsforyou</h2><div className="trends-list">{trends.map((trend,i)=>{return(<div className="trend"key={trend.title+'-'+i}><div className="trend__details"><div className="trend__details__category">{trend.category}<span className="trend__details__category--label">Trending</span></div><span className="trend__details__title">{trend.title}</span><span className="trend__details__tweets-count">{trend.tweetsCount}Tweets</span></div><button className="more-btn"><More color="white"/></button></div>)})}</div></div><div className="follows"><h2>Who to follow</h2><div className="follows-list">{whoToFollow.map((user)=>{return(<div className="user"key={user.id}><Link to={`/${user.id}`}className="user__details"><div className="user__img"><img src={user.image}alt=""/></div><div className="user__info"><span className="user__name">{user.name}</span><span className="user__id">@{user.id}</span></div></Link><FollowBtn userId={user.id}/></div>)})}</div><span className="show-more-text">Show more</span></div></Container>)}The UI shows the search input and loops through thetrendsandwhoToFollowarray and displays them on the UI.Create the Layout ComponentTheLayoutcomponent shows the create tweet modal, so before the layout, create this component.Create a CreateTweetDialog ComponentCreate a new filesrc/components/Tweet/CreateTweetDialog.js. Start with the import and styles:js123456789101112131415161718importstyledfrom'styled-components'importModalfrom'../Modal'importTweetFormfrom'./TweetForm'constContainer=styled.div`.modal-block {
    margin-top: 20px;
    padding: 15px;
    width: 600px;
    height: max-content;
    z-index: 10;
  }

  .tweet-form {
    margin-top: 20px;
  }`The shareableTweetFormcomponent will be used in this component. Next, the UI:js123456789101112131415161718192021exportdefaultfunctionCreateTweetDialog({onClickOutside}){constonSubmit=async(text)=>{// create tweetonClickOutside()}return(<Container><Modal onClickOutside={onClickOutside}className="modal-block"><TweetForm
          onSubmit={onSubmit}shouldFocus={true}minHeight={240}className="tweet-form"placeholder="What's happening"/></Modal></Container>)}The create tweet function itself will be created in a few sections below; this is just the layout.Compose With the Layout ComponentWith theLeftSide,RightSide, and tweet modal components ready, you can create theLayoutcomponent.Create a new filesrc/components/Layout.js. Add the imports:js12345678import{useState}from'react'import{useStreamContext}from'react-activity-feed'importstyledfrom'styled-components'importLeftSidefrom'./LeftSide'importCreateTweetDialogfrom'./Tweet/CreateTweetDialog'importRightSidefrom'./RightSide'importLoadingIndicatorfrom'./LoadingIndicator'The styles:js123456789101112131415161718192021222324252627282930313233constContainer=styled.div`min-height: 100vh;
  background: black;
  --left: 300px;
  --right: 400px;
  --middle: calc(100% - var(--left) - var(--right));

  .content {
    max-width: 1300px;
    margin: 0 auto;
    width: 100%;
    display: flex;
  }

  .left-side-bar {
    height: 100vh;
    width: var(--left);
    position: sticky;
    top: 0;
  }

  .main-content {
    position: relative;
    width: var(--middle);
    border-left: 1px solid #333;
    border-right: 1px solid #333;
    min-height: 100vh;
  }

  .right-side-bar {
    width: var(--right);
  }`TheContainerstyled component has three style variables:--leftof300px,--rightof400px, and--middle, which is calculated by subtracting the left and right from 100%. The left section uses the left variable, and so for the right and the middle content.For the component:js12345678910111213141516171819202122232425262728293031exportdefaultfunctionLayout({children}){const{user}=useStreamContext()const[createDialogOpened,setCreateDialogOpened]=useState(false)if(!user)return<LoadingIndicator/>return(<>{createDialogOpened&&(<CreateTweetDialog
          onClickOutside={()=>setCreateDialogOpened(false)}/>)}<Container><div className="content"><div className="left-side-bar"><LeftSide onClickTweet={()=>setCreateDialogOpened(true)}/></div><main className="main-content">{!user?<LoadingIndicator/>:children}</main><div className="right-side-bar"><RightSide/></div><div/></div></Container></>)}TheLayoutcomponent manages acreateDialogOpenedstate which is updated totruewhen the "Tweet" button in theLeftSidecomponent is clicked.Create the HomeContent ComponentThis component will show the logged-in user's timeline. Their timeline shows the tweets of people they follow.TheHomeContentcomponent houses the top header, the tweet form below the header, and the timeline feed. Let us start from the header.Create the Home Top Header componentCreate a new filesrc/components/Home/MainHeader.jswith the following code:js12345678910111213141516171819202122232425262728importstyledfrom'styled-components'importStarfrom'../Icons/Star'constHeader=styled.header`display: flex;
  align-items: center;
  padding: 15px;
  color: white;
  width: 100%;
  font-weight: bold;
  justify-content: space-between;
  backdrop-filter: blur(2px);
  background-color: rgba(0, 0, 0, 0.5);

  h1 {
    font-size: 20px;
  }`exportdefaultfunctionMainHeader(){return(<Header><h1>Home</h1><Star color="white"/></Header>)}Create the CreateTweetTop componentTheCreateTweetTopcomponent shows the tweet form below the header. This component will also use the shareableTweetFormcomponent.Create a new file,src/components/Home/CreateTweetTop.jswith the following code:js12345678910111213141516171819importstyledfrom'styled-components'importTweetFormfrom'../Tweet/TweetForm'constContainer=styled.div`padding: 15px;`exportdefaultfunctionCreateTweetTop(){constonSubmit=async(text)=>{// create tweet here}return(<Container><TweetForm placeholder="What's happening?"onSubmit={onSubmit}/></Container>)}TheonSubmitmethod does nothing for you. Later on in this article, you will add the functionality to create a tweet. For now, let us focus on the layout.For the remaining part of theHomeContentcomponent, you also need a tweet block that shows a tweet's information, actor details, and reactions.Create the TweetBlock componentTheTweetBlockcomponent is broken down into three elements:TweetActorName, the tweet's content, and aCommentDialogmodal component.Create the TweetActorName componentTheTweetActorNameis a shared component that shows thenameandidof an actor. It also shows the time (hours difference or date) that the tweet was made. Create a new file calledsrc/components/Tweet/TweetActorName.js.Add the imports and styles:js123456789101112131415161718192021222324252627282930313233343536373839import{format}from'date-fns'import{Link}from'react-router-dom'importstyledfrom'styled-components'constTextBlock=styled(Link)`display: flex;

  &:hover .user--name {
    text-decoration: underline;
  }

  .user {
    &--name {
      color: white;
      font-weight: bold;
    }
    &--id {
      margin-left: 5px;
      color: #777;
    }
  }
  .tweet-date {
    margin-left: 15px;
    color: #777;
    position: relative;

    &::after {
      content: '';
      width: 2px;
      height: 2px;
      background-color: #777;
      position: absolute;
      left: -8px;
      top: 0;
      bottom: 0;
      margin: auto 0;
    }
  }`And the component:js123456789101112131415161718192021222324exportdefaultfunctionTweetActorName({time,name,id}){consttimeDiff=Date.now()-newDate(time).getTime()// convert ms to hoursconsthoursBetweenDates=timeDiff/(60*60*1000)constlessThan24hrs=hoursBetweenDates<24constlessThan1hr=hoursBetweenDates<1consttimeText=lessThan1hr?format(timeDiff,'m')+'m':lessThan24hrs?format(timeDiff,'H')+'h':format(newDate(time),'MMM d')return(<TextBlock to={`/${id}`}><span className="user--name">{name}</span><span className="user--id">@{id}</span><span className="tweet-date">{timeText}</span></TextBlock>)}The time is interpreted in three ways. If it is less than one hour, it shows as "[X]m". If it is less than twenty-four hours, it shows as "[X]h". And if it is none of these conditions, it is displayed as "Month Date".Create a Tweet Link Generator UtilityTweet links usually exist in this format:/{username}/status/{tweet-id}/. You will create a reusable function that generates a link like this.Create a new filesrc/utils/links.jswith the following code:js123exportfunctiongenerateTweetLink(actorId,tweetActivityId){return`/${actorId}/status/${tweetActivityId}`}Create a Text Formatter Utility for LinksBecause texts can contain links, hashtags, and mentions, you will create a utility for formatting such texts and replacing some of the texts with anchor tags.Create a new filesrc/utils/string.js. And add the following function:js123456789101112131415161718192021222324252627282930exportfunctionformatStringWithLink(text,linkClass,noLink=false){// regex to match links, hashtags and mentionsconstregex=/((https?:\/\/\S*)|(#\S*))|(@\S*)/giconstmodifiedText=text.replace(regex,(match)=>{leturl,labelif(match.startsWith('#')){// it is a hashtagurl=match
      label=match}elseif(match.startsWith('@')){// it is a mentionurl=`/${match.replace('@','')}`label=match}else{// it is a linkurl=match
      label=url.replace('https://','')}consttag=noLink?'span':'a'return`<${tag}class="${noLink?'':linkClass}" href="${url}">${label}</${tag}>`})returnmodifiedText}This utility returns an HTML string that can be embedded into an element.Create the CommentDialog ComponentTheCommentDialogmodal popups up when the comment icon is clicked on a tweet block:This dialog will be used to add a comment to a tweet. Create a new filesrc/components/Tweet/CommentDialog. Let us start with the imports and styles:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106importstyledfrom'styled-components'import{formatStringWithLink}from'../../utils/string'importModalfrom'../Modal'importTweetActorNamefrom'./TweetActorName'importTweetFormfrom'./TweetForm'constContainer=styled.div`.modal-block {
    padding: 15px;
    width: 600px;
    height: max-content;
  }`constBlockContent=styled.div`.tweet {
    margin-top: 30px;
    display: flex;
    position: relative;

    &::after {
      content: '';
      background-color: #444;
      width: 2px;
      height: calc(100% - 35px);
      position: absolute;
      left: 20px;
      z-index: 0;
      top: 45px;
    }

    .img {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      margin-right: 15px;
      border-radius: 50%;
      overflow: hidden;

      img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
    }

    .details {
      .actor-name {
        font-size: 15px;
        &--name {
          color: white;
          font-weight: bold;
        }

        &--id {
          color: #888;
        }
      }

      .tweet-text {
        color: white;
        margin-top: 3px;
        font-size: 14px;
      }

      .replying-info {
        color: #555;
        display: flex;
        margin-top: 20px;
        font-size: 14px;

        &--actor {
          margin-left: 5px;
          color: var(--theme-color);
        }
      }
    }
  }

  .comment {
    display: flex;
    margin-top: 20px;

    .img {
      width: 35px;
      height: 35px;
      margin-left: 3px;
      border-radius: 50%;
      margin-right: 15px;
      border-radius: 50%;
      overflow: hidden;

      img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }
    }

    .comment-form {
      flex: 1;
      height: 120px;
    }
  }`This component uses the shareableTweetFormandTweetActorNamecomponents.Next, the component:js1234567891011121314151617exportdefaultfunctionCommentDialog({activity,onPostComment,onClickOutside,}){const{object:{data:tweet},}=activityconsttweetActor=activity.actorconstonSubmit=async(text)=>{awaitonPostComment(text)onClickOutside()}}This component receives three props:activity: The active activity that the comment should be added toonPostComment: A function called with thetextargument when the submit button from theTweetFormcomponent is calledonClickOutside: A function called when the backdrop of the modal is calledNow, for the UI:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051exportdefaultfunctionCommentDialog({// ...}){// ...return(<Container><Modal onClickOutside={onClickOutside}className="modal-block"><BlockContent><div className="tweet"><div className="img"><img src={tweetActor.data.image}alt=""/></div><div className="details"><TweetActorName
                time={activity.time}name={tweetActor.data.name}id={tweetActor.data.id}/><p
                className="tweet-text"dangerouslySetInnerHTML={{__html:formatStringWithLink(tweet.text,'tweet__text--link',true).replace(/\n/g,'<br/>'),}}/><div className="replying-info">Replying to{' '}<span className="replying-info--actor">@{tweetActor.id}</span></div></div></div><div className="comment"><TweetForm
              className="comment-form"submitText="Reply"placeholder="Tweet your reply"onSubmit={onSubmit}shouldFocus={true}/></div></BlockContent></Modal></Container>)}Composing the TweetBlock ComponentWith the required components created, you can now compose this component.Create a new file,src/components/Tweet/TweetBlock.js. Start with the imports:js123456789101112131415importclassNamesfrom'classnames'import{useState}from'react'import{useStreamContext}from'react-activity-feed'import{useNavigate}from'react-router-dom'importstyledfrom'styled-components'import{formatStringWithLink}from'../../utils/string'importCommentDialogfrom'./CommentDialog'importCommentfrom'../Icons/Comment'importHeartfrom'../Icons/Heart'importRetweetfrom'../Icons/Retweet'importUploadfrom'../Icons/Upload'importMorefrom'../Icons/More'importTweetActorNamefrom'./TweetActorName'import{generateTweetLink}from'../../utils/links'Next, paste the styles:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081constBlock=styled.div`display: flex;
  border-bottom: 1px solid #333;
  padding: 15px;

  .user-image {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    overflow: hidden;
    margin-right: 10px;

    img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
  }

  .tweet {
    flex: 1;
    .link {
      display: block;
      padding-bottom: 5px;
      text-decoration: none;
      width: 100%;
    }

    &__text {
      color: white;
      font-size: 15px;
      line-height: 20px;
      margin-top: 3px;
      width: 100%;

      &--link {
        color: var(--theme-color);
        text-decoration: none;
      }
    }

    &__actions {
      display: flex;
      justify-content: space-between;
      margin-top: 5px;

      button {
        display: flex;
        align-items: center;
      }

      &__value {
        margin-left: 10px;
        color: #666;

        &.colored {
          color: var(--theme-color);
        }
      }
    }

    &__image {
      margin-top: 20px;
      border-radius: 20px;
      border: 1px solid #333;
      overflow: hidden;
      width: calc(100% + 20px);

      width: 100%;
      height: 100%;
      object-fit: cover;
      object-position: center;
    }
  }

  .more {
    width: 40px;
    height: 40px;
    display: flex;
  }`Then the component:js123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657exportdefaultfunctionTweetBlock({activity}){const{user}=useStreamContext()constnavigate=useNavigate()const[commentDialogOpened,setCommentDialogOpened]=useState(false)constactor=activity.actorlethasLikedTweet=falseconsttweet=activity.object.data// check if current logged in user has liked tweetif(activity?.own_reactions?.like){constmyReaction=activity.own_reactions.like.find((l)=>l.user.id===user.id)hasLikedTweet=Boolean(myReaction)}constonToggleLike=()=>{// toggle like reaction}constactions=[{id:'comment',Icon:Comment,alt:'Comment',value:activity?.reaction_counts?.comment||0,onClick:()=>setCommentDialogOpened(true),},{id:'retweet',Icon:Retweet,alt:'Retweet',value:0,},{id:'heart',Icon:Heart,alt:'Heart',value:activity?.reaction_counts?.like||0,onClick:onToggleLike},{id:'upload',Icon:Upload,alt:'Upload',},]consttweetLink=activity.id?generateTweetLink(actor.id,activity.id):'#'constonPostComment=async(text)=>{// create comment}}ThehasLikedTweetvariable is a boolean that indicates if the currently logged-in user has liked the current tweet. To find this information, you check thelikeobject of theown_reactionsobject of theactivity. Thelikeobject holds an array of objects which contains information about users that have added alikereaction to an activity.TheonToggleLikeandonPostCommentfunctions do nothing just yet. Part 3 covers adding reactions.Next for this component is the UI:js12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576exportdefaultfunctionTweetBlock({activity}){//...return(<><Block><div className="user-image"><img src={actor.data.image}alt=""/></div><div className="tweet"><button onClick={()=>navigate(tweetLink)}className="link"><TweetActorName
              name={actor.data.name}id={actor.id}time={activity.time}/><div className="tweet__details"><p
                className="tweet__text"dangerouslySetInnerHTML={{__html:formatStringWithLink(tweet.text,'tweet__text--link').replace(/\n/g,'<br/>'),}}/></div></button><div className="tweet__actions">{actions.map((action)=>{return(<button
                  onClick={(e)=>{e.stopPropagation()action.onClick?.()}}key={action.id}type="button"><action.Icon
                    color={action.id==='heart'&&hasLikedTweet?'var(--theme-color)':'#777'}size={17}fill={action.id==='heart'&&hasLikedTweet&&true}/><span
                    className={classNames('tweet__actions__value',{colored:action.id==='heart'&&hasLikedTweet,})}>{action.value}</span></button>)})}</div></div><button className="more"><More color="#777"size={20}/></button></Block>{activity.id&&commentDialogOpened&&(<CommentDialog
          onPostComment={onPostComment}shouldOpen={commentDialogOpened}onClickOutside={()=>setCommentDialogOpened(false)}activity={activity}/>)}</>)}This UI shows the tweet block with the action buttons (comment, like) and the comment dialog when it is active. On submitting the tweet form in the comment dialog, nothing happens for now. You will add this functionality in Part 3.Creating the Timeline ComponentTheTimelinecomponent shows the tweets made by the users the currently logged-in user follows:Since we haven't added the follow feature yet, you will create this component to show the tweets made by the currently logged-in user.Create a new filesrc/components/Home/Timeline.jswith the following code:js12345678910111213import{FlatFeed,useStreamContext}from'react-activity-feed'importTweetBlockfrom'../Tweet/TweetBlock'exportdefaultfunctionTimeline(){const{user}=useStreamContext()return(<div><FlatFeed Activity={TweetBlock}userId={user.id}feedGroup="user"/></div>)}TheFlatFeedcomponent allows you to pass a customActivitycomponent using theActivityprop.Also, in theFlatFeedcomponent, you can use the "timeline" or "user"feedGroup. The "timeline" shows a feed of activities made by the users a user follows. While the "user", similar to a user's profile page, shows a feed of activities made by a particular user (the logged-in user in our case). For now, we will leave this as "user". You will change this to "timeline" when you add the follow feature.Composing the HomeContent ComponentYou can now compose theHomeContentcomponent with the dialog, utilities, timeline, and other components created.Create a new filesrc/components/Home/HomeContent.js. Add the import and styles:js123456789101112131415161718192021222324252627282930313233importstyledfrom'styled-components'import{Feed,useStreamContext}from'react-activity-feed'importCreateTweetTopfrom'./CreateTweetTop'importMainHeaderfrom'./MainHeader'importTimelinefrom'../Home/Timeline'importLoadingIndicatorfrom'../LoadingIndicator'constContainer=styled.div`.header {
    position: sticky;
    top: 0;
    z-index: 1;
  }

  .create-tweet-top {
    border-bottom: 1px solid #333;
  }

  .new-tweets-info {
    border-bottom: 1px solid #333;
    padding: 20px;
    text-align: center;
    color: var(--theme-color);
    display: block;
    width: 100%;
    font-size: 16px;

    &:hover {
      background: #111;
    }
  }`TheFeedcomponent does not add anything to the UI. It provides feed data and methods such that the children of these components can create tweets in the user's feed.Next, the component:js1234567891011121314151617181920212223242526exportdefaultfunctionHomeContent(){const{client}=useStreamContext()constuser=client.currentUser.dataif(!user)return(<Container><LoadingIndicator/></Container>)return(<Container><div className="header"><MainHeader/></div><Feed feedGroup="user"><div className="create-tweet-top"><CreateTweetTop/></div><Timeline/></Feed></Container>)}Create a Page Component for the HomepageWith the layout and home content components ready, you can now create a page for the home content.Create a new filesrc/pages/HomePage.jswith the following code:js12345678910importLayoutfrom'../components/Layout'importHomeContentfrom'../components/Home/HomeContent'exportdefaultfunctionHome(){return(<Layout><HomeContent/></Layout>)}InApp.js, add a route for the home page like this:js12// other importsimportHomePagefrom'./pages/HomePage'js12// other routes<Route element={<HomePage/>}path="/home"/>With your development server on, when you go tolocalhost:3000/home, you will see the Homepage result.When you click the "Tweet" button on the left section, you can also see the create tweet modal.For now, you cannot see the comment dialog as the tweet block is not in use. Next, I will walk you through adding the create tweet feature so you can see the other components at work.Add a Create Tweet FeatureIn this section, you add the create tweet feature that allows users to create tweets. After adding this feature and using it, you can see theTweetBlockcomponents in theTimelinecomponent.Create a Custom useTweet HookThe tweet feature can be triggered from theCreateTweetDialogand theCreateTweetTopcomponents. Creating a custom hook for this feature makes things manageable.Create a new filesrc/hooks/useTweet.jswith the following code:js123456789101112131415161718192021import{nanoid}from'nanoid'import{useStreamContext}from'react-activity-feed'exportdefaultfunctionuseTweet(){const{client}=useStreamContext()constuser=client.feed('user',client.userId)constcreateTweet=async(text)=>{constcollection=awaitclient.collections.add('tweet',nanoid(),{text})awaituser.addActivity({verb:'tweet',object:`SO:tweet:${collection.id}`,})}return{createTweet,}}In this hook, you retrieve theclientobject fromuseStreamContext. With theclientobject, you can instantiate the user's feed.ThecreateTweetfunction receives atextargument, and in that function, you create a tweet collection with thetextdata in an object. Then, you create an activity on theuser's feed, with the collectionidpassed to theobjectproperty. This property receives a reference to a collection, which you have specified as a tweet reference, and the collection'sid.Now you can use thecreateTweetfunction in other components.Add the useTweet Hook to the CreateTweetDialog ComponentIn thesrc/components/Tweet/CreateTweetDialog.jscomponent file, import the hook:js12// other importsimportuseTweetfrom'../../hooks/useTweet'Then, use the hook in the component:js1234567891011exportdefaultfunctionCreateTweetDialog({onClickOutside}){const{createTweet}=useTweet()constonSubmit=async(text)=>{createTweet(text)onClickOutside()}return// the UI}Add the useTweet Hook to the CreateTweetTop ComponentIn thesrc/components/Home/CreateTweetTop.jscomponent file, import the hook:js12// other importsimportuseTweetfrom'../../hooks/useTweet'Then, use the hook in the component:js123456789exportdefaultfunctionCreateTweetTop(){const{createTweet}=useTweet()constonSubmit=async(text)=>{createTweet(text)}return// the UI}And now, you can create tweets. Click on "Tweet" in the left section of the screen, and create your first tweet in the modal.On submitting and refreshing, you will see the homepage showing the new tweet.ConclusionIn this tutorial, you have successfully created the Twitter layout using the React Activity Feed SDK. This project currently allows a user to select a profile and authenticate them with the feeds application in your Stream dashboard. It also includes reusable components, and the create tweet feature.Next onthis series, check out part two where you learn how toadd a profile page for users and integrate the follow-user functionality.Integrating Video With Your App?We've built a Video and Audio solution just for you.Check out our APIs and SDKs.Learn more ->Table of contentsDeveloper SetupCreate Starter Page for Selecting UsersCreate the User HomepageAdd a Create Tweet FeatureConclusionGet Started!Activate your free Stream account today and start prototyping.Start Coding Free->Related LinksBuild Twitter CloneReact Feeds AppReact Gmail Spaces CloneFlutter Instagram CloneRecommended postsBuilding In-App Notifications in Next.jsExploring the Power and Potential of Stream’s Feeds APIMaking a Video Collaboration Platform With Flutter Feed SDKTwitter Clone Part 3: Adding Tweet Reactions and Showing NotificationsTwitter Clone Part 2: Creating a Profile Page and Following UsersTwitter Clone Part 1: Connecting Users to Stream Feeds and Creating a TweetBuild a Twitter Clone with Activity Feeds and ReactCode Your Own Instagram Clone With Flutter and FeedsProductsChat MessagingVideo & AudioActivity FeedsAI ModerationComparisonStream vs SendbirdSendbird AlternativesPricing BreakdownStream vs VonageTwilio AlternativesTwilio migrationDevelopersChat Messaging DocsVideo & Audio DocsActivity Feeds DocsAI Moderation DocsResourcesStack OverflowYouTubeGitHubFeatured tutorialsBuild a Twitter CloneWebRTC Tutorial SeriesCompanyHiringAbout UsCareersBlogCase StudiesEnterprisePress & AwardsGlossaryTrust CenterContact UsSalesSupportBug ReportStream is proudly designed, built and continually enhanced inBoulder, CO, andAmsterdam, NL; with a global remote team.System Status© Getstream.io, Inc. All Rights Reserved.TermsPrivacySecurity
Assistant: