add user profile stats
This commit is contained in:
parent
6a857254c2
commit
79dc198df9
@ -8,6 +8,8 @@ const GET_REGIONS = `${BASE_URL}/regions`;
|
||||
const GET_REGENCIES = `${BASE_URL}/region/regencies`;
|
||||
const GET_PROVINCES = `${BASE_URL}/region/provinces`;
|
||||
|
||||
const GET_CURRENT_USER_STATS = `${BASE_URL}/user/profile`;
|
||||
|
||||
const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
|
||||
const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings`
|
||||
const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent`
|
||||
@ -28,6 +30,7 @@ export {
|
||||
GET_REGIONS,
|
||||
GET_PROVINCES,
|
||||
GET_REGENCIES,
|
||||
GET_CURRENT_USER_STATS,
|
||||
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
||||
GET_LIST_TOP_LOCATIONS,
|
||||
GET_LIST_LOCATIONS_URI,
|
||||
|
@ -639,6 +639,8 @@ function LocationDetail() {
|
||||
</div>
|
||||
{/* {screen.width >= 1024 &&
|
||||
<div className={'bg-secondary'} style={{ display: 'table-cell', position: 'relative', verticalAlign: 'top', width: 330, textAlign: 'left', padding: 15, boxSizing: 'border-box', height: 1080 }}>
|
||||
// ADD USER DISTRIBUTION SOMETHING LIKE THIS
|
||||
// https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_user_rating
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Reprehenderit cumque aliquam doloribus in reiciendis? Laborum, ea assumenda, tempora dolore placeat aspernatur, cumque totam sequi debitis dolor nam eligendi suscipit aliquid?
|
||||
</div>
|
||||
} */}
|
||||
|
9
src/pages/UserFeed/index.tsx
Normal file
9
src/pages/UserFeed/index.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
function UserFeed() {
|
||||
return (
|
||||
<div>
|
||||
<h1>User feed</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserFeed;
|
227
src/pages/UserProfile/index.tsx
Normal file
227
src/pages/UserProfile/index.tsx
Normal file
@ -0,0 +1,227 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { DEFAULT_AVATAR_IMG } from "../../constants/default";
|
||||
import { UserRootState } from "../../store/type";
|
||||
import "./style.css"
|
||||
import { useEffect, useState } from "preact/compat";
|
||||
import { getUserStatsService } from "../../services";
|
||||
import { CustomInterweave, SeparatorWithAnchor } from "../../components";
|
||||
|
||||
interface UserStats {
|
||||
followers: number,
|
||||
score_count: number
|
||||
}
|
||||
|
||||
interface UserStatsReviews {
|
||||
id: number,
|
||||
comments: string,
|
||||
name: string,
|
||||
province_name: string,
|
||||
score: number,
|
||||
thumbnail: string
|
||||
}
|
||||
|
||||
interface ScoresDistrbution {
|
||||
score: number
|
||||
}
|
||||
|
||||
interface UserStatsResponse {
|
||||
reviews: Array<UserStatsReviews>,
|
||||
user_stats: UserStats,
|
||||
scores_distribution: Array<ScoresDistrbution>
|
||||
}
|
||||
|
||||
const emptyUserStatsResponse: UserStatsResponse = {
|
||||
reviews: Array<UserStatsReviews>(),
|
||||
user_stats: {
|
||||
followers: 0,
|
||||
score_count: 0
|
||||
},
|
||||
scores_distribution: [
|
||||
{
|
||||
score: 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function UserProfile() {
|
||||
const [userStats, setUserStats] = useState<UserStatsResponse>(emptyUserStatsResponse);
|
||||
|
||||
const user = useSelector((state: UserRootState) => state.auth)
|
||||
|
||||
async function getUserStats() {
|
||||
try {
|
||||
const res = await getUserStatsService()
|
||||
setUserStats(res.data)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
function scoreDistributionLabel(val: string) {
|
||||
if (val === '0') {
|
||||
return `${val}-9`
|
||||
}
|
||||
|
||||
if (val === '99') {
|
||||
return '100'
|
||||
}
|
||||
|
||||
return `${val}0-${val}9`
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUserStats()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={'content main-content'}>
|
||||
<div name={'profile_header'} className={'flex column items-end mb-4'}>
|
||||
<div className={'flex-1'}>
|
||||
<img
|
||||
src={user.avatar_picture !== '' ? user.avatar_picture : DEFAULT_AVATAR_IMG}
|
||||
style={{ width: 140, aspectRatio: '1/1', float: 'left' }}
|
||||
className={'mr-4'}
|
||||
/>
|
||||
<p className={'text-lg'}>{user.username}</p>
|
||||
{/* <div className={'mt-2'}>
|
||||
<button className={'bg-tertiary text-xs mr-2'} style={{ padding: '5px 15px', letterSpacing: .8}}>
|
||||
FOLLOW
|
||||
</button>
|
||||
<button className={'bg-error text-xs'} style={{ padding: '5px 15px', letterSpacing: .8}}>
|
||||
REPORT
|
||||
</button>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className={'inline-block mr-4 text-center'}>
|
||||
<p className={'font-bold'} style={{ fontSize: 32 }}>{userStats?.user_stats.followers}</p>
|
||||
<p className={'text-xs'}>Followers</p>
|
||||
</div>
|
||||
<div className={'inline-block text-center'}>
|
||||
<p className={'font-bold'} style={{ fontSize: 32 }}>{userStats?.user_stats.score_count}</p>
|
||||
<p className={'text-xs'}>Reviews</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section name={"profile-navigation"}>
|
||||
<div className={'bg-secondary profile-nav'}>
|
||||
<a>
|
||||
<div>
|
||||
summary
|
||||
</div>
|
||||
</a>
|
||||
<a>
|
||||
<div>
|
||||
reviews
|
||||
</div>
|
||||
</a>
|
||||
<a>
|
||||
<div>
|
||||
likes
|
||||
</div>
|
||||
</a>
|
||||
<a>
|
||||
<div>
|
||||
stories
|
||||
</div>
|
||||
</a>
|
||||
<a>
|
||||
<div>
|
||||
tags
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section name={'REVIEWS SECTION'}>
|
||||
<div className={'mt-4'} style={{ tableLayout: 'fixed', display: 'table', width: '100%' }}>
|
||||
<div className={'wideLeft'} style={{ textAlign: 'left', paddingRight: 20, maxWidth: 1096, minWidth: 680, display: 'table-cell', position: 'relative', verticalAlign: 'top', width: '100%', boxSizing: 'border-box' }}>
|
||||
<div name={'USERS REVIEW'} style={{ textAlign: 'left' }}>
|
||||
<SeparatorWithAnchor pageName={"User's review"} pageLink='#' secondLink={userStats!.reviews.length > 0 ? '#' : ''} />
|
||||
{userStats!.reviews.length > 0 ?
|
||||
<>
|
||||
{userStats!.reviews.map(x => (
|
||||
<div style={{ padding: '15px 0' }}>
|
||||
<div className={'mr-3'} style={{ width: 65, float: 'left' }}>
|
||||
<a href="#">
|
||||
<img
|
||||
loading={'lazy'}
|
||||
style={{ width: '100%', aspectRatio: '1/1', objectFit: 'cover' }}
|
||||
src={x.thumbnail !== '' ? x.thumbnail : 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontWeight: 700, fontSize: 16, lineHeight: 'initial' }}>
|
||||
<a>
|
||||
<span>{x.name}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p className={'text-xs'}>{x.province_name}</p>
|
||||
<div className={'inline-block'}>
|
||||
<div className={'text-sm text-center'} >{x.score}</div>
|
||||
<div style={{ height: 4, width: 25, position: 'relative', backgroundColor: '#d8d8d8' }}>
|
||||
<div style={{ height: 4, backgroundColor: '#85ce73', width: `${x.score}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: 15, lineHeight: '24px', margin: '10px 77px 1px', wordWrap: 'break-word' }}>
|
||||
<CustomInterweave
|
||||
content={x.comments}
|
||||
/>
|
||||
</div>
|
||||
<div className={'reviewLinks'} style={{ marginLeft: 76 }}>
|
||||
<div className={'mr-2'} style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle' }}>
|
||||
<a className={'text-sm'} href={'#'}>
|
||||
<svg className={'inline-block mr-1'} xmlns="http://www.w3.org/2000/svg" fill={"gray"} height="14" viewBox="0 -960 960 960" width="14"><path d="m480-120-58-52q-101-91-167-157T150-447.5Q111-500 95.5-544T80-634q0-94 63-157t157-63q52 0 99 22t81 62q34-40 81-62t99-22q94 0 157 63t63 157q0 46-15.5 90T810-447.5Q771-395 705-329T538-172l-58 52Zm0-108q96-86 158-147.5t98-107q36-45.5 50-81t14-70.5q0-60-40-100t-100-40q-47 0-87 26.5T518-680h-76q-15-41-55-67.5T300-774q-60 0-100 40t-40 100q0 35 14 70.5t50 81q36 45.5 98 107T480-228Zm0-273Z" /></svg>
|
||||
<div className={'inline-block'}>24</div>
|
||||
</a>
|
||||
</div>
|
||||
<div style={{ minWidth: 55, display: 'inline-block', verticalAlign: 'middle', }}>
|
||||
<a className={'text-sm'} href={'#'}>
|
||||
<svg className={'inline-block mr-1'} stroke="currentColor" fill="white" stroke-width="0" viewBox="0 0 512 512" height="14" width="14" xmlns="http://www.w3.org/2000/svg"><path d="M256 32C114.6 32 0 125.1 0 240c0 49.6 21.4 95 57 130.7C44.5 421.1 2.7 466 2.2 466.5c-2.2 2.3-2.8 5.7-1.5 8.7S4.8 480 8 480c66.3 0 116-31.8 140.6-51.4 32.7 12.3 69 19.4 107.4 19.4 141.4 0 256-93.1 256-208S397.4 32 256 32zM128 272c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 0c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 0c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"></path></svg>
|
||||
<div className={'inline-block'}>25 </div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<span className={'text-sm italic'}>No users review to display</span>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{screen.width >= 1024 &&
|
||||
<div className={'bg-secondary'} style={{ display: 'table-cell', position: 'relative', verticalAlign: 'top', width: 330, textAlign: 'left', boxSizing: 'border-box' }}>
|
||||
<div style={{ padding: '20px 10px' }}>
|
||||
<div className={'bg-primary p-2'}>
|
||||
<SeparatorWithAnchor
|
||||
pageName={"Score Distribution"}
|
||||
pageLink="#"
|
||||
titleStyles={{ fontSize: 12, letterSpacing: .9 }}
|
||||
/>
|
||||
{Object.entries(userStats.scores_distribution[0]).map(x => (
|
||||
<div style={{ height: 15, margin: '5px 0'}}>
|
||||
<div style={{ float: 'left', marginRight: 5, width: 40, fontSize: 11, textAlign: 'center' }}>{scoreDistributionLabel(x[0])}</div>
|
||||
{console.log(x[1]/userStats.user_stats.score_count * 100/100)}
|
||||
<div style={{ borderTopRightRadius: 3, borderBottomRightRadius: 3, height: 13, float: 'left', marginRight: 5, minWidth: 1, backgroundColor: '#0be881', width: `calc(${x[1]/userStats.user_stats.score_count * 100}%/2)` }}></div>
|
||||
<div style={{ float: 'left', fontSize: 11 }}>{x[1]}</div>
|
||||
<br style={{ clear: 'both' }} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div style={{ clear: 'both' }} />
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserProfile;
|
13
src/pages/UserProfile/style.css
Normal file
13
src/pages/UserProfile/style.css
Normal file
@ -0,0 +1,13 @@
|
||||
.profile-nav {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile-nav div {
|
||||
display: inline-block;
|
||||
padding: 0 2%;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-size: 0.75rem;
|
||||
}
|
@ -8,11 +8,15 @@ import Login from './Login';
|
||||
import NotFound from "./NotFound";
|
||||
import AddLocation from "./AddLocation";
|
||||
import Submissions from "./Submissions";
|
||||
import UserProfile from "./UserProfile";
|
||||
import UserFeed from "./UserFeed";
|
||||
|
||||
export {
|
||||
Login,
|
||||
|
||||
Home,
|
||||
UserProfile,
|
||||
UserFeed,
|
||||
|
||||
NotFound,
|
||||
|
||||
|
@ -5,7 +5,9 @@ import {
|
||||
LocationDetail,
|
||||
NewsEvent,
|
||||
Story,
|
||||
AddLocation
|
||||
AddLocation,
|
||||
UserProfile,
|
||||
UserFeed
|
||||
} from '../pages';
|
||||
|
||||
const routes = [
|
||||
@ -43,7 +45,17 @@ const routes = [
|
||||
path: "/add-location",
|
||||
name: "AddLocation",
|
||||
element: <AddLocation />
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/user/profile",
|
||||
name: "UserProfile",
|
||||
element: <UserProfile />
|
||||
},
|
||||
{
|
||||
path: "/user/feed",
|
||||
name: "UserFeed",
|
||||
element: <UserFeed />
|
||||
},
|
||||
]
|
||||
|
||||
export default routes;
|
@ -9,6 +9,7 @@ import { getImagesByLocationService } from "./images"
|
||||
import { createAccountService, loginService, logoutService } from "./auth";
|
||||
import { postReviewLocation, getCurrentUserLocationReviewService } from "./review";
|
||||
import { getRegionsService, getProvincesService, getRegenciesService} from "./regions";
|
||||
import { getUserStatsService } from "./users";
|
||||
|
||||
export {
|
||||
createAccountService,
|
||||
@ -19,6 +20,8 @@ export {
|
||||
getProvincesService,
|
||||
getRegenciesService,
|
||||
|
||||
getUserStatsService,
|
||||
|
||||
getListLocationsService,
|
||||
getListRecentLocationsRatingsService,
|
||||
getListTopLocationsService,
|
||||
|
@ -114,7 +114,7 @@ async function getLocationTagsService(id: Number) {
|
||||
async function createLocationService(data: FormData): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null};
|
||||
try {
|
||||
const response = await client({ method: 'POST', url: POST_CREATE_LOCATION, data: data})
|
||||
const response = await client({ method: 'POST', url: POST_CREATE_LOCATION, data: data, withCredentials: true})
|
||||
newState.data = response.data;
|
||||
newState.status = response.status
|
||||
return newState;
|
||||
|
24
src/services/users.ts
Normal file
24
src/services/users.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { GET_CURRENT_USER_STATS } from "../constants/api";
|
||||
import { IHttpResponse } from "../types/common";
|
||||
import { client } from "./config";
|
||||
|
||||
|
||||
async function getUserStatsService(): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null };
|
||||
try {
|
||||
const res = await client({ method: 'GET', url: GET_CURRENT_USER_STATS, withCredentials: true})
|
||||
newState.data = res.data
|
||||
newState.status = res.status
|
||||
return newState
|
||||
} catch(error) {
|
||||
let err = error as AxiosError
|
||||
newState.error = err
|
||||
newState.status = err.status
|
||||
throw(newState)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getUserStatsService,
|
||||
}
|
Loading…
Reference in New Issue
Block a user