add user profile stats

This commit is contained in:
NCanggoro 2023-10-04 19:55:03 +07:00
parent 6a857254c2
commit 79dc198df9
10 changed files with 300 additions and 3 deletions

View File

@ -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,

View File

@ -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>
} */} } */}

View File

@ -0,0 +1,9 @@
function UserFeed() {
return (
<div>
<h1>User feed</h1>
</div>
)
}
export default UserFeed;

View 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;

View 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;
}

View File

@ -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,

View File

@ -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;

View File

@ -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,

View File

@ -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
View 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,
}