Skip to content

Commit

Permalink
feat: find and manage friends, optimize source code
Browse files Browse the repository at this point in the history
  • Loading branch information
MeewMeew committed Oct 10, 2023
1 parent da5d9a7 commit 6bb4a65
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 76 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Official Website: [https://mb.mew.id.vn](https://mb.mew.id.vn)
- Like and comment on posts.
- Receive real-time notifications for updates.
- Edit your profile, including changing your avatar and cover image.
- Find and manage your friends.
- Enjoy a responsive design.

## Installation
Expand All @@ -39,6 +40,7 @@ npm install

- Create a Firebase project and add a web app to it.
- Copy the Firebase API key to the .env file:
- Backend source code: [MeewMeew/facebook-clone-be](https://github.com/MeewMeew/facebook-clone-be)

```bash
VITE_FIREBASE_CONFIG=your_config
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "facebook-clone",
"version": "1.0.4",
"version": "1.0.5",
"license": "MIT",
"scripts": {
"build": "run-p type-check build-only",
Expand Down
36 changes: 23 additions & 13 deletions src/components/User/UserHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ const props = defineProps({
user: {
type: Object as PropType<IUser>,
required: true
},
tab: {
type: String,
default: 'user',
required: false
}
})
Expand Down Expand Up @@ -264,32 +269,37 @@ onBeforeMount(async () => {
</div>
</div>

<div class="flex items-centerw-full border-t h-[50px] py-[4px]">
<button class="w-[85px]">
<div class="flex items-centerw-full border-t h-[50px] mb-2 pt-2 transition-all">
<div class="w-[85px] duration-150">
<router-link :to="{ name: 'user', params: { id: user.id } }"
class="flex items-center text-[15px] justify-center h-[45px] text-blue-500 hover:bg-[#F2F2F2] w-full font-medium rounded-lg cursor-pointer">
class="flex items-center text-[15px] justify-center h-[48px] hover:bg-[#F2F2F2] font-medium rounded-lg cursor-pointer"
:class="[tab === 'user' ? 'text-blue-500' : '']">
Bài viết
</router-link>
<div class="border-b-4 border-b-blue-400 rounded-md"></div>
</button>
<div v-if="tab === 'user'" class="border-t-4 border-t-blue-400 rounded-md w-full"></div>
</div>
<button
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] w-[85px] font-medium rounded-lg mx-1 cursor-pointer">
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] font-medium rounded-lg mx-1 cursor-pointer">
Giới thiệu
</button>
<router-link :to="{ name: 'fuser' }"
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] w-[85px] font-medium rounded-lg mx-1 cursor-pointer">
Bạn bè
</router-link>
<div class="w-[85px] duration-150">
<router-link :to="{ name: 'fuser' }"
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] font-medium rounded-lg mx-1 cursor-pointer"
:class="[tab === 'fuser' ? 'text-blue-500' : '']">
Bạn bè
</router-link>
<div v-if="tab === 'fuser'" class="border-t-4 border-t-blue-400 rounded-md w-full"></div>
</div>
<button
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] w-[85px] font-medium rounded-lg mx-1 cursor-pointer">
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] font-medium rounded-lg mx-1 cursor-pointer">
Ảnh
</button>
<button
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] w-[85px] font-medium rounded-lg mx-1 cursor-pointer">
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] font-medium rounded-lg mx-1 cursor-pointer">
Video
</button>
<button
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] w-[85px] font-medium rounded-lg mx-1 cursor-pointer">
class="flex items-center text-[15px] justify-center h-[48px] p-1 hover:bg-[#F2F2F2] font-medium rounded-lg mx-1 cursor-pointer">
Check in
</button>
</div>
Expand Down
88 changes: 88 additions & 0 deletions src/components/User/UserProfile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import Image from 'primevue/image';
import type { PropType } from 'vue';
import { onMounted, ref } from 'vue';
import { Attachment, Post } from '@/database';
import { useUser } from '@/stores/user';
import type { IAttachmentItem, IUser } from '@/types';
import CreatePostBox from '../Common/CreatePostBox.vue';
import PostList from '../Post/PostList.vue';
const props = defineProps({
user: {
type: Object as PropType<IUser>,
required: true
}
})
const { cuser } = storeToRefs(useUser())
const images = ref<{ id: number, image: string }[]>([])
onMounted(async () => {
const posts = await Post.getAll({
uid: props.user.id,
limit: 20,
sort: 'created_at',
order: 'desc',
attachment: true
})
for (const post of posts) {
if (post.attachment) {
const medium = await Attachment.image((post.attachment as IAttachmentItem).large)
if (images.value.length === 9) break;
else images.value.push({ id: post.id, image: medium })
}
}
})
</script>
<template>
<div class="w-full md:w-5/12 mt-4 mr-4">
<div class="bg-white p-3 rounded-lg shadow-lg">
<div class="font-bold pb-2 text-xl">Giới thiệu</div>
<div class="pb-5">
<button class="w-full bg-gray-200 hover:bg-gray-300 rounded-lg p-2 font-medium text-sm text-[#050505]">
Thêm tiểu sử
</button>
</div>
<div class="pb-5">
<button class="w-full bg-gray-200 hover:bg-gray-300 rounded-lg p-2 font-medium text-sm text-[#050505]">
Chỉnh sửa chi tiết
</button>
</div>
<div class="pb-5">
<button class="w-full bg-gray-200 hover:bg-gray-300 rounded-lg p-2 font-medium text-sm text-[#050505]">
Chỉnh sửa sở thích
</button>
</div>
<div>
<button class="w-full bg-gray-200 hover:bg-gray-300 rounded-lg p-2 font-medium text-sm text-[#050505]">
Thêm nội dung đáng chú ý
</button>
</div>
</div>

<div class="bg-white p-3 mt-4 rounded-lg shadow-lg sticky z-10 top-20">
<div class="font-bold pb-2 text-xl">Ảnh</div>
<div class="grid grid-cols-3">
<router-link :to="{ name: 'post', params: { id: i.id } }" v-for="i in images" :key="i.id" class="rounded-lg">
<Image :src="i.image" :pt="{
root: 'rounded-lg relative',
image: {
class: 'cursor-pointer border-white border-4 border-white aspect-square object-cover rounded-lg cursor-pointer'
}
}" />
</router-link>
</div>
</div>
</div>

<div class="w-full md:w-7/12 overflow-y-scroll overflow-x-hidden nice-scrollbar" v-if="cuser">
<CreatePostBox v-if="cuser.id === props.user.id" />
<PostList scope="user" :uid="props.user.id" :avatar="props.user?.photoURL" />
</div>
</template>
5 changes: 5 additions & 0 deletions src/helpers/protected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function protectEmail(email: string, char = '*') {
const [name, domain] = email.split('@')
const protectedEmail = `${name.slice(0, 3)}${char.repeat(name.length - 4)}@${domain}`
return protectedEmail
}
3 changes: 1 addition & 2 deletions src/layouts/MainNavLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ref } from 'vue'
import DotsGrid from 'vue-material-design-icons/DotsGrid.vue'
import FacebookMessenger from 'vue-material-design-icons/FacebookMessenger.vue'
import Logout from 'vue-material-design-icons/Logout.vue'
import Magnify from 'vue-material-design-icons/Magnify.vue'
import CreatePostOverlay from '@/components/Common/CreatePostOverlay.vue'
import CropperModal from '@/components/Common/CropperModal.vue'
Expand Down Expand Up @@ -45,7 +44,7 @@ defineProps({

<div
class="flex relative bg-[#EFF2F5] items-center justify-center lg:rounded-[10vw] lg:w-full w-[39px] rounded-full cursor-pointer">
<Magnify class="lg:ml-2 lg:p-1 p-2 m-auto" :size="23" fillColor="#64676B" />
<i class="pi pi-search lg:ml-2 lg:p-1 p-2 m-auto text-[#64676B] text-lg" />
<input type="text" placeholder="Tìm kiếm trên Mewbook"
class="outline-none border-none h-full bg-transparent pt-[.6rem] pr-[1rem] pb-[.6rem] pl-[.25rem] font-[.9rem] w-full hidden lg:block" />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const router = createRouter({
{
name: 'fuser',
path: 'friends',
component: () => import('@/views/User/Base.vue'),
component: () => import('@/views/User/Friend.vue'),
}
]
},
Expand Down
101 changes: 101 additions & 0 deletions src/views/User/Friend.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<script setup lang="ts">
import { debounce } from 'lodash';
import Avatar from 'primevue/avatar';
import Button from 'primevue/button';
import { computed, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { Attachment, Friend, User } from '@/database';
import { protectEmail } from '@/helpers/protected';
import type { IUser } from '@/types';
const router = useRouter()
const search = ref<string>('');
const friends = ref<IUser[]>([])
const fsearch = ref<IUser[]>([])
const hasFriendSearch = computed(() => {
return fsearch.value.length > 0
})
async function loadFriends() {
const id = router.currentRoute.value.params.id
const f = await Friend.getByUID(parseInt(id as string))
const users = await User.getAll()
if (f && users.length > 0) {
for (const user of users) {
if (f.friends.includes(user.id.toString())) {
if (Attachment.isID(user.photoURL)) {
const attachment = await Attachment.get(user.photoURL)
user.photoURL = await Attachment.image(attachment.attachments.large)
friends.value.push(user)
}
}
}
}
}
onMounted(async () => {
await loadFriends()
fsearch.value = friends.value
})
watch(search, debounce(async (value) => {
if (value === '') fsearch.value = friends.value
else {
fsearch.value = friends.value.filter((item) => {
return item.displayName.toLowerCase().includes(value.toLowerCase())
})
}
}, 500))
</script>
<template>
<div class="w-full h-full rounded-lg bg-white my-4 p-5">
<div class="flex flex-row items-center justify-between">
<div class="select-none px-2">
<span class="text-xl font-medium">Bạn bè</span>
</div>
<div class="flex flex-row space-x-3">
<div class="flex bg-[#EFF2F5] items-center justify-start w-full rounded-full cursor-pointer py-2">
<i class="pi pi-search pl-3 mx-auto text-[#64676B] text-lg" />
<input type="text" placeholder="Tìm kiếm bạn bè" v-model="search"
class="outline-none border-none h-3 bg-transparent w-full inline-block px-3 py-3" />
</div>
<div class="w-full">
<router-link :to="{ name: 'friends' }">
<Button label="Lời mời kết bạn" text size="small" />
</router-link>
<router-link :to="{ name: 'friends' }">
<Button label="Tìm bạn bè" text size="small" />
</router-link>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 h-full w-full gap-3 my-5" v-if="hasFriendSearch">
<div class="h-[100px] w-full rounded-md border" v-for="(item, index) in fsearch" :key="index">
<router-link :to="{ name: 'user', params: { id: item.id } }">
<div
class="flex flex-row items-center justify-start w-full h-full rounded-md cursor-pointer hover:bg-[#EFF2F5] transition-all space-x-5 px-5">
<div class="cursor-pointer">
<Avatar :image="item.photoURL" size="xlarge" :pt="{
root: 'w-16 h-16',
image: 'rounded-lg w-full h-full'
}" />
</div>
<div class="flex flex-col w-full space-y-1">
<span class="text-[#050505] text-lg font-medium truncate">{{ item.displayName }}</span>
<span class="text-[#050505] text-md font-medium truncate">{{ protectEmail(item.email, '*') }}</span>
</div>
</div>
</router-link>
</div>
</div>
<div class="flex flex-col items-center justify-center w-full h-full py-5" v-else>
<span class="text-lg font-medium">Không có kết quả cho: {{ search }}</span>
</div>
</div>
</template>
Loading

0 comments on commit 6bb4a65

Please sign in to comment.