Compare commits

...

11 Commits

16 changed files with 230 additions and 56 deletions

4
.env
View File

@@ -1,2 +1,2 @@
# NEXT_PUBLIC_BASE_URL=https://api-staging.cariparkir.co.id NEXT_PUBLIC_STAGING_CP_URL=https://api-staging.cariparkir.co.id
NEXT_PUBLIC_BASE_URL=https://d2cc-101-255-119-166.ap.ngrok.io NEXT_PUBLIC_BASE_URL=https://api-bookinglockey-staging.cariparkir.co.id

View File

@@ -52,10 +52,10 @@ const Ask = () => {
aplikasi ini.</p> aplikasi ini.</p>
</div> </div>
<div className={'absolute top-3 left-0 z-0'}> <div className={'absolute top-3 left-0 z-0'}>
<Image src={'/assets/backgrounds/cloud-left.svg'} alt={'cloud-left'}/> <Image src={'/assets/backgrounds/cloud-left.svg'} alt={'cloud-left'} preview={false} />
</div> </div>
<div className={'absolute top-1 right-0 z-0'}> <div className={'absolute top-1 right-0 z-0'}>
<Image src={'/assets/backgrounds/cloud-right.svg'} alt={'cloud-right'}/> <Image src={'/assets/backgrounds/cloud-right.svg'} alt={'cloud-right'} preview={false} />
</div> </div>
<div className={'flex flex-col gap-4 m-5'}> <div className={'flex flex-col gap-4 m-5'}>
@@ -66,7 +66,7 @@ const Ask = () => {
<span className={'font-bold'}>{it.no}</span> <span className={'font-bold'}>{it.no}</span>
<span className={'text-xs text-[#9B9B9B]'}>{it.timetable}</span> <span className={'text-xs text-[#9B9B9B]'}>{it.timetable}</span>
</div> </div>
<Image src={it.icon} alt={'icon'}width={40} height={40} /> <Image src={it.icon} alt={'icon'} width={40} height={40} preview={false} />
</a> </a>
))} ))}
</div> </div>

View File

@@ -2,19 +2,15 @@ import React from 'react';
import Image from "next/image"; import Image from "next/image";
import {Divider} from "antd"; import {Divider} from "antd";
const Location = () => { const Location = ({location}) => {
return ( return (
<div> <div>
<div className={'flex flex-col items-center gap-4'}> <div className={'flex flex-col items-center gap-4'}>
<Image src={'/assets/images/location.svg'} width={80} height={80} alt={'location'}/> <Image src={'/assets/images/location.svg'} width={80} height={80} alt={'location'}/>
<div className={'text-center'}> <div className={'text-center'}>
<h1 className={'font-bold'}>LOCKEY Lippo Mall Kemang</h1> <h1 className={'font-bold'}>{location?.data?.[0]?.name}</h1>
<p className={'text-sm text-[#B7BAC2]'}>Jl. Pangeran Antasari No.36, Bangka, Kec. Mampang Prpt., <p className={'text-sm text-[#B7BAC2]'}>{location?.data?.[0]?.address}</p>
Kota, Daerah
Khusus Ibukota
Jakarta
12150</p>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,4 @@
export const appConfig = { export const appConfig = {
apiUrl: process.env.NEXT_PUBLIC_BASE_URL apiUrl: process.env.NEXT_PUBLIC_BASE_URL,
apiCpUrl: process.env.NEXT_PUBLIC_STAGING_CP_URL,
} }

11
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"babel-plugin-import": "^1.13.5", "babel-plugin-import": "^1.13.5",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"framer-motion": "^7.6.1", "framer-motion": "^7.6.1",
"jwt-decode": "^3.1.2",
"mobx": "^6.6.1", "mobx": "^6.6.1",
"mobx-react-lite": "^3.4.0", "mobx-react-lite": "^3.4.0",
"next": "12.2.2", "next": "12.2.2",
@@ -3456,6 +3457,11 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/klona": { "node_modules/klona": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
@@ -8624,6 +8630,11 @@
"object.assign": "^4.1.2" "object.assign": "^4.1.2"
} }
}, },
"jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"klona": { "klona": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",

View File

@@ -19,6 +19,7 @@
"babel-plugin-import": "^1.13.5", "babel-plugin-import": "^1.13.5",
"date-fns": "^2.29.3", "date-fns": "^2.29.3",
"framer-motion": "^7.6.1", "framer-motion": "^7.6.1",
"jwt-decode": "^3.1.2",
"mobx": "^6.6.1", "mobx": "^6.6.1",
"mobx-react-lite": "^3.4.0", "mobx-react-lite": "^3.4.0",
"next": "12.2.2", "next": "12.2.2",

