Skip to content

Commit

Permalink
Merge pull request #19 from sryung1225/dev
Browse files Browse the repository at this point in the history
프로필 수정하기 기능 업그레이드 (#5 #9) & 공통 개편사항 적용 (#17 #18)
  • Loading branch information
sryung1225 authored Dec 26, 2023
2 parents 99dd6c2 + 5ce8f33 commit 6dad9cc
Show file tree
Hide file tree
Showing 14 changed files with 339 additions and 115 deletions.
133 changes: 133 additions & 0 deletions src/components/edit-profile-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, { useState } from 'react';
import { updateProfile } from 'firebase/auth';
import { doc, updateDoc } from 'firebase/firestore';
import {
deleteObject,
getDownloadURL,
ref,
uploadBytes,
} from 'firebase/storage';
import { auth, db, storage } from '../firebase.ts';
import IUser from '../interfaces/IUser.ts';
import CompressImage from '../utils/compress-image.tsx';
import useEscClose from '../utils/use-esc-close.tsx';
import * as S from '../styles/profile-form.ts';
import { ReactComponent as IconUser } from '../assets/images/i-user.svg';
import { ReactComponent as LoadingSpinner } from '../assets/images/loading-spinner-mini.svg';

interface IEditProfileForm extends Pick<IUser, 'userAvatar' | 'userName'> {
onClose: () => void;
}

export default function EditProfileForm({
userAvatar: initialAvatar,
userName: initialName,
onClose,
}: IEditProfileForm) {
const user = auth.currentUser;
const [isLoading, setLoading] = useState(false);
const [avatar, setAvatar] = useState<File | null>(null);
const [avatarPreview, setAvatarPreview] = useState(user?.photoURL);
const onAvatarChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const images = e.target.files;
if (images && images.length === 1) {
const selectedImage = images[0];
const compressedImage = await CompressImage({
imageFile: selectedImage,
size: 200,
});
setAvatar(compressedImage);
const previewUrl = compressedImage
? URL.createObjectURL(compressedImage)
: '';
setAvatarPreview(previewUrl);
}
};
const onAvatarDelete = () => {
setAvatar(null);
setAvatarPreview(null);
};

const [name, setName] = useState(initialName);
const onNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};

const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!user || isLoading || name === '') return;
try {
setLoading(true);
const userDocRef = doc(db, 'users', user.uid);
await updateDoc(userDocRef, {
userName: name,
});
await updateProfile(user, {
displayName: name,
});
const locationRef = ref(storage, `avatars/${user?.uid}`);
if (avatar) {
const result = await uploadBytes(locationRef, avatar);
const url = await getDownloadURL(result.ref);
await updateDoc(userDocRef, {
userAvatar: url,
});
await updateProfile(user, {
photoURL: url,
});
} else if (!avatar && initialAvatar && initialAvatar !== avatarPreview) {
await updateProfile(user, {
photoURL: '',
});
await deleteObject(locationRef);
await updateDoc(userDocRef, {
userAvatar: null,
});
}
setAvatarPreview(null);
setAvatar(null);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
onClose();
}
};
useEscClose(onClose);
return (
<S.Form onSubmit={onSubmit}>
{avatarPreview ? (
<>
<S.AttachAvatarPreview
src={avatarPreview}
alt="프로필이미지 미리보기"
width="120"
height="120"
/>
<S.AttachAvatarDelete type="button" onClick={onAvatarDelete} />
</>
) : (
<S.AttachAvatarButton htmlFor="avatar_edit">
<IconUser />
</S.AttachAvatarButton>
)}
<S.AttachAvatarInput
onChange={onAvatarChange}
id="avatar_edit"
type="file"
accept="image/*"
/>
<S.InputText
onChange={onNameChange}
name="name_edit"
type="text"
placeholder="이름을 입력해주세요"
value={name}
required
/>
<S.SubmitButton type="submit">
{isLoading ? <LoadingSpinner /> : '수정'}
</S.SubmitButton>
</S.Form>
);
}
23 changes: 16 additions & 7 deletions src/components/edit-tweet-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { auth, db, storage } from '../firebase.ts';
import ITweet from '../interfaces/ITweet.ts';
import CompressImage from '../utils/compress-image.tsx';
import useEscClose from '../utils/use-esc-close.tsx';
import * as S from '../styles/tweet-form.ts';
import { ReactComponent as IconPhoto } from '../assets/images/i-photo.svg';
import { ReactComponent as LoadingSpinner } from '../assets/images/loading-spinner-mini.svg';
Expand All @@ -25,12 +26,12 @@ export default function EditTweetForm({
}: IEditTweetForm) {
const [isLoading, setLoading] = useState(false);
const [tweet, setTweet] = useState(initialTweet);
const [image, setImage] = useState<File | null>(null);
const [imagePreview, setImagePreview] = useState(initialPhoto);

const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const onTweetChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setTweet(e.target.value);
};

