add user settings, add protected route
This commit is contained in:
parent
68cc693e3f
commit
905ee3669c
64
src/app.tsx
64
src/app.tsx
@ -2,16 +2,17 @@ import { Route, Routes } from 'react-router-dom'
|
||||
import { BrowserRouter as Router } from 'react-router-dom'
|
||||
import './app.css'
|
||||
import { DefaultLayout } from './layouts'
|
||||
import routes from './routes'
|
||||
import "yet-another-react-lightbox/styles.css";
|
||||
import { Login, NotFound, Submissions } from './pages'
|
||||
import { Provider } from 'react-redux'
|
||||
import { persistore, store } from './store/config'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
import { ProtectedRoute } from './routes/ProtectedRoute'
|
||||
import { AdminProtectedRoute, UserProtectedRoute } from './routes/ProtectedRoute'
|
||||
import { getRoutes } from './routes';
|
||||
|
||||
|
||||
export function App() {
|
||||
const { routes } = getRoutes();
|
||||
return (
|
||||
<>
|
||||
<Provider store={store}>
|
||||
@ -20,20 +21,51 @@ export function App() {
|
||||
<Routes>
|
||||
<Route path='/login' element={<Login />} />
|
||||
<Route element={<DefaultLayout />}>
|
||||
{routes.map(({ path, name, element }) => (
|
||||
<>
|
||||
<Route
|
||||
path={path}
|
||||
id={name}
|
||||
element={element}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
<Route path='/submissions' id='submissions' element={
|
||||
<ProtectedRoute>
|
||||
<Submissions />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
{routes.map(({ path, name, element, protectedRoute }) => {
|
||||
let Element = element as any
|
||||
if (protectedRoute === "user") {
|
||||
return (
|
||||
<>
|
||||
<Route
|
||||
path={path}
|
||||
id={name}
|
||||
element={
|
||||
<UserProtectedRoute>
|
||||
<Element />
|
||||
</UserProtectedRoute>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (protectedRoute === "admin") {
|
||||
return (
|
||||
<>
|
||||
<>
|
||||
<Route
|
||||
path={path}
|
||||
id={name}
|
||||
element={
|
||||
<AdminProtectedRoute>
|
||||
<Element />
|
||||
</AdminProtectedRoute>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Route
|
||||
path={path}
|
||||
id={name}
|
||||
element={element}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
@ -9,6 +9,9 @@ const GET_REGENCIES = `${BASE_URL}/region/regencies`;
|
||||
const GET_PROVINCES = `${BASE_URL}/region/provinces`;
|
||||
|
||||
const GET_CURRENT_USER_STATS = `${BASE_URL}/user/profile`;
|
||||
const PATCH_USER_AVATAR = `${BASE_URL}/user/avatar`;
|
||||
const PATCH_USER_INFO = `${BASE_URL}/user`;
|
||||
const DELETE_USER_AVATAR = `${BASE_URL}/user/avatar`
|
||||
|
||||
const GET_LIST_LOCATIONS_URI = `${BASE_URL}/locations`;
|
||||
const GET_LIST_TOP_LOCATIONS = `${BASE_URL}/locations/top-ratings`
|
||||
@ -24,9 +27,10 @@ const GET_CURRENT_USER_REVIEW_LOCATION_URI = `${BASE_URL}/user/review/location`
|
||||
|
||||
export {
|
||||
BASE_URL,
|
||||
SIGNUP_URI,
|
||||
LOGIN_URI,
|
||||
LOGOUT_URI,
|
||||
SIGNUP_URI,
|
||||
DELETE_USER_AVATAR,
|
||||
GET_REGIONS,
|
||||
GET_PROVINCES,
|
||||
GET_REGENCIES,
|
||||
@ -34,10 +38,12 @@ export {
|
||||
GET_LIST_RECENT_LOCATIONS_RATING_URI,
|
||||
GET_LIST_TOP_LOCATIONS,
|
||||
GET_LIST_LOCATIONS_URI,
|
||||
POST_CREATE_LOCATION,
|
||||
GET_LOCATION_URI,
|
||||
GET_LOCATION_TAGS_URI,
|
||||
GET_IMAGES_BY_LOCATION_URI,
|
||||
POST_REVIEW_LOCATION_URI,
|
||||
GET_CURRENT_USER_REVIEW_LOCATION_URI,
|
||||
PATCH_USER_AVATAR,
|
||||
PATCH_USER_INFO,
|
||||
POST_REVIEW_LOCATION_URI,
|
||||
POST_CREATE_LOCATION,
|
||||
}
|
@ -1,9 +1,22 @@
|
||||
import { NullValueRes } from "../types/common";
|
||||
|
||||
export interface SocialMedia {
|
||||
type: string,
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
about: string,
|
||||
website: string,
|
||||
social_media: Array<SocialMedia>
|
||||
}
|
||||
|
||||
export type User = {
|
||||
id: number,
|
||||
email: string,
|
||||
username: string,
|
||||
about: string,
|
||||
website: string,
|
||||
avatar_picture: string,
|
||||
banned_at: NullValueRes<"Time", string>,
|
||||
banned_until: NullValueRes<"Time", string>,
|
||||
@ -12,7 +25,7 @@ export type User = {
|
||||
is_admin: boolean,
|
||||
is_critics: boolean,
|
||||
is_verfied: boolean,
|
||||
social_media: NullValueRes<"RawMessage", any>
|
||||
social_media: Array<SocialMedia>
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +33,8 @@ export function emptyUser(): User {
|
||||
return {
|
||||
avatar_picture: '',
|
||||
ban_reason: '',
|
||||
about: '',
|
||||
website: '',
|
||||
banned_at: { Time: '', Valid: false},
|
||||
banned_until: { Time: '', Valid: false},
|
||||
email: '',
|
||||
@ -28,7 +43,7 @@ export function emptyUser(): User {
|
||||
is_critics: false,
|
||||
is_permaban: false,
|
||||
is_verfied: false,
|
||||
social_media: {RawMessage: '', Valid: false},
|
||||
social_media: Array<SocialMedia>(),
|
||||
username: ''
|
||||
}
|
||||
}
|
286
src/pages/UserSettings/index.tsx
Normal file
286
src/pages/UserSettings/index.tsx
Normal file
@ -0,0 +1,286 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { DefaultButton, TitleSeparator, WarningButton } from "../../components";
|
||||
import { UserRootState } from "../../store/type";
|
||||
import { DEFAULT_AVATAR_IMG } from "../../constants/default";
|
||||
import { ChangeEvent, TargetedEvent, useRef } from "preact/compat";
|
||||
import { useState } from "react";
|
||||
import { enumKeys, useAutosizeTextArea } from "../../utils";
|
||||
import { SocialMediaEnum } from "../../types/common";
|
||||
import { SocialMedia, UserInfo } from "../../../src/domains/User";
|
||||
import './styles.css'
|
||||
import { deleteUserAvatarService, patchUserAvatarService, patchUserInfoService } from "../../../src/services/users";
|
||||
import { AxiosError } from "axios";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { authAdded } from "../../features/auth/authSlice/authSlice";
|
||||
|
||||
interface PageState {
|
||||
avatar_file: File | null,
|
||||
avatar: string,
|
||||
upload_file_err_msg: string,
|
||||
file_input_key: string
|
||||
}
|
||||
|
||||
function UserSettings() {
|
||||
const user = useSelector((state: UserRootState) => state.auth)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [pageState, setPageState] = useState<PageState>({
|
||||
avatar_file: null,
|
||||
avatar: user.avatar_picture,
|
||||
upload_file_err_msg: '',
|
||||
file_input_key: Math.random().toString(24)
|
||||
})
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const [form, setForm] = useState<UserInfo>({
|
||||
about: user.about,
|
||||
website: user.website,
|
||||
social_media: user.social_media
|
||||
})
|
||||
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
useAutosizeTextArea(textAreaRef.current, form.about);
|
||||
|
||||
function onChangeFormInput(e: ChangeEvent) {
|
||||
let event = e.target as HTMLInputElement;
|
||||
setForm({ ...form, [event.name]: event.value })
|
||||
}
|
||||
|
||||
function handleTextAreaChange(e: ChangeEvent<HTMLTextAreaElement>): void {
|
||||
const val = e.target as HTMLTextAreaElement;
|
||||
|
||||
setForm({ ...form, about: val.value })
|
||||
}
|
||||
|
||||
function onAddSocialMediaClicked() {
|
||||
let tempArr: Array<SocialMedia> = form.social_media
|
||||
tempArr.push({ type: SocialMediaEnum.Facebook, value: '' })
|
||||
setForm({ ...form, social_media: tempArr })
|
||||
}
|
||||
|
||||
function onChangeSocialMediaInput(e: ChangeEvent, idx: number) {
|
||||
let event = e.target as HTMLInputElement;
|
||||
let socmed = form.social_media;
|
||||
socmed[idx].value = event.value;
|
||||
setForm({ ...form, social_media: socmed })
|
||||
|
||||
}
|
||||
|
||||
function onChangeSocialMediaType(e: ChangeEvent, idx: number) {
|
||||
let event = e.target as HTMLOptionElement
|
||||
let socmed = form.social_media;
|
||||
socmed[idx].type = event.value;
|
||||
setForm({ ...form, social_media: socmed })
|
||||
}
|
||||
|
||||
function onDeleteSelectedSocial(idx: number) {
|
||||
setForm({ ...form, social_media: form.social_media.filter((_, index) => index !== idx) })
|
||||
}
|
||||
|
||||
async function onUploadAvatar(e: TargetedEvent) {
|
||||
setIsLoading(true)
|
||||
e.preventDefault();
|
||||
try {
|
||||
const form = new FormData();
|
||||
form.append('file', pageState.avatar_file!)
|
||||
const res = await patchUserAvatarService(form)
|
||||
let userStore = {
|
||||
...user,
|
||||
avatar_picture: res.data.image_url
|
||||
}
|
||||
dispatch(authAdded(userStore));
|
||||
setIsLoading(false)
|
||||
} catch(err) {
|
||||
let error = err as AxiosError;
|
||||
console.log(error)
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
setPageState({ ...pageState, file_input_key: Math.random().toString(24)})
|
||||
|
||||
}
|
||||
|
||||
async function onRemoveAvatar(e: TargetedEvent) {
|
||||
setIsLoading(true)
|
||||
e.preventDefault();
|
||||
try {
|
||||
await deleteUserAvatarService()
|
||||
let userStore = {
|
||||
...user,
|
||||
avatar_picture: ''
|
||||
}
|
||||
dispatch(authAdded(userStore))
|
||||
setPageState({ ...pageState, avatar: ''})
|
||||
setIsLoading(false)
|
||||
} catch(err) {
|
||||
setIsLoading(false)
|
||||
const error = err as AxiosError
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function onUpdateUserInfo(e: TargetedEvent) {
|
||||
setIsLoading(true)
|
||||
e.preventDefault();
|
||||
try {
|
||||
console.log(form)
|
||||
const res = await patchUserInfoService(form)
|
||||
let userData = {
|
||||
...user,
|
||||
about: res.data.about,
|
||||
website: res.data.website,
|
||||
social_media: res.data.social_media
|
||||
}
|
||||
|
||||
dispatch(authAdded(userData))
|
||||
setIsLoading(false)
|
||||
}catch(err) {
|
||||
setIsLoading(false)
|
||||
let error = err as AxiosError;
|
||||
alert(error)
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeUploadImage(e: ChangeEvent) {
|
||||
let event = e.target as HTMLInputElement;
|
||||
const file = event.files![0] as File;
|
||||
|
||||
// if cancel button pressed
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
|
||||
if (file.type !== "image/png" && file.type !== "image/jpg" && file.type !== "image/jpeg" ) {
|
||||
setPageState({ ...pageState, upload_file_err_msg: 'ONLY ACCEPT JPEG/PNG/JPG FILE!!!!' })
|
||||
return
|
||||
}
|
||||
|
||||
if (file.size > 10000000) {
|
||||
setPageState({ ...pageState, upload_file_err_msg: 'file size too large, max file size 10mb' })
|
||||
return
|
||||
}
|
||||
|
||||
setPageState({ ...pageState, upload_file_err_msg: '', avatar_file: file, avatar: URL.createObjectURL(file)})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'main-content content'}>
|
||||
<div className={'mb-4'}>
|
||||
<h1 className={'text-2xl'}>User settings</h1>
|
||||
<TitleSeparator titleClasses="mt-8 " pageName={'avatar'} titleStyles={{ letterSpacing: .9 }} />
|
||||
<div style={{ padding: '0 15px 20px 0', float: 'left', maxWidth: '50%', width: 200 }}>
|
||||
<img
|
||||
src={pageState.avatar !== '' ? pageState.avatar : DEFAULT_AVATAR_IMG}
|
||||
style={{ aspectRatio: '1/1', objectFit: 'cover'}}
|
||||
/>
|
||||
</div>
|
||||
<div className={'text-sm mb-5'} style={{ overflow: 'hidden' }}>
|
||||
<span className={'mr-3'}>Change avatar</span>
|
||||
<input name="uploadImage" key={pageState.file_input_key} type={"file"} accept={'image/*'} onChange={onChangeUploadImage} />
|
||||
{pageState.upload_file_err_msg &&
|
||||
<p className={'text-xs text-error mt-2'}>{pageState.upload_file_err_msg}</p>
|
||||
}
|
||||
<div className={'mt-2 text-tertiary'}>
|
||||
<p>File extension must be .jpeg, .png, .jpg</p>
|
||||
<p>File size max 10mb</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'text-xs button-avatar-container'}>
|
||||
<DefaultButton
|
||||
label="update"
|
||||
className={isLoading ? 'pointer-events-none' : ''}
|
||||
style={{ paddingLeft: 36, paddingRight: 36, textTransform: 'uppercase'}}
|
||||
isLoading={isLoading}
|
||||
onClick={onUploadAvatar}
|
||||
/>
|
||||
<WarningButton
|
||||
label="remove avatar"
|
||||
containerClassName="mt-5"
|
||||
style={{ textTransform: 'uppercase', paddingLeft: 8, paddingRight: 8 }}
|
||||
className={isLoading ? 'pointer-events-none' : ''}
|
||||
onClick={onRemoveAvatar}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ clear: 'both' }} />
|
||||
</div>
|
||||
|
||||
<section name="User Info">
|
||||
<TitleSeparator pageName="INFO" />
|
||||
<div className={'mb-3'} style={{ maxWidth: 600, minWidth: 280 }}>
|
||||
<h2 className={'text-sm mb-1 font-bold'}>ABOUT YOU</h2>
|
||||
<textarea
|
||||
onChange={handleTextAreaChange}
|
||||
maxLength={500}
|
||||
ref={textAreaRef}
|
||||
className={'p-2 text-sm bg-secondary'}
|
||||
value={form.about}
|
||||
style={{ border: 'none', overflow: 'auto', outline: 'none', boxShadow: 'none', width: '100%', minHeight: 100, overflowY: 'hidden' }}
|
||||
/>
|
||||
<div className={'mt-2 mb-1'}>
|
||||
<h2 className={'font-bold text-sm mb-1'}>WEBSITE</h2>
|
||||
<input className={'bg-secondary text-sm'} placeholder={'www.mywebsite.com'} style={{ width: '100%', borderRadius: 7, padding: '5px 10px' }} type={'text'} onChange={onChangeFormInput} name={'website'} value={form.website} />
|
||||
</div>
|
||||
<h2>Social</h2>
|
||||
{form.social_media.map((val, index) => (
|
||||
<div className={'mt-2'}>
|
||||
<select
|
||||
onChange={(e) => onChangeSocialMediaType(e, index)}
|
||||
className={'bg-secondary inline-block text-sm'}
|
||||
name="social_media_type"
|
||||
id="social_media_type"
|
||||
style={{ borderRadius: 7, padding: '5px 10px' }}
|
||||
value={form.social_media[index].type.toLowerCase()}
|
||||
>
|
||||
{enumKeys(SocialMediaEnum).map(x => (
|
||||
<option value={SocialMediaEnum[x]}>{SocialMediaEnum[x]}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
<input
|
||||
className={'bg-secondary text-sm ml-1 mr-1 inline-block'}
|
||||
style={{ width: '66%', borderRadius: 7, padding: '5px 10px' }}
|
||||
type={'text'}
|
||||
onChange={(e) => onChangeSocialMediaInput(e, index)}
|
||||
name={'social_media'}
|
||||
value={val.value} />
|
||||
<span className={'text-xs text-gray mr-3'}>( username )</span>
|
||||
<span
|
||||
className={'text-sm bg-error'}
|
||||
style={{ padding: '0 5px' }}
|
||||
>
|
||||
<a onClick={() => onDeleteSelectedSocial(index)} style={{ verticalAlign: 'text-bottom' }}>x</a>
|
||||
</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<a onClick={onAddSocialMediaClicked}>
|
||||
<div className={'text-center mt-2 bg-secondary'} style={{ padding: '5px 10px', borderRadius: 7 }}>
|
||||
+ add social media
|
||||
</div>
|
||||
</a>
|
||||
<div className={'flex flex-row justify-between mt-7'}>
|
||||
<DefaultButton
|
||||
label="update"
|
||||
containerClassName="text-xs"
|
||||
onClick={onUpdateUserInfo}
|
||||
style={{ paddingLeft: 36, paddingRight: 36, textTransform: 'uppercase', }}
|
||||
className={isLoading ? 'pointer-events-none' : ''}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<WarningButton
|
||||
label="delete account"
|
||||
containerClassName="text-xs"
|
||||
style={{ textTransform: 'uppercase', paddingLeft: 8, paddingRight: 8 }}
|
||||
isLoading={isLoading}
|
||||
className={isLoading ? 'pointer-events-none' : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserSettings;
|
3
src/pages/UserSettings/styles.css
Normal file
3
src/pages/UserSettings/styles.css
Normal file
@ -0,0 +1,3 @@
|
||||
.button-avatar-container p a:hover {
|
||||
color: white;
|
||||
}
|
@ -10,6 +10,7 @@ import AddLocation from "./AddLocation";
|
||||
import Submissions from "./Submissions";
|
||||
import UserProfile from "./UserProfile";
|
||||
import UserFeed from "./UserFeed";
|
||||
import UserSettings from "./UserSettings";
|
||||
|
||||
export {
|
||||
Login,
|
||||
@ -17,6 +18,7 @@ export {
|
||||
Home,
|
||||
UserProfile,
|
||||
UserFeed,
|
||||
UserSettings,
|
||||
|
||||
NotFound,
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { useSelector } from "react-redux"
|
||||
import { UserRootState } from "src/store/type"
|
||||
|
||||
|
||||
export const ProtectedRoute = ({children}: any) => {
|
||||
export const AdminProtectedRoute = ({children}: any) => {
|
||||
|
||||
const user = useSelector((state: UserRootState) => state.auth)
|
||||
if(!user.is_admin) {
|
||||
@ -11,4 +11,13 @@ export const ProtectedRoute = ({children}: any) => {
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
export const UserProtectedRoute = ({ children }: any) => {
|
||||
const user = useSelector((state: UserRootState) => state.auth)
|
||||
if(Object.keys(user).length === 0) {
|
||||
return <Navigate to={"/"} replace />
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
@ -7,55 +7,92 @@ import {
|
||||
Story,
|
||||
AddLocation,
|
||||
UserProfile,
|
||||
UserFeed
|
||||
UserFeed,
|
||||
UserSettings,
|
||||
Submissions
|
||||
} from '../pages';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
element: <Home />
|
||||
},
|
||||
{
|
||||
path: "/best-places",
|
||||
name: "Home",
|
||||
element: <BestLocation />
|
||||
},
|
||||
{
|
||||
path: "/discover",
|
||||
name: "Home",
|
||||
element: <Discovery />
|
||||
},
|
||||
{
|
||||
path: "/stories",
|
||||
name: "Home",
|
||||
element: <Story />
|
||||
},
|
||||
{
|
||||
path: "/news-events",
|
||||
name: "Home",
|
||||
element: <NewsEvent />
|
||||
},
|
||||
{
|
||||
path: "/location/:id",
|
||||
name: "LocationDetail",
|
||||
element: <LocationDetail />
|
||||
},
|
||||
{
|
||||
path: "/add-location",
|
||||
name: "AddLocation",
|
||||
element: <AddLocation />
|
||||
},
|
||||
{
|
||||
path: "/user/profile",
|
||||
name: "UserProfile",
|
||||
element: <UserProfile />
|
||||
},
|
||||
{
|
||||
path: "/user/feed",
|
||||
name: "UserFeed",
|
||||
element: <UserFeed />
|
||||
},
|
||||
]
|
||||
interface BaseRoutes {
|
||||
path: string,
|
||||
name: string,
|
||||
protectedRoute?: string,
|
||||
element: any
|
||||
}
|
||||
|
||||
export default routes;
|
||||
export interface IRoutes {
|
||||
routes: Array<BaseRoutes>
|
||||
}
|
||||
|
||||
export const getRoutes = (): IRoutes => {
|
||||
let groupRoutes: Array<BaseRoutes> = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
element: <Home />
|
||||
},
|
||||
{
|
||||
path: "/best-places",
|
||||
name: "Home",
|
||||
element: <BestLocation />
|
||||
},
|
||||
{
|
||||
path: "/discover",
|
||||
name: "Home",
|
||||
element: <Discovery />
|
||||
},
|
||||
{
|
||||
path: "/stories",
|
||||
name: "Home",
|
||||
element: <Story />
|
||||
},
|
||||
{
|
||||
path: "/news-events",
|
||||
name: "Home",
|
||||
element: <NewsEvent />
|
||||
},
|
||||
{
|
||||
path: "/location/:id",
|
||||
name: "LocationDetail",
|
||||
element: <LocationDetail />
|
||||
},
|
||||
// PROTECTED USER ROUTES
|
||||
{
|
||||
path: "/add-location",
|
||||
name: "AddLocation",
|
||||
protectedRoute: "user",
|
||||
element: AddLocation
|
||||
},
|
||||
{
|
||||
path: "/user/profile",
|
||||
name: "UserProfile",
|
||||
protectedRoute: "user",
|
||||
element: UserProfile
|
||||
},
|
||||
{
|
||||
path: "/user/settings",
|
||||
name: "UserSettings",
|
||||
protectedRoute: "user",
|
||||
element: UserSettings
|
||||
},
|
||||
{
|
||||
path: "/user/feed",
|
||||
name: "UserFeed",
|
||||
protectedRoute: "user",
|
||||
element: UserFeed
|
||||
},
|
||||
//PROTECTED ADMIN ROUTES
|
||||
{
|
||||
path: "/admin/submissions",
|
||||
name: "Submissions",
|
||||
protectedRoute: "admin",
|
||||
element: Submissions
|
||||
},
|
||||
]
|
||||
|
||||
let routes: IRoutes = {
|
||||
routes: groupRoutes
|
||||
}
|
||||
|
||||
return routes
|
||||
|
||||
}
|
@ -16,7 +16,7 @@ interface IAuthentication {
|
||||
async function createAccountService({ username, password }: IAuthentication) {
|
||||
const newState = { ...initialState };
|
||||
try {
|
||||
const response = await client({ method: 'POST', url: SIGNUP_URI, data: { username, password } })
|
||||
const response = await client({ method: 'POST', url: SIGNUP_URI, data: { username, password }, withCredentials: true })
|
||||
newState.data = response.data
|
||||
newState.error = null
|
||||
return newState
|
||||
@ -29,7 +29,7 @@ async function createAccountService({ username, password }: IAuthentication) {
|
||||
async function loginService({ username, password }: IAuthentication) {
|
||||
const newState = { ...initialState };
|
||||
try {
|
||||
const response = await client({ method: 'POST', url: LOGIN_URI, data: { username, password } })
|
||||
const response = await client({ method: 'POST', url: LOGIN_URI, data: { username, password }, withCredentials: true })
|
||||
newState.data = response.data
|
||||
newState.error = null
|
||||
return newState
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { GET_CURRENT_USER_STATS } from "../constants/api";
|
||||
import { AxiosError } from "axios";
|
||||
import { DELETE_USER_AVATAR, GET_CURRENT_USER_STATS, PATCH_USER_AVATAR, PATCH_USER_INFO } from "../constants/api";
|
||||
import { IHttpResponse } from "../types/common";
|
||||
import { client } from "./config";
|
||||
import { UserInfo } from "../../src/domains/User";
|
||||
|
||||
|
||||
async function getUserStatsService(): Promise<IHttpResponse> {
|
||||
@ -19,6 +20,54 @@ async function getUserStatsService(): Promise<IHttpResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
async function patchUserAvatarService(form: FormData): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null};
|
||||
try {
|
||||
const res = await client({ method: "PATCH", url: PATCH_USER_AVATAR, data: form, 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);
|
||||
}
|
||||
}
|
||||
|
||||
async function patchUserInfoService(data: UserInfo): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null};
|
||||
try {
|
||||
const res = await client({ method: 'PATCH', url: PATCH_USER_INFO, data: data, 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);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUserAvatarService(): Promise<IHttpResponse> {
|
||||
const newState: IHttpResponse = { data: null, error: null};
|
||||
try {
|
||||
const res = await client({ method: 'DELETE', url: DELETE_USER_AVATAR, 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,
|
||||
patchUserAvatarService,
|
||||
deleteUserAvatarService,
|
||||
patchUserInfoService
|
||||
}
|
@ -32,3 +32,13 @@ export enum LocationType {
|
||||
HikingCamping = "hiking / camping",
|
||||
Other = "other"
|
||||
}
|
||||
|
||||
// https://www.similarweb.com/top-websites/indonesia/computers-electronics-and-technology/social-networks-and-online-communities/#:~:text=facebook.com%20ranked%20number%201,Media%20Networks%20websites%20in%20Indonesia.
|
||||
// maybe should add ppgames.net too BASED
|
||||
export enum SocialMediaEnum {
|
||||
Facebook = "facebook",
|
||||
Instagram = "instagram",
|
||||
Tiktox = "tiktok",
|
||||
X = "x",
|
||||
Youtube = "youtube"
|
||||
}
|
Loading…
Reference in New Issue
Block a user