Compare commits
3 Commits
87616ef6bd
...
1585830184
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1585830184 | ||
|
|
22918b8bdb | ||
|
|
d32eb2e7ed |
@ -8,6 +8,7 @@ const adResponseSchema = z.object({
|
|||||||
url: z.string(),
|
url: z.string(),
|
||||||
start_date: z.string(),
|
start_date: z.string(),
|
||||||
end_date: z.string(),
|
end_date: z.string(),
|
||||||
|
clicked: z.number(),
|
||||||
})
|
})
|
||||||
const adsResponseSchema = z.object({
|
const adsResponseSchema = z.object({
|
||||||
data: z.array(adResponseSchema),
|
data: z.array(adResponseSchema),
|
||||||
|
|||||||
30
app/apis/news/create-log-ads.ts
Normal file
30
app/apis/news/create-log-ads.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { HttpServer, type THttpServer } from '~/libs/http-server'
|
||||||
|
import type { TAdsSchema } from '~/pages/form-advertisements'
|
||||||
|
|
||||||
|
const logAdsResponseSchema = z.object({
|
||||||
|
data: z.object({
|
||||||
|
Message: z.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
type TParameters = {
|
||||||
|
id: TAdsSchema['id']
|
||||||
|
} & THttpServer
|
||||||
|
|
||||||
|
export const createLogAdsRequest = async (parameters: TParameters) => {
|
||||||
|
const { id, ...restParameters } = parameters
|
||||||
|
const payload = {
|
||||||
|
ads_id: id,
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { data } = await HttpServer(restParameters).post(
|
||||||
|
'/api/logs/ads',
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
return logAdsResponseSchema.parse(data)
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line unicorn/no-useless-promise-resolve-reject
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
|
import { Button } from '@headlessui/react'
|
||||||
import Autoplay from 'embla-carousel-autoplay'
|
import Autoplay from 'embla-carousel-autoplay'
|
||||||
import useEmblaCarousel from 'embla-carousel-react'
|
import useEmblaCarousel from 'embla-carousel-react'
|
||||||
import { Link, useRouteLoaderData } from 'react-router'
|
import { useFetcher, useRouteLoaderData } from 'react-router'
|
||||||
|
|
||||||
import type { loader } from '~/routes/_news'
|
import type { loader } from '~/routes/_news'
|
||||||
|
|
||||||
@ -8,6 +9,7 @@ export const Banner = () => {
|
|||||||
const loaderData = useRouteLoaderData<typeof loader>('routes/_news')
|
const loaderData = useRouteLoaderData<typeof loader>('routes/_news')
|
||||||
const { adsData } = loaderData || {}
|
const { adsData } = loaderData || {}
|
||||||
const [emblaReference] = useEmblaCarousel({ loop: true }, [Autoplay()])
|
const [emblaReference] = useEmblaCarousel({ loop: true }, [Autoplay()])
|
||||||
|
const fetcher = useFetcher()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
@ -17,24 +19,25 @@ export const Banner = () => {
|
|||||||
ref={emblaReference}
|
ref={emblaReference}
|
||||||
>
|
>
|
||||||
<div className="embla__container flex">
|
<div className="embla__container flex">
|
||||||
{adsData?.map(({ image_url: urlImage, url: link, id }, index) => (
|
{adsData?.map(({ image_url: urlImage, url, id }, index) => (
|
||||||
<div
|
<fetcher.Form
|
||||||
|
method="POST"
|
||||||
|
action={`/actions/log/ads/${id}`}
|
||||||
key={index}
|
key={index}
|
||||||
className="embla__slide max-h-[100px] min-h-[65px] w-full min-w-0 flex-none"
|
className="embla__slide max-h-[100px] min-h-[65px] w-full min-w-0 flex-none"
|
||||||
>
|
>
|
||||||
<Link
|
<Button
|
||||||
to={link}
|
className="h-full w-full cursor-pointer py-2"
|
||||||
className="mt-2 h-full py-2"
|
type="submit"
|
||||||
target="_blank"
|
onClick={() => window.open(url, '_blank')}
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={urlImage}
|
src={urlImage}
|
||||||
alt={id}
|
alt={id}
|
||||||
className="h-[70px] w-[100%] object-contain object-center sm:h-full"
|
className="h-[70px] w-[100%] object-contain object-center sm:h-full"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Button>
|
||||||
</div>
|
</fetcher.Form>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -51,6 +51,10 @@ export const AdvertisementsPage = () => {
|
|||||||
return formatDate(data)
|
return formatDate(data)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Jumlah Klik',
|
||||||
|
data: 'clicked',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Action',
|
title: 'Action',
|
||||||
data: 'id',
|
data: 'id',
|
||||||
@ -66,7 +70,7 @@ export const AdvertisementsPage = () => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
5: (value: string, _type: unknown, data: TAdResponse) => (
|
6: (value: string, _type: unknown, data: TAdResponse) => (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button
|
<Button
|
||||||
as="a"
|
as="a"
|
||||||
|
|||||||
48
app/routes/actions.log.ads.$id.ts
Normal file
48
app/routes/actions.log.ads.$id.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { data } from 'react-router'
|
||||||
|
import { XiorError } from 'xior'
|
||||||
|
|
||||||
|
import { createLogAdsRequest } from '~/apis/news/create-log-ads'
|
||||||
|
import { handleCookie } from '~/libs/cookies'
|
||||||
|
|
||||||
|
import type { Route } from './+types/actions.log.ads.$id'
|
||||||
|
|
||||||
|
export const action = async ({ request, params }: Route.ActionArgs) => {
|
||||||
|
const { userToken: accessToken } = await handleCookie(request)
|
||||||
|
const { id } = params
|
||||||
|
try {
|
||||||
|
const { data: logsData } = await createLogAdsRequest({
|
||||||
|
id,
|
||||||
|
accessToken,
|
||||||
|
})
|
||||||
|
|
||||||
|
return data(
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
logsData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof XiorError) {
|
||||||
|
return data(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: error?.response?.data?.error?.message || error.message,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: error?.response?.status || 500,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return data(
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
message: 'Internal server error',
|
||||||
|
},
|
||||||
|
{ status: 500 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import { getValidatedFormData } from 'remix-hook-form'
|
|||||||
import { XiorError } from 'xior'
|
import { XiorError } from 'xior'
|
||||||
|
|
||||||
import { updateSubscribeRequest } from '~/apis/admin/update-subscribe'
|
import { updateSubscribeRequest } from '~/apis/admin/update-subscribe'
|
||||||
import { getUser } from '~/apis/news/get-user'
|
|
||||||
import {
|
import {
|
||||||
subscribeSchema,
|
subscribeSchema,
|
||||||
type TSubscribeSchema,
|
type TSubscribeSchema,
|
||||||
@ -34,18 +33,15 @@ export const action = async ({ request }: Route.ActionArgs) => {
|
|||||||
console.log('payload', payload) // eslint-disable-line no-console
|
console.log('payload', payload) // eslint-disable-line no-console
|
||||||
|
|
||||||
// TODO: will run after payment success
|
// TODO: will run after payment success
|
||||||
const { data: updateSubscribeData } = await updateSubscribeRequest({
|
const { data: subscribeData } = await updateSubscribeRequest({
|
||||||
payload,
|
payload,
|
||||||
accessToken,
|
accessToken,
|
||||||
})
|
})
|
||||||
console.log(updateSubscribeData) // eslint-disable-line no-console
|
|
||||||
|
|
||||||
const { data: userData } = await getUser({ accessToken })
|
|
||||||
|
|
||||||
return data(
|
return data(
|
||||||
{
|
{
|
||||||
success: true,
|
success: true,
|
||||||
user: userData,
|
subscribeData,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user