Skip to content

Commit

Permalink
ADD:users features (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
automerge-pingpong[bot] committed Oct 22, 2023
2 parents 2ebc56f + 4e4d229 commit 44a0340
Show file tree
Hide file tree
Showing 19 changed files with 255 additions and 93 deletions.
2 changes: 1 addition & 1 deletion backend/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ RUN npm install

COPY ./code .

CMD ["sh", "-c","npx prisma db push --force-reset && npx prisma studio & npm run start:dev"]
CMD ["sh", "-c","npx prisma studio & npm run start:dev"]
2 changes: 1 addition & 1 deletion backend/code/src/profile/dto/profile.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type NAME = {
last: string;
};

type PICTURE = {
export type PICTURE = {
thumbnail: string;
medium: string;
large: string;
Expand Down
4 changes: 3 additions & 1 deletion backend/code/src/profile/profile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ export class ProfileService {
userId: string,
friendId?: null | string,
): Promise<ProfileDto> {
let id = userId;
if (friendId && friendId !== userId) {
const blockid = [userId, friendId].sort().join('-');
const block = await this.usersService.getBlockbyId(blockid);
if (block) {
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
}
id = friendId;
}
const user = await this.usersService.getUserById(userId);
const user = await this.usersService.getUserById(id);
if (!user) {
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
}
Expand Down
9 changes: 7 additions & 2 deletions backend/code/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { NAME } from '../profile/dto/profile.dto';
import { NAME, PICTURE } from '../profile/dto/profile.dto';

@Injectable()
export class UsersService {
Expand Down Expand Up @@ -135,7 +135,12 @@ export class UsersService {
});
return users.map((user) => {
const name: NAME = { first: user.firstName, last: user.lastName };
return { name: name, id: user.userId, avatar: user.avatar };
const avatar: PICTURE = {
thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${user.avatar}`,
medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${user.avatar}`,
large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${user.avatar}`,
};
return { name , id: user.userId, avatar };
});
}
}
6 changes: 1 addition & 5 deletions frontend/code/src/Api/base.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import axios from 'axios';
import toast from 'react-hot-toast';

const api = axios.create({
baseURL: `${process.env.REACT_APP_API_ENDPOINT}`,
Expand Down Expand Up @@ -27,10 +26,7 @@ const errorHandler = async (error:any) => {
refreshAttempted = false
}
}
else
{
toast.error(`${error.response.data.message}`)
}

return Promise.reject({ ...error });
};

Expand Down
3 changes: 2 additions & 1 deletion frontend/code/src/Components/Chat/Services/ChatServices.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import api from "../../../Api/base";
import api from "../../../Api/base";


export const createNewRoomCall = async (
name: string,
Expand Down
69 changes: 48 additions & 21 deletions frontend/code/src/Components/FirstLogin/UploadAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,59 @@
import { useForm, SubmitHandler } from "react-hook-form"


import { useUserStore } from "../../Stores/stores"
import api from '../../Api/base'
import { toast } from "react-hot-toast"
import { useNavigate } from "react-router-dom"
import { UploadLogic } from './UploadLogic'
type Inputs = {
Avatar: string
AvatarRequired: string
Avatar: string;
firstName : string ;
lastName : string ;
discreption : string;
email : string;
}


const ERROR_MESSAGES = ["Field is required" , "Require min length of " , "Passed max length of"]
const payload_objects = ["firstName","lastName","email","phone","discreption"]
const data_names = ["First name", "Last name", "Email", "Phone", "Bio"];
export const UploadAvatar = () => {
const userStore = useUserStore();
const navigate = useNavigate();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Inputs>()
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data)

handleSubmit
} = useForm<Inputs>()
const onSubmit: SubmitHandler<any> = async(data) => {
try{

toast.promise(api.post("/profile/me",{...data ,finishProfile: true }) , {loading:"Saving user information",success:"Saved successfully",error:"Error on Saving Data"})
userStore.login();
navigate("/Home")
}catch(e)
{}
}
const handleError = (errors : any) => {
//eslint-disable-next-line
payload_objects.map((item:any, index : number) =>{
if (errors[`${item}`]?.type === "required") toast.error(`${data_names[index]} ${ERROR_MESSAGES[0]} `);
if (errors[`${item}`]?.type === "minLength") toast.error(`${data_names[index]} ${ERROR_MESSAGES[1]} 4`);
if (errors[`${item}`]?.type === "maxLength")toast.error(`${data_names[index]} ${ERROR_MESSAGES[2]} 50 `);
})

}

return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="file" {...register("Avatar")} />


<input {...register("AvatarRequired", { required: true })} />
{errors.AvatarRequired && <span>This field is required</span>}


<input type="submit" />
</form>
<>
<form onSubmit={handleSubmit(onSubmit,handleError)} className="flex flex-col max-w-[50vw] gap-8 justify-center items-center">
<UploadLogic/>
<input type="text" placeholder="First Name " {...register("firstName",{required: true, maxLength: 50 , minLength:4}) } defaultValue={userStore.name.first} className="input input-bordered input-primary w-full max-w-xs" />
<input type="text" placeholder="Last Name " {...register("lastName",{required: true, maxLength: 50 , minLength:4})} defaultValue={userStore.name.last} className="input input-bordered input-primary w-full max-w-xs" />
<input type="text" placeholder="Bio" {...register("discreption",{required: true, maxLength: 50 , minLength:4})} defaultValue={userStore.bio} className="input input-bordered input-primary w-full max-w-xs" />
<input type="text" placeholder="Email " {...register("email",{required:true, pattern: {
value: /\S+@\S+\.\S+/,
message: "Entered value does not match email format",
}})} defaultValue={userStore.email} className="input input-bordered input-primary w-full max-w-xs" />

<input className="bg-primary h-12 w-80 rounded-md hover:bg-secondary hover:cursor-pointer transition-colors text-white" type="submit" value="Save" />
</form>
</>
)
}
47 changes: 47 additions & 0 deletions frontend/code/src/Components/FirstLogin/UploadLogic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {useRef} from 'react'
import { Avatar } from '../Settings/assets/Avatar'
import { Edit } from '../Settings/assets/Edit'
import { useUserStore } from '../../Stores/stores'
import api from '../../Api/base'
import { toast } from 'react-hot-toast'
export const UploadLogic = () => {
const userStore = useUserStore();
const inputRef = useRef<any>();
const handleUploadedFile = async(event:any) => {
const h_size = event.target.files[0].size / 1024;
if (h_size > 5120)
{
toast.error("uploded avatar is bigger than 5mb")
return Promise.reject("Error");
}
const formData = new FormData();
formData.append("image", event.target.files[0]);

await api.post("/profile/avatar", formData ,{headers:{"Content-Type":"multipart/form-data"}});
const res = await api.get("/profile/me")
userStore.setAvatar(res?.data?.picture);

};
const handleClick = () => {
inputRef?.current?.click();

}
return (
<>
<div className="relative pt-6 ">
<Avatar picture={userStore.picture.medium} />
<div className="absolute bottom-0 right-0" onClick={handleClick}>
<Edit />
</div>
</div>
<input type="file" className="hidden" id="picture" onChange={(e) => {
toast.promise(handleUploadedFile(e),{
loading: "Updating Profile Image",
success:"New Avatar Saved",
error:"Error On Uploading image"
},{position:"top-center",className:"h-20",duration:2000})
}} ref={inputRef}
/>
</>
)
}
9 changes: 4 additions & 5 deletions frontend/code/src/Components/FirstLogin/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@

import { UploadAvatar } from "./UploadAvatar";

export const FirstLogin = () => {
return (
<>
<div className="absolute h-full w-full bg-black opacity-40 z-20"></div>
<div className="absolute h-full w-full bg-black opacity-20 z-20"></div>

<div className="z-30 absolute h-[70vh] opacity-90 rounded-3xl w-[50vw] top-1/4 right-1/4 bg-base-100 shadow-2xl shadow-white border-4 border-white">
<div className="z-30 absolute h-[70vh] opacity-90 rounded-xl w-[50vw] top-1/4 right-1/4 bg-base-100 s shadow-xl border-opacity-20 border-8 shadow-primary border-primary">

<div className="flex flex-col p-10 gap-6 justify-center items-center content-center">
<div>
Avatar
</div>

<UploadAvatar/>
</div>

Expand Down
2 changes: 1 addition & 1 deletion frontend/code/src/Components/Home/LeaderBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Chart } from './assets/Chart'
import { Table } from './assets/Table'
export const LeaderBoard = () => {
return (
<div className='flex flex-col rounded-2xl justify-start items-start mt-6 sm:h-full h-full w-full bg-base-200 overflow-scroll no-scrollbar'>
<div className='flex flex-col rounded-2xl justify-start items-start mt-6 sm:h-full h-full w-full bg-base-200 overflow-auto no-scrollbar'>
<div className="flex justify-start items-start pl-2 pt-2 sm:pl-12 sm:pt-12 gap-x-4 py-4">
<Chart/> <span className='font-montserrat'>Leader Board </span>

Expand Down
4 changes: 2 additions & 2 deletions frontend/code/src/Components/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ export const Home : FC = () : JSX.Element =>{
return (
<>

<div className="flex flex-col items-center h-screen w-full sm:gap-y-8 gap-y-1 bg-accent">
<div className="flex flex-col items-center h-full w-full sm:gap-y-8 gap-y-1 bg-accent">
<div className='flex justify-center relative items-start pt-6 h-2/6 max-h-48 sm:max-h-96 w-[90vw] sm:h-3/4 sm:w-[85vw]'>
<img className='w-full h-full object-cover object-top rounded-3xl' alt='leaderboard hero' src={herosvg} />
<Link to={"/Play"}><Button/></Link>

<div className='absolute xl:text-4xl md:text-3xl sm:text-2xl flex top-[15%] right-2/6 text-neutral font-lexend font-extrabold'>READY TO PLAY A GAME? </div>

</div>
<div className='flex justify-center relative items-start pt-6 h-auto w-[90vw] sm:w-[85vw] overflow-scroll no-scrollbar'>
<div className='flex justify-center relative items-start pt-6 h-auto w-[90vw] sm:w-[85vw] overflow-hidden'>
<LeaderBoard/>

</div>
Expand Down
32 changes: 29 additions & 3 deletions frontend/code/src/Components/Layout/Assets/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
import {useState , ChangeEvent , useEffect} from 'react'
import {BiSearch} from 'react-icons/bi'
import { SearchResults } from './SearchResults'

function useDebounce<T>(value: T, delay?: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)

useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay || 500)

return () => {
clearTimeout(timer)
}
}, [value, delay])

return debouncedValue
}

export const Search = () => {
const [searchText, setSearchText] = useState("");
const DebounceValue = useDebounce(searchText);
const onSearchTextChange = (e: ChangeEvent<HTMLInputElement>) => setSearchText(e.target.value);

return (
<div className='hidden sm:flex sm:items-center absolute pr-24 '>
<input type="text" placeholder="Search" className={`input w-44 h-8 md:w-60 mr-4 md:h-12`}/>
<div className='relative right-12 top-0 w-12 '><BiSearch size="1.4em" /></div>

<div className='dropdown hover:cursor-pointer hidden sm:flex sm:items-center absolute w-80 right-52'>

<input tabIndex={0} type="text" placeholder="Search" className={`input w-80 h-8 md:w-full mr-4 md:h-12`} onChange={onSearchTextChange} onBlur={() => {setSearchText("")}} onFocus={()=>{setSearchText("")}} value={searchText}/>

<div className='relative right-14 top-0 w-12 '><BiSearch size="1.4em" /></div>
<ul tabIndex={0} className="dropdown-content z-[9999] menu p-2 shadow bg-base-100 rounded-box w-full top-12">
<SearchResults query={DebounceValue} />
</ul>
</div>
)
}
40 changes: 40 additions & 0 deletions frontend/code/src/Components/Layout/Assets/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useEffect, useState } from "react"
import api from "../../../Api/base"
import { Link } from "react-router-dom";
export const SearchResults = (props:any) => {
const [result , setResult] = useState([]);
useEffect(() =>
{
const search = async() => {
try {
const res = await api.get("/users/search",{params:{q:props.query}})
setResult(res.data)
console.log(res.data)
} catch (error) {
}
}
props.query && search()
},[props.query])
return (
<div className="flex flex-col w-full h-full bg-base-100 z-50">
{ result.length > 0 && result.map((item : any , index : number) => {
return (
<Link key={index} to={`Profile/${item.id}`}>
<li className="hover:bg-primary hover:rounded-xl transform duration-500 h-full w-full z-[10000]">
<div className="flex items-center gap-2">
<div className="avatar">
<div className="w-auto rounded-full gap-4 ring ring-primary ring-offset-base-100 ring-offset-2">
<img alt="" src={item.avatar.thumbnail} />
</div>
</div>
<span >{item.name.first} {item.name.last}</span>
</div>
</li>
</Link>
)
})
}
{}
</div>
)
}
Loading

0 comments on commit 44a0340

Please sign in to comment.