Render Profile Badge Component:

  • create /twittme-web/src/profiles/lookup.js ```javascript import { backendLookup } from “../lookup”;

export function apiProfileDetail(username, callback) { backendLookup(“GET”, /profiles/${username}/, callback); }

  1. - create /twittme-web/src/profiles/badge.js
  2. ```javascript
  3. import React, { useState, useEffect } from "react";
  4. import { apiProfileDetail } from "./lookup";
  5. export function ProfileBadgeComponent(props) {
  6. const { username } = props;
  7. //lookup
  8. const [didLookup, setDidLookup] = useState(false);
  9. const [profile, setProfile] = useState(null);
  10. const handleBackendLookup = (response, status) => {
  11. if (status === 200) {
  12. setProfile(response);
  13. }
  14. };
  15. useEffect(() => {
  16. if (didLookup === false) {
  17. apiProfileDetail(username, handleBackendLookup);
  18. setDidLookup(true);
  19. }
  20. }, [username, didLookup, setDidLookup]);
  21. //if still looking up, show "Loading"
  22. //if find the user profile, show first name
  23. //if can't find the user profile, show nothing
  24. return didLookup === false ? "Loading..." : profile ? <span>{profile.first_name}</span> : null
  25. }
  • import and export ProfileBadge in /twittme-web/src/profiles/index.js

    1. import { UserPicture, UserDisplay, UserLink } from "./components";
    2. import { ProfileBadgeComponent } from "./badge"; // new
    3. export {
    4. ProfileBadgeComponent, // new
    5. UserPicture,
    6. UserDisplay,
    7. UserLink,
    8. };
  • render ProfileBadge in /twittme-web/src/index.js ```javascript import React from “react”; import ReactDOM from “react-dom”; import “./index.css”; import App from “./App”; import { ProfileBadgeComponent } from “./profiles”; // new import * as serviceWorker from “./serviceWorker”;

import { TweetsComponent, TweetDetailComponent, FeedComponent } from “./tweets”;

//…

//new const userProfileBadgeElements = document.querySelectorAll(“.tweetme-2-profile-badge”); //get all elements with this class

userProfileBadgeElements.forEach((container) => { ReactDOM.render(e(ProfileBadgeComponent, container.dataset), container); });

//…

  1. - hide feed block, add a div block with class "tweetme-2-profile-badge" in /twittme-web/public/index.html
  2. ```html
  3. <body>
  4. <noscript>You need to enable JavaScript to run this app.</noscript>
  5. <!--
  6. <div id="tweetme-2-feed"></div>
  7. -->
  8. <div class='d-none' id="tweetme-2-feed"></div>
  9. <div class='d-none' id='tweet-container'></div>
  10. <div class="tweetme-2-profile-badge" data-username="root"></div>
  11. <div class='d-none' id="tweetme-2" data-username="root" data-can-tweet="false"></div>
  12. <script>
  13. var path = window.location.pathname
  14. var idRegex = /(?<tweetid>\d+)/
  15. var match = path.match(idRegex)
  16. if(match) {
  17. var tweetId = match.groups.tweetid
  18. var tweetme2El = document.getElementById("tweetme-2")
  19. tweetme2El.className = 'd-none'
  20. var tweetEl = "<div class='tweetme-2-detail' data-tweet-id=" + tweetId + " data-class-name='col-6 mx-auto'></div>"
  21. var mainContainer = document.getElementById("tweet-container")
  22. mainContainer.innerHTML = tweetEl
  23. }
  24. </script>
  25. </body>
  • to avoid 403, uncomment ‘Twittme.rest_api.dev.DevAuthentication in /Twittme/settings.py
    1. if DEBUG:
    2. DEFAULT_RENDERER_CLASSES += [
    3. 'rest_framework.renderers.BrowsableAPIRenderer',
    4. ]
    5. DEFAULT_AUTHENTICATION_CLASSES += [
    6. 'Twittme.rest_api.dev.DevAuthentication'
    7. ]
    to test, npm start and runserver, access http://localhost:30/
    the page displays the first name of root in settings
    image.png