View File

@@ -1,4 +1,4 @@
import React, {useEffect} from 'react'; import React, {useEffect, useState} from 'react';
import DefaultLayout from "../../../components/Layout/DefaultLayout"; import DefaultLayout from "../../../components/Layout/DefaultLayout";
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Image } from 'antd'; import { Image } from 'antd';
@@ -9,6 +9,11 @@ import { observer } from 'mobx-react-lite';
const FlashScreen = observer(() => { const FlashScreen = observer(() => {
const router = useRouter(); const router = useRouter();
const store = useStore() const store = useStore()
const [startOrder, setStartOrder] = useState(false)
setTimeout(() => {
setStartOrder(true)
}, 3000)
const { qrCode } = router.query; const { qrCode } = router.query;
@@ -22,12 +27,22 @@ const FlashScreen = observer(() => {
if (isAvailable) { if (isAvailable) {
localStorage.setItem("lockey_id", isAvailable?.id) localStorage.setItem("lockey_id", isAvailable?.id)
localStorage.setItem("location_name", isAvailable?.location_name); localStorage.setItem("location_name", isAvailable?.location_name);
store.lockey.createOrder({ if (startOrder) {
lockeyId: isAvailable.id store.lockey.createOrder({
}) lockeyId: isAvailable.id
.then(res => {
router.push(`/${qrCode}/payment-order/`)
}) })
.then(res => {
router.push(`/${qrCode}/payment-order/`)
localStorage.setItem("idOrder", res?.data?.id)
localStorage.setItem("bookingCode", res?.data?.booking_code);
localStorage.setItem("checkInTime", res?.data?.check_in_time)
localStorage.setItem("validTime", res?.data?.valid_time)
localStorage.setItem("price", res?.data?.lockey?.price)
console.log(res, "data order")
})
} else {
console.log("loading")
}
} else if (isNotAvailable) { } else if (isNotAvailable) {
localStorage.setItem("lockey_id", isNotAvailable?.id) localStorage.setItem("lockey_id", isNotAvailable?.id)
localStorage.setItem("location_name", isNotAvailable?.location_name); localStorage.setItem("location_name", isNotAvailable?.location_name);
@@ -35,7 +50,7 @@ const FlashScreen = observer(() => {
} }
console.log(isAvailable, "jj") console.log(isAvailable, "jj")
} }
}, [router.isReady, listLockeys]) }, [startOrder])
return ( return (
<> <>

View File

@@ -1,22 +1,28 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import DefaultLayout from "../../../components/Layout/DefaultLayout"; import DefaultLayout from "../../../components/Layout/DefaultLayout";
import {observer} from "mobx-react-lite"; import {observer} from "mobx-react-lite";
import Image from "next/image"; import {Button, Divider, Form, Image, Input, Modal, Spin} from "antd";
import {Button, Divider, Form, Input, Modal, Spin} from "antd";
import Constraint from "../../../components/Constraint"; import Constraint from "../../../components/Constraint";
import Ask from "../../../components/Ask"; import Ask from "../../../components/Ask";
import Location from "../../../components/Location"; import Location from "../../../components/Location";
import DownloadApps from "../../../components/DownloadApps"; import DownloadApps from "../../../components/DownloadApps";
import Sheet from 'react-modal-sheet'; import Sheet from 'react-modal-sheet';
import Countdown from "react-countdown"; import Countdown, { zeroPad } from "react-countdown";
import {useRouter} from "next/router"; import {useRouter} from "next/router";
import BottomSheet from "../../../components/BottomSheet"; import BottomSheet from "../../../components/BottomSheet";
import { botsRepository } from '../../../repository/bots'; import { botsRepository } from '../../../repository/bots';
import { useStore } from '../../../components/StoreProvider';
import { format } from 'date-fns';
import Link from 'next/link';
const Payment = observer(() => { const Payment = observer(() => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const router = useRouter(); const router = useRouter();
const store = useStore();
const { qrCode } = router.query; const { qrCode } = router.query;
const [orderId, setOrderId] = useState('');
const [price, setPrice] = useState(0);
const [checkInTime, setCheckInTime] = useState('');
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [openOrderOut, setOpenOrderOut] = useState(false); const [openOrderOut, setOpenOrderOut] = useState(false);
const [notOrder, setNotOrder] = useState(false); const [notOrder, setNotOrder] = useState(false);
@@ -31,30 +37,70 @@ const Payment = observer(() => {
}; };
const { data: location } = botsRepository.hooks.useGetLocationByQR(qrCode) const { data: location } = botsRepository.hooks.useGetLocationByQR(qrCode)
console.log(location, "he")
const expiredOrder = async () => {
try {
await botsRepository.api.updateStatusExpiredOrder(orderId)
setIsNotComplete(true)
setOpenOrderOut(true)
} catch (err) {
console.log("session epxired failed")
setIsNotComplete(true)
setOpenOrderOut(true)
}
}
const renderer = ({minutes, seconds, completed}) => { const renderer = ({minutes, seconds, completed}) => {
if (completed) { if (completed) {
// router.push('/flash-screen'); console.log("session complite")
} else { } else {
return <span>{minutes}:{seconds}</span>; return <span>{zeroPad(minutes)}:{zeroPad(seconds)}</span>;
} }
}; };
useEffect(() => {
if (typeof window !== undefined) {
let price = localStorage.getItem("price");
let getCheckInTime = localStorage.getItem("checkInTime");
let orderId = localStorage.getItem("idOrder")
setOrderId(orderId);
setPrice(price)
const checkInTime = format(new Date(getCheckInTime), "mm")
console.log(checkInTime, "time")
}
}, [])
const handleSubmitPhoneNumber = async () => {
try {
const values = await form.validateFields();
setOpen(false);
setIsLoading(true);
const body = {
phoneNumber: values.phoneNumber,
};
await store.lockey.createPayment(body);
form.resetFields();
setIsLoading(false)
} catch (err) {
console.log(err);
setIsLoading(false);
}
}
return ( return (
<> <>
{isNotComplete ? ( {isNotComplete ? (
<div className="flex items-center justify-center bg-[#FF4F34] h-10 text-base"> <div className="flex items-center justify-center bg-[#FF4F34] h-10 text-base">
<span className={'text-white'}>Batas Penyelesaian Pesanan mu Habis</span> <span className={'text-white'}>Batas Penyelesaian pesanan Anda Habis</span>
</div> </div>
) : ( ) : (
<div className="flex items-center justify-center bg-[#00AED6] h-10 text-base"> <div className="flex items-center justify-center bg-[#00AED6] h-10 text-base">
<span className={'text-white'}>Selesaikan Pesanan mu dalam <Countdown <span className={'text-white'}>selesaikan pesananmu dalam <Countdown
date={Date.now() + 300000} date={Date.now(checkInTime) + 300000}
renderer={renderer} renderer={renderer}
onComplete={() => { onComplete={expiredOrder}/>
setIsNotComplete(true)
setOpenOrderOut(true)
}}/>
</span> </span>
</div> </div>
)} )}
@@ -66,7 +112,7 @@ const Payment = observer(() => {
<Form form={form} layout={'vertical'}> <Form form={form} layout={'vertical'}>
<div className={'mt-5'}> <div className={'mt-5'}>
<Form.Item name="phone" <Form.Item name="phoneNumber"
label={<span className={'font-semibold text-xl'}>Nomor Telepon Pembayaran</span>} label={<span className={'font-semibold text-xl'}>Nomor Telepon Pembayaran</span>}
rules={[ rules={[
{required: true, message: "Silahkan masukan Nomor Ponsel!"}, {required: true, message: "Silahkan masukan Nomor Ponsel!"},
@@ -76,7 +122,7 @@ const Payment = observer(() => {
type={'number'} type={'number'}
className={'rounded-lg'} className={'rounded-lg'}
size={'large'} size={'large'}
suffix={<Image src={'/assets/icons/gopay.svg'} width={80} height={30} alt={'icon gopay'} />} suffix={<Image src={'/assets/icons/gopay.svg'} width={80} height={30} preview={false} alt={'icon gopay'} />}
placeholder={'08xxxxxxxxx'} placeholder={'08xxxxxxxxx'}
disabled={ disabled={
notOrder === true notOrder === true
@@ -93,7 +139,7 @@ const Payment = observer(() => {
<div className={'flex justify-between'}> <div className={'flex justify-between'}>
<h3 className={'text-lg font-bold'}>Total Pembayaran</h3> <h3 className={'text-lg font-bold'}>Total Pembayaran</h3>
<h3 className={'text-lg font-bold text-[#FF6103]'}>Rp. 99.000</h3> <h3 className={'text-lg font-bold text-[#FF6103]'}>Rp. {price}</h3>
</div> </div>
<Form.Item shouldUpdate> <Form.Item shouldUpdate>
@@ -127,14 +173,16 @@ const Payment = observer(() => {
{/*MODAL LOADING*/} {/*MODAL LOADING*/}
<Modal <Modal
className={'rounded-lg'} className={'rounded-3xl'}
open={isLoading} open={isLoading}
footer={null} footer={null}
closable={false} closable={false}
maskClosable={false} maskClosable={false}
bodyStyle={{paddingLeft: 5, paddingRight: 5}}> bodyStyle={{paddingLeft: 5, paddingRight: 5}}>
<div className={'flex flex-col justify-center'}> <div className={'flex flex-col justify-center items-center'}>
<Spin size={'large'}/> <div className='flex justify-center items-center'>
<Image src={'/assets/icons/Load_Logo_CP.gif'} width={35} height={48} alt={'gif'} preview={false} />
</div>
<span className={'text-center font-semibold'}>Mohon tunggu</span> <span className={'text-center font-semibold'}>Mohon tunggu</span>
<span className={'text-center text-sm'}>Saat ini pemesanan LOCKEY anda sedang di proses. </span> <span className={'text-center text-sm'}>Saat ini pemesanan LOCKEY anda sedang di proses. </span>
</div> </div>
@@ -145,7 +193,7 @@ const Payment = observer(() => {
<div className={'px-5 max-w-lg'}> <div className={'px-5 max-w-lg'}>
<div className={'flex justify-center'}> <div className={'flex justify-center'}>
<Image src={'/assets/images/confirmation.svg'} width={200} height={200} <Image src={'/assets/images/confirmation.svg'} width={200} height={200}
alt={'confirmation'}/> alt={'confirmation'} preview={false} />
</div> </div>
<h3 className={'text-lg font-bold'}>Konfirmasi Pembayaran?</h3> <h3 className={'text-lg font-bold'}>Konfirmasi Pembayaran?</h3>
@@ -154,11 +202,8 @@ const Payment = observer(() => {
<div className={'flex justify-between gap-4'}> <div className={'flex justify-between gap-4'}>
<Button size={'large'} className={'w-full rounded-lg'} <Button size={'large'} className={'w-full rounded-lg'}
onClick={onCloseSheet}>Cancel</Button> onClick={onCloseSheet}>Cancel</Button>
<Button size={'large'} className={'w-full rounded-lg bg-black text-white'} <Button size={'large'} htmlType={'submit'} className={'w-full rounded-lg bg-black text-white'}
onClick={() => { onClick={handleSubmitPhoneNumber}>Bayar</Button>
setOpen(false)
setIsLoading(true)
}}>Bayar</Button>
</div> </div>
</div> </div>
</BottomSheet> </BottomSheet>
@@ -167,8 +212,8 @@ const Payment = observer(() => {
<BottomSheet onOpen={openOrderOut} onClose={() => setOpenOrderOut(false)}> <BottomSheet onOpen={openOrderOut} onClose={() => setOpenOrderOut(false)}>
<div className={'px-5 max-w-lg'}> <div className={'px-5 max-w-lg'}>
<div className={'flex justify-center'}> <div className={'flex justify-center'}>
<Image src={'/assets/images/illustration-order-out.svg'} width={200} height={200} <Image src={'/assets/images/Illustration-order-out.svg'} width={200} height={200}
alt={'confirmation'}/> alt={'confirmation'} preview={false} />
</div> </div>
<h3 className={'text-lg font-bold'}>Ingin pesan kembali?</h3> <h3 className={'text-lg font-bold'}>Ingin pesan kembali?</h3>

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -1,4 +1,4 @@
import {http} from "../utils/http"; import { http2 } from "../utils/http2"
const url = { const url = {
contacts: () => '/product/api/v5/customer-service' contacts: () => '/product/api/v5/customer-service'
@@ -8,7 +8,7 @@ const hooks = {}
const api = { const api = {
async getContacts() { async getContacts() {
return await http.fetcher(url.contacts()) return await http2.fetcher(url.contacts())
}, },
} }

View File

@@ -6,6 +6,7 @@ const url = {
findLocationByQrCode: (qrCode) => `/locations/findOne/${qrCode}`, findLocationByQrCode: (qrCode) => `/locations/findOne/${qrCode}`,
createOrder: () => `/bots/create-order`, createOrder: () => `/bots/create-order`,
checkTransaction: (orderId) => `/bots/find/one-history/${orderId}`, checkTransaction: (orderId) => `/bots/find/one-history/${orderId}`,
expiredOrder: (id) => `/bots/expired-status/${id}`,
} }
const hooks = { const hooks = {
@@ -22,6 +23,14 @@ const api = {
const res = await http.fetcher(url.checkTransaction(orderId)); const res = await http.fetcher(url.checkTransaction(orderId));
return res return res
}, },
async createOrder(body) {
return await http.post(url.createOrder(), body)
},
updateStatusExpiredOrder(id) {
return http.put(url.expiredOrder(id));
}
} }
export const botsRepository = { export const botsRepository = {

View File

@@ -1,16 +1,13 @@
import {http} from "../utils/http"; import {http} from "../utils/http";
const url = { const url = {
payment: () => '/usermanagement/api/v5/validate/check-phone-register' payment: () => '/usermanagement/api/v5/validate/check-phone-register',
sendInvoice: () => '/payment-service/api/v6/send-invoice',
} }
const hooks = {} const hooks = {}
const api = { const api = {}
async usePayment(data) {
return await http.post(url.payment, data)
},
}
export const orderRepository = { export const orderRepository = {
url, url,

View File

@@ -1,5 +1,7 @@
import {http} from "../utils/http"; import { http } from "../utils/http";
import { http2 } from "../utils/http2";
import {botsRepository} from "../repository/bots"; import {botsRepository} from "../repository/bots";
import { orderRepository } from "../repository/order";
export class LockeyStore { export class LockeyStore {
constructor(context) { constructor(context) {
@@ -9,4 +11,8 @@ export class LockeyStore {
createOrder(body) { createOrder(body) {
return http.post(botsRepository.url.createOrder(), body); return http.post(botsRepository.url.createOrder(), body);
} }
createPayment(body) {
return http2.post(orderRepository.url.payment(), body);
}
} }

View File

@@ -19,3 +19,13 @@ body {
flex-direction: column !important; flex-direction: column !important;
align-items: center !important; align-items: center !important;
} }
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number]{
-moz-appearance: textfield;
}

78
utils/http2.js Normal file
View File

@@ -0,0 +1,78 @@
import {appConfig} from "../config/app";
import {TokenUtil} from "./token";
import axios from "axios";
const instanceCp = axios.create({
baseURL: appConfig.apiCpUrl,
headers: {
"Content-Type": "application/json",
},
});
instanceCp.interceptors.request.use(
(config) => {
if (TokenUtil.accessToken) {
config.headers["Authorization"] = 'Bearer ' + TokenUtil.accessToken; // for Node.js Express back-end
}
config.headers["ngrok-skip-browser-warning"] = true
return config;
},
(error) => {
return Promise.reject(error);
}
);
instanceCp.interceptors.response.use(
(res) => {
return res;
},
async (err) => {
const originalConfig = err.config;
if (originalConfig.url !== "/auth/login" && err.response) {
// Access Token was expired
if (err.response.status === 401 && !originalConfig._retry) {
originalConfig._retry = true;
try {
if (TokenUtil.refreshToken) {
await authenticationRepository.api.refreshToken()
}
return instanceCp(originalConfig);
} catch (_error) {
return Promise.reject(_error);
}
}
}
return Promise.reject(err);
}
);
export const http2 = {
fetcher: async (url) => {
const resp = await instanceCp.get(appConfig.apiCpUrl + url);
return resp.data;
},
get: async (url, opts = {}) => {
const resp = await instanceCp.post(appConfig.apiCpUrl + url);
return resp.data;
},
post: async (url, data, opts) => {
const resp = await instanceCp.post(appConfig.apiCpUrl + url, data);
return resp.data;
},
put: async (url, data, opts) => {
const resp = await instanceCp.put(appConfig.apiCpUrl + url, data);
return resp.data;
},
del: async (url, opts) => {
const resp = await instanceCp.delete(appConfig.apiCpUrl + url);
return resp.data;
},
};

View File

@@ -2096,6 +2096,11 @@
"array-includes" "^3.1.3" "array-includes" "^3.1.3"
"object.assign" "^4.1.2" "object.assign" "^4.1.2"
"jwt-decode@^3.1.2":
"integrity" "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
"resolved" "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz"
"version" "3.1.2"
"klona@^2.0.4": "klona@^2.0.4":
"integrity" "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==" "integrity" "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA=="
"resolved" "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz" "resolved" "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz"