チャットApp(chatroom)

前回匿名認証のみにし、ログアウトしたらユーザアカウントを消去するようにしました。

今回はチャットルームを作ります。前回からディレクトリの配置を以下のように変えました。

スクリーンショット 2020-08-22 21.32.37

動的ルーティングはディレクトリでも可能で、この場合[roomid]ディレクトリをURLで指定した場合はindex.tsxが読み込まれます。[roomid]/signinと指定すると[roomid]内のsignin.tsxが読み込まれます。このsignin.tsxは例えばURLを教えてもらったりしてこのURLにアクセスしたら強制的にリダイレクトしてログイン処理をしてもらう場所です。

[roomid]/index.tsx

import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import BasicHead from "../../../components/atom/head";
import TitleLogo from "../../../components/atom/logo";
import BasicButton from "../../../components/atom/button";
import BasicH2 from "../../../components/atom/basicH2";
import UserField from "../../../components/compo/userField";
import Styles from "../../../styles/chatroom.module.css";
import { activeUserExist, getActiveUser } from "../../../functions/auth";
import { getChatroomFromFirestore, ChatroomType } from "../../../functions/database";
export default function Room() {
   const [ user, setUser] = useState<firebase.User>();
   const [ room, setRoom] = useState<ChatroomType>();
   const router = useRouter();
   // userの存在を確認
   const checkUser = async() => {
       if (await activeUserExist()) {
           const usr = getActiveUser();
           setUser(usr);
       } else {
           console.log("no user");
       }
   }
   // roomの存在を確認
   const checkroom = async() => {
       const path = location.pathname.split("/chatroom/")[1];
       console.log(path);
       const [msg, rm] = await getChatroomFromFirestore(path);
       if (msg === "get chatroom successfully!") {
           setRoom(rm);
           console.log(rm);
       }
   }
   useEffect(() => {
       (async() => {
           await checkUser();
           await checkroom();
           console.log("checked");
       })();
   }, []);
   
   const chatArea = () => {
       if (room) {
           return (
               <div className={ Styles.chatareaInner }>
                   <BasicH2>{ room.owner } のチャットルーム</BasicH2>
               </div>
           );
       }
       return (
           <div className={ Styles.chatareaInner }>
               <BasicH2>
                   このチャットルームは存在しないか、オーナーがログアウトして消去された可能性があります。
               </BasicH2>
           </div>
       );
   }
   if (!user) {
       return (
           <div>
               <BasicHead />
               <div className={ Styles.title }>
                   <TitleLogo />
               </div>
               <div className={ Styles.container }>
                   <BasicH2>どうやらあなたはログインが済んでいないようです。ここから匿名ログインをしてください</BasicH2>
                   <BasicButton
                       fullWidth ={ true }
                       onclick   ={ () => router.push("/chatroom/[roomid]/signin", location.pathname + "/signin") }
                   >匿名ログイン</BasicButton>
               </div>
           </div>
       );
   }
   
   return (
       <div>
           <BasicHead />
           <main>
               <div className={ Styles.title }>
                   <TitleLogo />
               </div>
               <div className={ Styles.toProfile }>
                   <BasicButton
                       onclick={ () => router.push("/profile/[username]", "/profile/" + user.displayName) }
                   >
                       プロフィールページへ
                   </BasicButton>
               </div>
               <div className={ Styles.area }>
                   <div className={ Styles.chatarea }>
                           { chatArea() }
                   </div>
               </div>
               <div className={ Styles.userField }>
                   <UserField
                       user={ user }
                   />
               </div>
           </main>
       </div>
   );
}

前回作った[roomid].tsxを書き換えています。まずuserの存在の有無(ここではログイン状態かどうか)で表示させるページを切り替えます。userがいない場合は、chatroom/[roomid]/signinに飛ぶボタンを表示し、そうでなければ、チャットルームのページを表示します。

またチャットルームが存在しない場合は、チャットルームのページを表示し、該当のチャットルームが存在しないメッセージを表示します。

[roomid]/signin.tsx