The Follow Button:

  • in /twittme-web/src/profiles/badge.js
    • make ProfileBadge into a function
    • see contents of the user ```javascript import React, { useState, useEffect } from “react”;

import { apiProfileDetail } from “./lookup”;

//new function ProfileBadge(props) { const { user } = props; console.log(user); return user ? {user.first_name} : null; }

export function ProfileBadgeComponent(props) { const { username } = props;

//lookup const [didLookup, setDidLookup] = useState(false); const [profile, setProfile] = useState(null);

const handleBackendLookup = (response, status) => { if (status === 200) { setProfile(response); } }; useEffect(() => { if (didLookup === false) { apiProfileDetail(username, handleBackendLookup); setDidLookup(true); } }, [username, didLookup, setDidLookup]);

//return didLookup === false ? “Loading…” : profile ? {profile.first_name} : null return didLookup === false ? ( “Loading…” ) : profile ? ( ) : null; }

  1. access localhost:30<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597612619777-a77b0660-cfa2-4e6a-a78e-b39c66b4d194.png#align=left&display=inline&height=416&margin=%5Bobject%20Object%5D&name=image.png&originHeight=832&originWidth=1889&size=78328&status=done&style=none&width=944.5)<br />Those are what we have in a profile badge.
  2. - update UserDisplay and UserPicture in /twittme-web/src/profiles/components.js
  3. - if hideLink == True, don't provide the user profile link
  4. ```javascript
  5. export function UserDisplay(props) {
  6. //const { user, includeFullName } = props;
  7. const { user, includeFullName, hideLink } = props;
  8. const nameDisplay =
  9. includeFullName === true ? `${user.first_name} ${user.last_name} ` : null;
  10. /*
  11. return <React.Fragment>
  12. {nameDisplay}
  13. <UserLink username={user.username}>@{user.username}</UserLink>
  14. </React.Fragment>
  15. */
  16. return <React.Fragment>
  17. {nameDisplay}
  18. {hideLink === true ? `@${user.username}` : <UserLink username={user.username}>@{user.username}</UserLink>}
  19. </React.Fragment>
  20. }
  21. export function UserPicture(props) {
  22. //const { user } = props;
  23. const {user, hideLink} = props
  24. const userIdSpan = <span className='mx-1 px-3 py-2 rounded-circle bg-dark text-white'>
  25. {user.username[0]}
  26. </span>
  27. /*
  28. return <UserLink username={user.username}>
  29. <span className="mx-1 px-3 py-2 rounded-circle bg-dark text-white">
  30. {user.username[0]}
  31. </span>
  32. </UserLink>
  33. */
  34. return hideLink === true ? userIdSpan : <UserLink username={user.username}>{userIdSpan}</UserLink>
  35. }
  • apply updated UserDisplay and UserPicture into /twittme-web/src/profiles/badge.js ```javascript import React, { useState, useEffect } from “react”;

import { apiProfileDetail } from “./lookup”;

import { UserDisplay, UserPicture } from “./components”; //new function ProfileBadge(props) { const { user } = props; console.log(user); //return user ? {user.first_name} : null; return user ? (

) : null; }

export function ProfileBadgeComponent(props) { const { username } = props;

//lookup const [didLookup, setDidLookup] = useState(false); const [profile, setProfile] = useState(null);

const handleBackendLookup = (response, status) => { if (status === 200) { setProfile(response); } }; useEffect(() => { if (didLookup === false) { apiProfileDetail(username, handleBackendLookup); setDidLookup(true); } }, [username, didLookup, setDidLookup]);

return didLookup === false ? ( “Loading…” ) : profile ? ( ) : null; }

  1. access [http://localhost:30/](http://localhost:30/)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597614903745-d69113fc-6ca4-49cf-9b10-13e49bdbddcc.png#align=left&display=inline&height=378&margin=%5Bobject%20Object%5D&name=image.png&originHeight=756&originWidth=1653&size=126115&status=done&style=none&width=826.5)<br />now we have a profile badge with a picture, first and last name, and username.
  2. Then we need a "Follow" button about this profile.
  3. - add that button in /twittme-web/src/profiles/badge.js
  4. ```javascript
  5. function ProfileBadge(props) {
  6. const { user } = props;
  7. console.log(user);
  8. //return user ? <span>{user.first_name}</span> : null;
  9. return user ? (
  10. <div>
  11. <UserPicture user={user} hideLink />
  12. <p>
  13. <UserDisplay user={user} includeFullName hideLink />
  14. </p>
  15. <button className='btn btn-primary'>{user.is_following ? "Unfollow" : "Follow"}</button>
  16. </div>
  17. ) : null;
  18. }

