From 946bed296abb7b918eea94dd43d31eee77ab8945 Mon Sep 17 00:00:00 2001 From: NCanggoro Date: Wed, 11 Oct 2023 16:31:25 +0700 Subject: [PATCH] add news evenst page --- .../Separator/NavigationSeparator/index.tsx | 27 ++ .../Separator/NavigationSeparator/style.css | 3 + src/components/index.ts | 2 + src/constants/api.ts | 5 + src/constants/default.ts | 3 +- src/domains/NewsEvent.ts | 29 +++ src/pages/NewsEvent/index.tsx | 241 ++++++++++++++++++ src/pages/NewsEvent/style.css | 4 + src/pages/NewsEvents/index.tsx | 10 - src/pages/index.tsx | 2 +- src/services/index.ts | 4 + src/services/news.ts | 50 ++++ 12 files changed, 368 insertions(+), 12 deletions(-) create mode 100644 src/components/Separator/NavigationSeparator/index.tsx create mode 100644 src/components/Separator/NavigationSeparator/style.css create mode 100644 src/domains/NewsEvent.ts create mode 100644 src/pages/NewsEvent/index.tsx create mode 100644 src/pages/NewsEvent/style.css delete mode 100644 src/pages/NewsEvents/index.tsx create mode 100644 src/services/news.ts diff --git a/src/components/Separator/NavigationSeparator/index.tsx b/src/components/Separator/NavigationSeparator/index.tsx new file mode 100644 index 0000000..8f0a824 --- /dev/null +++ b/src/components/Separator/NavigationSeparator/index.tsx @@ -0,0 +1,27 @@ +import { CSSProperties, TargetedEvent } from 'preact/compat'; +import './style.css'; + +type NavigationSeparatorProps = { + pageNames: Array, + onClick: (e: TargetedEvent, name: string) => void, + titleStyles?: any, + containerStyle?: CSSProperties + selectedValue?: string | number +} + +function NavigationSeparator(props: NavigationSeparatorProps) { + return ( +
+ {props.pageNames.map(x => ( + props.onClick(e, x)}> + {x} + + ))} +
+ ) +} + +export default NavigationSeparator; \ No newline at end of file diff --git a/src/components/Separator/NavigationSeparator/style.css b/src/components/Separator/NavigationSeparator/style.css new file mode 100644 index 0000000..501dbb2 --- /dev/null +++ b/src/components/Separator/NavigationSeparator/style.css @@ -0,0 +1,3 @@ +.navigation-separator-text:hover { + color: white +} \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index 728ad16..03a4870 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,6 +8,7 @@ import DefaultButton from "./Button/DefaultButton"; import SeparatorWithAnchor from "./Separator/WithAnchor"; import DefaultSeparator from "./Separator/Default"; import TitleSeparator from "./Separator/TitleSeparator"; +import NavigationSeparator from "./Separator/NavigationSeparator"; import Footer from './Footer/'; import CustomInterweave from "./CustomInterweave"; import DropdownInput from "./DropdownInput"; @@ -24,6 +25,7 @@ export { SeparatorWithAnchor, DefaultSeparator, TitleSeparator, + NavigationSeparator, Footer, CustomInterweave, DropdownInput, diff --git a/src/constants/api.ts b/src/constants/api.ts index e8a36ab..df7a211 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -13,6 +13,9 @@ 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_NEWS_EVENTS_URI = `${BASE_URL}/news-events`; +const POST_NEWS_EVENTS_URI = GET_NEWS_EVENTS_URI + 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` @@ -31,6 +34,7 @@ export { LOGOUT_URI, SIGNUP_URI, DELETE_USER_AVATAR, + GET_NEWS_EVENTS_URI, GET_REGIONS, GET_PROVINCES, GET_REGENCIES, @@ -46,4 +50,5 @@ export { PATCH_USER_INFO, POST_REVIEW_LOCATION_URI, POST_CREATE_LOCATION, + POST_NEWS_EVENTS_URI, } \ No newline at end of file diff --git a/src/constants/default.ts b/src/constants/default.ts index f9d1cf7..3d485ee 100644 --- a/src/constants/default.ts +++ b/src/constants/default.ts @@ -1,2 +1,3 @@ -export const DEFAULT_AVATAR_IMG = 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'; +export const DEFAULT_AVATAR_IMG = 'https://cdn.discordapp.com/attachments/743422487882104837/1153985664849805392/421-4212617_person-placeholder-image-transparent-hd-png-download.png'; +export const DEFAULT_LOCATION_THUMBNAIL_IMG = 'https://i.ytimg.com/vi/0DY1WSk8B9o/maxresdefault.jpg'; diff --git a/src/domains/NewsEvent.ts b/src/domains/NewsEvent.ts new file mode 100644 index 0000000..3cd10f9 --- /dev/null +++ b/src/domains/NewsEvent.ts @@ -0,0 +1,29 @@ +export type News = { + id: number, + title: string, + url: string + source: string + thumbnail: string, + description: string + is_deleted: boolean, + submitted_by: string, + approved_by: number, + created_at: string, + updated_at: string +} + +export function emptyNewsEvent(): News { + return { + id: 0, + title: '', + url: '', + source: '', + thumbnail: '', + description: '', + is_deleted: false, + submitted_by: '', + approved_by: 0, + created_at: '', + updated_at: '', + } +} \ No newline at end of file diff --git a/src/pages/NewsEvent/index.tsx b/src/pages/NewsEvent/index.tsx new file mode 100644 index 0000000..156f336 --- /dev/null +++ b/src/pages/NewsEvent/index.tsx @@ -0,0 +1,241 @@ +import { ChangeEvent, TargetedEvent, useEffect, useRef, useState } from "preact/compat"; +import { DefaultButton, NavigationSeparator } from "../../components"; +import { News } from "../../domains/NewsEvent"; +import { getNewsServices, postNewsService } from "../../../src/services"; +import { AxiosError } from "axios"; +import { DEFAULT_LOCATION_THUMBNAIL_IMG } from "../../../src/constants/default"; +import { isUrl, useAutosizeTextArea } from "../../../src/utils"; +import ReactTextareaAutosize from "react-textarea-autosize"; +import { IHttpResponse } from "../../../src/types/common"; +import { useSelector } from "react-redux"; +import { UserRootState } from "src/store/type"; +import "./style.css" +import useCallbackState from "../../../src/types/state-callback"; + +function NewsEvent() { + const [news, setNews] = useState>([]); + const [isLoading, setIsLoading] = useState(false); + const [pageState, setPageState] = useState({ + news_category_name: "hot", + page: 1, + }) + + const user = useSelector((state: UserRootState) => state.auth) + + const [form, setForm] = useState({ + description: '', + url: '', + title: '', + title_error_msg: '', + link_error_msg: '', + }) + + const textAreaRef = useRef(null); + useAutosizeTextArea(textAreaRef.current, form.description); + + async function getNewsEvents(approved: number = 1) { + try { + const news = await getNewsServices({ page: pageState.page, page_size: 10, is_with_approval: approved}) + if (news.status == 200) { + setNews(news.data) + } + console.log(news) + } catch (error) { + let err = error as AxiosError; + if (!err.status) { + alert('Server is in trouble, probably dead RIP'); + } + console.log(err) + } + } + + function handleTextAreaChange(e: ChangeEvent): void { + const val = e.target as HTMLTextAreaElement; + + setForm({ + ...form, + description: val.value + }) + } + + function handleInputChange(e: ChangeEvent): void { + const target = e.target as HTMLInputElement; + + + setForm({ + ...form, + [target.name]: target.value, + link_error_msg: '', + title_error_msg: '' + }) + } + + function isFormEmpty() { + const { url, title} = form; + let url_error = '' + let title_error = '' + + if(url == '') url_error = "url mustn't empty" + if(title == '') title_error = "title mustn't empty" + + return {url_error, title_error} + } + + function handleOnChangeNewsCategory(e: TargetedEvent, name: string) { + e.preventDefault(); + + if(name.toLowerCase() === "fresh") { + setPageState({ ...pageState, news_category_name: "fresh" }) + getNewsEvents(0) + return + } + + setPageState({ ...pageState, news_category_name: "hot" }) + getNewsEvents(1) + } + + async function handleSumbitNews(e: TargetedEvent) { + e.preventDefault(); + setIsLoading(true) + + const {url_error, title_error } = isFormEmpty() + if(url_error !== "" || title_error !== "") { + setForm({ ...form, link_error_msg: url_error, title_error_msg: title_error}) + setIsLoading(false) + return + } + + if(!isUrl(form.url)) { + setForm({ ...form, link_error_msg: 'url is not correct(https://validurl.com")'}) + setIsLoading(false) + return + } + + try { + const response = await postNewsService({ + description: form.description, + submitted_by: user.id, + title: form.title, + url: form.url + }) + if(response.status == 201) { + alert("success") + setForm({ + link_error_msg: '', + title_error_msg: '', + title: '', + description: '', + url: '', + }) + } + setIsLoading(false) + } catch(error) { + setIsLoading(false) + let err = error as IHttpResponse; + console.log(err) + } + } + + useEffect(() => { + getNewsEvents() + }, []) + + return ( +
+

News / Events

+
+ +
+ {news.map(x => ( +
+ +
+ +
+
+
+
+ {x.title} +
+ +
+
+ {x.source} +
+
+ 1d ago by + {x.submitted_by} +
+
+ + +
+
+
+ ))} +
+ +
+
+

SUBMIT A NEWS / EVENT

+

Title

+ + {form.title_error_msg} +

Link

+ + {form.link_error_msg} +

Description (optional)

+ + +
+
+
+
+
+ ) + +} + +export default NewsEvent; \ No newline at end of file diff --git a/src/pages/NewsEvent/style.css b/src/pages/NewsEvent/style.css new file mode 100644 index 0000000..fdfcd8b --- /dev/null +++ b/src/pages/NewsEvent/style.css @@ -0,0 +1,4 @@ +.submitted-info::before { + content: " // "; + margin-left: 5px; +} \ No newline at end of file diff --git a/src/pages/NewsEvents/index.tsx b/src/pages/NewsEvents/index.tsx deleted file mode 100644 index 86ae15a..0000000 --- a/src/pages/NewsEvents/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -function NewsEvent() { - return( - <> -

Best PLaces

- - ) - -} - -export default NewsEvent; \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b2cc6f8..4b4ed1f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -2,7 +2,7 @@ import Home from "./Home"; import BestLocation from "./BestLocations"; import Discovery from "./Discovery"; import Story from "./Stories"; -import NewsEvent from "./NewsEvents"; +import NewsEvent from "./NewsEvent"; import LocationDetail from "./LocationDetail"; import Login from './Login'; import NotFound from "./NotFound"; diff --git a/src/services/index.ts b/src/services/index.ts index 775f453..3784e2b 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -10,6 +10,7 @@ import { createAccountService, loginService, logoutService } from "./auth"; import { postReviewLocation, getCurrentUserLocationReviewService } from "./review"; import { getRegionsService, getProvincesService, getRegenciesService} from "./regions"; import { getUserStatsService } from "./users"; +import { getNewsServices, postNewsService} from "./news"; export { createAccountService, @@ -31,4 +32,7 @@ export { postReviewLocation, getCurrentUserLocationReviewService, + + getNewsServices, + postNewsService } \ No newline at end of file diff --git a/src/services/news.ts b/src/services/news.ts new file mode 100644 index 0000000..0f0b6e2 --- /dev/null +++ b/src/services/news.ts @@ -0,0 +1,50 @@ +import { AxiosError } from "axios"; +import { GetRequestPagination, IHttpResponse } from "..//types/common"; +import { client } from "./config"; +import { GET_NEWS_EVENTS_URI, POST_NEWS_EVENTS_URI } from "../../src/constants/api"; + +interface GetNewsSevice extends GetRequestPagination { + is_with_approval: number +} + +async function getNewsServices({ page, page_size, is_with_approval}: GetNewsSevice): Promise { + const newState: IHttpResponse = { data: null, error: null}; + try { + const response = await client({ method: 'GET', url: `${GET_NEWS_EVENTS_URI}?page=${page}&page_size=${page_size}&is_with_approval=${is_with_approval}`}); + newState.data = response.data; + newState.status = response.status; + return newState; + } catch (error) { + let err = error as AxiosError; + newState.error = err; + newState.status = err.status + throw(newState) + } +} + +interface PostNewsServiceBody { + title: string, + url: string, + description: string, + submitted_by: number +} + +async function postNewsService(req: PostNewsServiceBody): Promise { + const newState: IHttpResponse = { data: null, error: null} + try { + const response = await client({ method: 'POST', url: POST_NEWS_EVENTS_URI, data: req, withCredentials: true}) + newState.data = response.data + newState.status = response.status + return newState + } catch (error) { + let err = error as AxiosError; + newState.error = err; + newState.status = err.status + throw(newState) + } +} + +export { + getNewsServices, + postNewsService +} \ No newline at end of file