import { useRouter } from "next/router";
import { useState } from "react";
import BasicHead from "../../../components/atom/head";
import TitleLogo from "../../../components/atom/logo";
import BasicH2 from "../../../components/atom/basicH2";
import BasicParagraph from "../../../components/atom/basicP";
import BasicTextField from "../../../components/atom/textbox";
import BasicButton from "../../../components/atom/button";
import ContainerDiv from "../../../components/atom/containerDiv";
import { activeUserExist, getActiveUser, signinAnonymous } from "../../../functions/auth";
import { validationUsername } from "../../../functions/validation";
import { saveUserImage, getUserImageUrl } from "../../../functions/storage";
import Styles from "../../../styles/setting.module.css";
export default function SettingProfile() {
   const router = useRouter();
   const [username, setUsername] = useState("");
   const [UMessage, setUMessage] = useState(["ユーザネームを決めてください。", "#000000"]);
   const [photo   , setPhoto   ] = useState();
   const [disable , setDisable ] = useState(false);
   const handleUsernameChange = (event:React.ChangeEvent<{ value: unknown }>) => {
       setUsername(event.target.value as string);
       if (validationUsername(event.target.value as string)) {
           setUMessage(["OK!!", "#55c501"]);
       } else {
           setUMessage(["ユーザネームは1文字以上で指定してください。", "#ff0000"]);
       }
   }
   
   const handlePhoto = (event:React.ChangeEvent<{files: unknown}>) => {
       setPhoto(event.target.files[0]);
   }
   const saveUserdata = (user:firebase.User, fullPath:string) => {
       user.updateProfile({
           displayName: username,
           photoURL   : fullPath,
       })
       .then(() => {
           console.log("save userdata success!!");
       })
       .catch(error => {
           console.log(error);
       });
   }
   const handleSaveUserData = async() => {
       if (UMessage[0] === "OK!!") {
           setDisable(true);
           const bool = await signinAnonymous();
           if (bool) {
               if (await activeUserExist()) {
                   const user = await getActiveUser();
                   if (photo) {
                       const [bool, fullPath] = await saveUserImage(photo, user.uid);
                       if (bool) {
                           const [url, ] = await getUserImageUrl(fullPath);
                           saveUserdata(user, url);
                       }
                   } else {
                       saveUserdata(user, "");
                   }
                   await activeUserExist();
                   const path = location.pathname.split("/setting")[0]
                   router.push("/chatroom/[roomid]", path);
               } else {
                   alert("不具合でログインしていない状態になっているようです。もう一度登録処理からお願いします。");
                   setDisable(false);
               }
           } else {
               alert("不具合でログインしていない状態になっているようです。もう一度登録処理からお願いします。");
               setDisable(false);
           }
       }
   }
   return (
       <div>
           <BasicHead />
           <main>
               <div className={ Styles.title }>
                   <TitleLogo />
               </div>
               <div className={ Styles.box }>
                   <ContainerDiv>
                       <div className={ Styles.register }>
                           <BasicH2>ユーザー設定</BasicH2>
                       </div>
                       <div className={ Styles.paragraph }>
                           <BasicParagraph>
                               まず、ここでユーザネームとプロフィール写真を設定してください。ユーザネームは必ず指定してください。
                           </BasicParagraph>
                       </div>
                       <div className={ Styles.innerbox }>
                           <BasicTextField
                               label     ="username"
                               value     ={ username }
                               onchange  ={ handleUsernameChange }
                               fullWidth ={ true }
                           />
                           <div style={{color: UMessage[1] }}>
                               <BasicParagraph>
                                   { UMessage[0] }
                               </BasicParagraph>
                           </div>
                       </div>
                       <div className={ Styles.innerbox }>
                           <BasicButton  
                               fullWidth ={ true } 
                           >
                               <input 
                                   id        = "photoFile"
                                   type      ="file"
                                   accept    ="image/*"
                                   className ={ Styles.inputFile }
                                   onChange  ={ handlePhoto }
                               />
                           </BasicButton>
                       </div>
                       <div className={ Styles.innerbox }>
                           <BasicButton
                               fullWidth ={ true }
                               disabled  ={ disable }
                               onclick   ={ handleSaveUserData }
                           >
                               登録
                           </BasicButton>
                       </div>
                   </ContainerDiv>
               </div>
           </main>
       </div>
   );
}

コードの量が多いですが、これはprofile/setting.tsxをコピーして書き換えたものなので、基本的には、setting.tsxと一緒です。変更したのは匿名認証とユーザネーム、アイコンの登録をいっぺんにするようにし、認証に成功した際のリダイレクト先をもとのチャットルームにしています。

これで実行してみましょう。以下のように適当なチャットルームのパスを指定しアクセスします。(もちろんこのようなパスのルームは存在しませんが、アクセスは可能です)

スクリーンショット 2020-08-22 21.49.48

今はログインしていない状態なので以下のようなページが表示されます。スタイルの読み込みが上手く行ってないのかボタンの色が適用されていません。まー気にせず「匿名ログイン」をクリックします

スクリーンショット 2020-08-22 21.50.14

匿名認証ページのスクリーンショットを撮り忘れました。登録ボタンを押すと以下のもとのチャットページにリダイレクトします。ちゃんとユーザ情報が右下に表示されます。青い枠の中は、存在しないチャットルームを参照しているので存在しないメッセージが出ています。

スクリーンショット 2020-08-22 21.51.11

さて、次回はチャットを書き込む機能を作っていきます。長かったですがいよいよメインディッシュです。ただメインなだけあってやることはたくさんあります。特に書き込みを相手の画面にもリアルタイムで表示するにはどうすればいいかはまだわかっていないので調べながらになります。チャットルームごとではなく相手が書き込んだものだけを読み込むようにするのはとても難しそうな気がしますが、頑張ってみます!

中途半端ですが、ここまでのものをgithubに上げておきます。興味がある方は見てみてください。

この記事が気に入ったらサポートをしてみませんか?