access http://localhost:30/
image.png
because now this access is following root, so the button is “Unfollow”, if not, it should be “Follow”.
click the button, and we get printed event details.
image.png

However, this button is not actually interacting with anything now.

  • in /twittme-web/src/profiles/badge.js
    • set state “didFollowToggle” and “profileLoading”
    • apply const “handleFollowToggle” ```javascript import React, { useState, useEffect } from “react”;

import { apiProfileDetail } from “./lookup”;

import { UserDisplay, UserPicture } from “./components”; //new

function ProfileBadge(props) { //const { user } = props; const { user, didFollowToggle, profileLoading } = props; console.log(user);

let currentVerb = user && user.is_following ? “Unfollow” : “Follow”; //new currentVerb = profileLoading ? “Loading…” : currentVerb; //new const handleFollowToggle = (event) => { //console.log(event); event.preventDefault(); //new //new, to make sure the button only can be clicked when the profile isn’t loading if (didFollowToggle && !profileLoading) { didFollowToggle(currentVerb); } };

/ return user ? (

) : null; / return user ? (

) : null; }

export function ProfileBadgeComponent(props) { const { username } = props;

//lookup const [didLookup, setDidLookup] = useState(false); const [profile, setProfile] = useState(null); const [profileLoading, setProfileLoading] = useState(false); //new

const handleBackendLookup = (response, status) => { if (status === 200) { setProfile(response); } }; useEffect(() => { if (didLookup === false) { apiProfileDetail(username, handleBackendLookup); setDidLookup(true); } }, [username, didLookup, setDidLookup]);

//new const handleNewFollow = (actionVerb) => { console.log(actionVerb); setProfileLoading(true); };

/ return didLookup === false ? ( “Loading…” ) : profile ? ( ) : null; / return didLookup === false ? ( “Loading…” ) : profile ? ( ) : null; }

  1. access localhost:30<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597653401576-2d16a370-2058-4d9e-bde5-bd14305c9bc2.png#align=left&display=inline&height=485&margin=%5Bobject%20Object%5D&name=image.png&originHeight=969&originWidth=1844&size=180888&status=done&style=none&width=922)<br />click "Unfollow"<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1597653435641-1a34bbf2-1e4d-43b6-8933-29c063c1f787.png#align=left&display=inline&height=503&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1006&originWidth=1908&size=194692&status=done&style=none&width=954)<br />so the actionVerb is "Unfollow", button becomes "Loading...", and we cannot click that button again (because it only allow not loading situation to progress)
  2. - To make a real interacting button
  3. - create apiProfileFollowToggle in /twittme-web/src/profiles/lookup.js
  4. ```javascript
  5. import { backendLookup } from "../lookup";
  6. export function apiProfileDetail(username, callback) {
  7. backendLookup("GET", `/profiles/${username}/`, callback);
  8. }
  9. //new
  10. export function apiProfileFollowToggle(username, action, callback) {
  11. const data = { action: `${action && action}`.toLowerCase() }; //of course, this is "follow" or "unfollow"
  12. backendLookup("POST", `/profiles/${username}/follow`, callback, data);
  13. }