const [image, setImage] = useState<File | null>(null);
const [imagePreview, setImagePreview] = useState(initialPhoto);
const onImageChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const images = e.target.files;
if (images && images.length === 1) {
Expand All @@ -50,6 +51,7 @@ export default function EditTweetForm({
setImage(null);
setImagePreview('');
};

const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const user = auth.currentUser;
Expand Down Expand Up @@ -85,18 +87,25 @@ export default function EditTweetForm({
onClose();
}
};
useEscClose(onClose);
return (
<S.EditForm onSubmit={onSubmit}>
<S.TextArea
onChange={onChange}
onChange={onTweetChange}
value={tweet}
rows={5}
maxLength={180}
placeholder="지금 무슨 일이 일어나고 있나요?"
required
/>
{imagePreview ? (
<>
<S.AttachImagePreview src={imagePreview} alt="첨부이미지 미리보기" />
<S.AttachImagePreview
src={imagePreview}
alt="첨부이미지 미리보기"
width="120"
height="120"
/>
<S.AttachImageDelete type="button" onClick={onImageDelete} />
</>
) : (
Expand All @@ -106,8 +115,8 @@ export default function EditTweetForm({
)}
<S.AttachImageInput
onChange={onImageChange}
type="file"
id="image_edit"
type="file"
accept="image/*"
/>
<S.SubmitButton type="submit">
Expand Down
15 changes: 3 additions & 12 deletions src/components/sign-in.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { FirebaseError } from 'firebase/app';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../firebase.ts';
import useEscClose from '../utils/use-esc-close.tsx';
import * as S from '../styles/auth.ts';
import * as P from '../styles/popup.ts';
import ImageComputer from '../assets/images/logo-small.png';
Expand All @@ -25,17 +26,6 @@ export default function SignIn({ onClose }: ISignInProps) {
const [userEmail, setUserEmail] = useState('');
const [userPassword, setUserPassword] = useState('');
const [firebaseError, setFirebaseError] = useState('');
useEffect(() => {
const handleEscKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleEscKey);
return () => {
document.removeEventListener('keydown', handleEscKey);
};
}, [onClose]);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { name, value },
Expand All @@ -62,6 +52,7 @@ export default function SignIn({ onClose }: ISignInProps) {
setLoading(false);
}
};
useEscClose(onClose);
return (
<P.PopupWrapper>
<P.Popup>
Expand Down
15 changes: 3 additions & 12 deletions src/components/sign-up.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { FirebaseError } from 'firebase/app';
import { createUserWithEmailAndPassword, updateProfile } from 'firebase/auth';
import { doc, setDoc } from 'firebase/firestore';
import { auth, db } from '../firebase.ts';
import useEscClose from '../utils/use-esc-close.tsx';
import * as S from '../styles/auth.ts';
import * as P from '../styles/popup.ts';
import ImageComputer from '../assets/images/logo-small.png';
Expand All @@ -27,17 +28,6 @@ export default function SignUp({ onClose }: ISignUpProps) {
const [userEmail, setUserEmail] = useState('');
const [userPassword, setUserPassword] = useState('');
const [firebaseError, setFirebaseError] = useState('');
useEffect(() => {
const handleEscKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
document.addEventListener('keydown', handleEscKey);
return () => {
document.removeEventListener('keydown', handleEscKey);
};
}, [onClose]);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const {
target: { name, value },
Expand Down Expand Up @@ -80,6 +70,7 @@ export default function SignUp({ onClose }: ISignUpProps) {
setLoading(false);
}
};
useEscClose(onClose);
return (
<P.PopupWrapper>
<P.Popup>
Expand Down
4 changes: 3 additions & 1 deletion src/components/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ export default function Timeline() {
}
};
}, []);
return (
return tweets.length !== 0 ? (
<S.TimelineWrapper>
{tweets.map((tweet) => (
<Tweet key={tweet.id} {...tweet} />
))}
</S.TimelineWrapper>
) : (
<S.Text>작성된 글이 없습니다.</S.Text>
);
}
2 changes: 1 addition & 1 deletion src/components/tweet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default function Tweet({
id={id}
tweet={tweet}
photo={photo}
onClose={() => setEditPopup(false)}
onClose={toggleEditPopup}
/>
</P.Popup>
</P.PopupWrapper>
Expand Down
61 changes: 61 additions & 0 deletions src/components/user-profile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useEffect, useState } from 'react';
import { doc, getDoc } from 'firebase/firestore';
import { auth, db } from '../firebase.ts';
import * as S from '../styles/profile.ts';
import * as P from '../styles/popup.ts';
import { ReactComponent as IconUser } from '../assets/images/i-user.svg';
import EditProfileForm from './edit-profile-form.tsx';

export default function UserProfile() {
const user = auth.currentUser;
const [userAvatar, setUserAvatar] = useState('');
const [userName, setUserName] = useState('');
useEffect(() => {
const fetchUserData = async () => {
if (!user) return;
const userDoc = await getDoc(doc(db, 'users', user?.uid));
if (userDoc.exists()) {
const userData = userDoc.data();
setUserAvatar(userData.userAvatar);
setUserName(userData.userName);
}
};
fetchUserData();
}, []);
const [editPopup, setEditPopup] = useState(false);
const toggleEditPopup = () => {
setEditPopup(!editPopup);
};
return (
<S.Profile>
<S.Avatar>
{userAvatar ? (
<S.AvatarImage
src={userAvatar}
alt="프로필 이미지"
width="120"
height="120"
/>
) : (
<IconUser />
)}
</S.Avatar>
<S.Name>{userName}</S.Name>
<S.EditButton onClick={toggleEditPopup} type="button">
프로필 수정
</S.EditButton>
{editPopup ? (
<P.PopupWrapper>
<P.Popup>
<P.CloseButton onClick={toggleEditPopup} type="button" />
<EditProfileForm
userAvatar={userAvatar}
userName={userName}
onClose={toggleEditPopup}
/>
</P.Popup>
</P.PopupWrapper>
) : null}
</S.Profile>
);
}
6 changes: 4 additions & 2 deletions src/components/user-timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
where,
} from 'firebase/firestore';
import { auth, db } from '../firebase.ts';
import ITweet from '../interfaces/ITweet.ts';
import Tweet from './tweet.tsx';
import * as S from '../styles/timeline.ts';
import ITweet from '../interfaces/ITweet.ts';

export default function UserTimeline() {
const user = auth.currentUser;
Expand Down Expand Up @@ -47,11 +47,13 @@ export default function UserTimeline() {
}
};
}, []);
return (
return tweets.length !== 0 ? (
<S.TimelineWrapper>
{tweets.map((tweet) => (
<Tweet key={tweet.id} {...tweet} />
))}
</S.TimelineWrapper>
) : (
<S.Text>작성된 글이 없습니다.</S.Text>
);
}
5 changes: 5 additions & 0 deletions src/interfaces/IUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default interface IUser {
userId: string;
userName: string;
userAvatar: string;
}
Loading

0 comments on commit 6dad9cc

Please sign in to comment.