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_REGENCIES = `${BASE_URL}/region/regencies`;
|
||||||
const GET_PROVINCES = `${BASE_URL}/region/provinces`;
|
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_LOCATIONS_URI = `${BASE_URL}/locations`;
|
||||||
const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings`
|
const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings`
|
||||||
const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent`
|
const GET_LIST_RECENT_LOCATIONS_RATING_URI = `${BASE_URL}/locations/recent`
|
||||||
@ -28,6 +30,7 @@ export {
|
|||||||
GET_REGIONS,
|
GET_REGIONS,
|
||||||
GET_PROVINCES,
|
GET_PROVINCES,
|
||||||
GET_REGENCIES,
|
GET_REGENCIES,
|
||||||
|
GET_CURRENT_USER_STATS,
|
||||||
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
||||||
GET_LIST_TOP_LOCATIONS,
|
GET_LIST_TOP_LOCATIONS,
|
||||||
GET_LIST_LOCATIONS_URI,
|
GET_LIST_LOCATIONS_URI,
|
||||||
|
@ -639,6 +639,8 @@ function LocationDetail() {
|
|||||||
</div>
|
</div>
|
||||||
{/* {screen.width >= 1024 &&
|
{/* {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 }}>
|
<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?
|
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>
|
</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 NotFound from "./NotFound";
|
||||||
import AddLocation from "./AddLocation";
|
import AddLocation from "./AddLocation";
|
||||||
import Submissions from "./Submissions";
|
import Submissions from "./Submissions";
|
||||||
|
import UserProfile from "./UserProfile";
|
||||||
|
import UserFeed from "./UserFeed";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Login,
|
Login,
|
||||||
|
|
||||||
Home,
|
Home,
|
||||||
|
UserProfile,
|
||||||
|
UserFeed,
|
||||||
|
|
||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
|
@ -5,7 +5,9 @@ import {
|
|||||||
LocationDetail,
|
LocationDetail,
|
||||||
NewsEvent,
|
NewsEvent,
|
||||||
Story,
|
Story,
|
||||||
AddLocation
|
AddLocation,
|
||||||
|
UserProfile,
|
||||||
|
UserFeed
|
||||||
} from '../pages';
|
} from '../pages';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@ -43,7 +45,17 @@ const routes = [
|
|||||||
path: "/add-location",
|
path: "/add-location",
|
||||||
name: "AddLocation",
|
name: "AddLocation",
|
||||||
element: <AddLocation />
|
element: <AddLocation />
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: "/user/profile",
|
||||||
|
name: "UserProfile",
|
||||||
|
element: <UserProfile />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/user/feed",
|
||||||
|
name: "UserFeed",
|
||||||
|
element: <UserFeed />
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
@ -9,6 +9,7 @@ import { getImagesByLocationService } from "./images"
|
|||||||
import { createAccountService, loginService, logoutService } from "./auth";
|
import { createAccountService, loginService, logoutService } from "./auth";
|
||||||
import { postReviewLocation, getCurrentUserLocationReviewService } from "./review";
|
import { postReviewLocation, getCurrentUserLocationReviewService } from "./review";
|
||||||
import { getRegionsService, getProvincesService, getRegenciesService} from "./regions";
|
import { getRegionsService, getProvincesService, getRegenciesService} from "./regions";
|
||||||
|
import { getUserStatsService } from "./users";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createAccountService,
|
createAccountService,
|
||||||
@ -19,6 +20,8 @@ export {
|
|||||||
getProvincesService,
|
getProvincesService,
|
||||||
getRegenciesService,
|
getRegenciesService,
|
||||||
|
|
||||||
|
getUserStatsService,
|
||||||
|
|
||||||
getListLocationsService,
|
getListLocationsService,
|
||||||
getListRecentLocationsRatingsService,
|
getListRecentLocationsRatingsService,
|
||||||
getListTopLocationsService,
|
getListTopLocationsService,
|
||||||
|
@ -114,7 +114,7 @@ async function getLocationTagsService(id: Number) {
|
|||||||
async function createLocationService(data: FormData): Promise<IHttpResponse> {
|
async function createLocationService(data: FormData): Promise<IHttpResponse> {
|
||||||
const newState: IHttpResponse = { data: null, error: null};
|
const newState: IHttpResponse = { data: null, error: null};
|
||||||
try {
|
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.data = response.data;
|
||||||
newState.status = response.status
|
newState.status = response.status
|
||||||
return newState;
|
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