apiProfileDetail and apiProfileFollowToggle are now related to the same part of data, but apiProfileFollowToggle apply POST to change “is_following”

  • change what in http://localhost/api/profiles/[username]/follow returns in /profiles/api/views.py

    1. @api_view(['GET', 'POST'])
    2. @permission_classes([IsAuthenticated])
    3. def user_follow_view(request, username, *args, **kwargs):
    4. me = request.user
    5. other_user_qs = User.objects.filter(username=username)
    6. if me.username == username:
    7. my_followers = me.profile.followers.all()
    8. return Response({"count": my_followers.count()}, status=200)
    9. if not other_user_qs.exists():
    10. return Response({}, status=404)
    11. other = other_user_qs.first()
    12. profile = other.profile
    13. data = request.data or {}
    14. action = data.get("action")
    15. if action == "follow": # if I am not following this profile
    16. profile.followers.add(me) # start following
    17. elif action == "unfollow": # else
    18. profile.followers.remove(me) # stop following
    19. else:
    20. pass
    21. # current_followers_qs = profile.followers.all()
    22. # return Response({"count": current_followers_qs.count()}, status=200)
    23. data = PublicProfileSerializer(instance=profile, context={"request": request})
    24. return Response(data.data, status=200)

    apiProfileFollowToggle above send an action “follow” or “unfollow”, and we change the status of following in line 19-24.

also, it change returns from only a count(following or not following)
image.png
to all data about that user
image.png

  • apply apiProfileFollowToggle to send the request of “Unfollow” and “Follow” in /twittme-web/src/profiles/badge.js ```javascript import React, { useState, useEffect } from “react”;

import { apiProfileDetail, apiProfileFollowToggle, //new, import from lookup } from “./lookup”;

import { UserDisplay, UserPicture } from “./components”;

function ProfileBadge(props) { const { user, didFollowToggle, profileLoading } = props; //console.log(user);

let currentVerb = user && user.is_following ? “Unfollow” : “Follow”; currentVerb = profileLoading ? “Loading…” : currentVerb; const handleFollowToggle = (event) => { //console.log(event); event.preventDefault(); if (didFollowToggle && !profileLoading) { didFollowToggle(currentVerb); } };

return user ? (

) : null; }

export function ProfileBadgeComponent(props) { const { username } = props;

//lookup const [didLookup, setDidLookup] = useState(false); const [profile, setProfile] = useState(null); const [profileLoading, setProfileLoading] = useState(false); //new

const handleBackendLookup = (response, status) => { if (status === 200) { setProfile(response); } }; useEffect(() => { if (didLookup === false) { apiProfileDetail(username, handleBackendLookup); setDidLookup(true); } }, [username, didLookup, setDidLookup]);

const handleNewFollow = (actionVerb) => { //console.log(actionVerb);

  1. //new, this username is "who I want to follow"
  2. apiProfileFollowToggle(username, actionVerb, (response, status)=>{
  3. console.log(response, status)
  4. if (status===200) {
  5. setProfile(response)
  6. // apiProfileDetail(username, handleBackendLookup)
  7. }
  8. setProfileLoading(false) //the profile is now loading here
  9. })
  10. setProfileLoading(true) //the profile loading is ended here

}

return didLookup === false ? ( “Loading…” ) : profile ? ( ) : null; } ``` to test, runserver and npm start, access http://localhost:30/
now the status is “cfe is following root” so the button is “Unfollow”
clicked “Unfollow” to unfollow
image.png
after a short “Loading…” time, the button becomes “Follow”, so now “cfe is not following root”
so does the response shown that “is_following: false”
click “Follow” again
image.png
After “Loading…”, we get back to the state at first, button is “Unfollow”, and “is_following: true”
image.png