Update Mechanism

master
Gilang R 2025-04-06 16:28:28 +07:00
parent 61e4e44767
commit 0310a7f94b
Signed by: sinyo1015
GPG Key ID: D40305586B71891C
9 changed files with 575 additions and 84 deletions

View File

@ -10,17 +10,22 @@
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.8.4",
"cron": "^4.1.3",
"dayjs": "^1.11.13",
"js-cookie": "^3.0.5",
"preline": "^2.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-fast-marquee": "^1.6.5",
"react-helmet": "^6.1.0",
"react-icons": "^5.3.0",
"react-qr-code": "^2.0.15",
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/js-cookie": "^3.0.6",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",

22
src/config/host.tsx Executable file
View File

@ -0,0 +1,22 @@
const MODE: "dev" | "staging" | "prod" = "dev";
const DEV_HOST = "http://next-api.ferinesia.local";
const STAGING_HOST = "";
const PROD_HOST = "https://prod-api-2.ferinesia.com";
const CURRENT_HOST: Record<"dev" | "staging" | "prod", string> = {
dev: DEV_HOST,
staging: STAGING_HOST,
prod: PROD_HOST
};
const endpoints = {
me: "/user/api/authentication/schedule_board/me",
qr_code_login: "/user/api/authentication/schedule_board/login_token",
login: "/user/api/authentication/schedule_board/login",
schedule_entries: "/schedules/api/schedule_board/get_schedule_entries"
}
const activeHost = CURRENT_HOST[MODE];
export {activeHost, endpoints};

View File

@ -0,0 +1,21 @@
import { activeHost } from "@/config/host";
import axios from "axios";
interface AxiosInstanceProp
{
bearerToken?: string
}
const createAxiosInstance = (prop: AxiosInstanceProp) => {
const axiosInstance = axios.create({
baseURL: activeHost,
headers: {
"Authorization": `Bearer ${prop.bearerToken}`
}
});
return axiosInstance;
}
export {createAxiosInstance};

View File

@ -4,4 +4,15 @@
html{
font-family: 'Inter';
}
}
/* HTML: <div class="loader"></div> */
.loader {
width: 50px;
aspect-ratio: 1;
border-radius: 50%;
border: 8px solid;
border-color: #000 #0000;
animation: l1 1s infinite;
}
@keyframes l1 {to{transform: rotate(.5turn)}}

View File

@ -5,6 +5,7 @@ import { createHashRouter, RouterProvider } from 'react-router-dom'
import {Index as HomepageIndex} from '@/pages/homepage'
import {Helmet} from "react-helmet"
import Login from '@/pages/auth/login'
import QRCodeLogin from './pages/auth/qr-code-scan'
const router = createHashRouter([
{
@ -14,7 +15,11 @@ const router = createHashRouter([
{
path: "/login",
element: <Login />
}
},
{
path: "/qr-login",
element: <QRCodeLogin />
},
])
createRoot(document.getElementById('root')!).render(

View File

@ -1,22 +1,77 @@
import ferinesiaLogo from "@/assets/logos/ferinesia-logo-thin.png"
import { useEffect, useState } from "react"
import { useLocation } from "react-router-dom"
import ferinesiaLogo from "@/assets/logos/ferinesia-logo-thin.png";
import { endpoints } from "@/config/host";
import { createAxiosInstance } from "@/functions/axios-instance";
import { useEffect, useState } from "react";
import { useLocation, useSearchParams } from "react-router-dom";
import Cookies from "js-cookie";
import { AxiosError } from "axios";
/**
* Copyright: Task Master (https://tailwindflex.com/@task_master)
* URL: https://tailwindflex.com/@task_master/login-form-with-social-login-buttons
*/
export interface InputValidationError{
type: string,
message: string
export interface InputValidationError {
type: string;
message: string;
}
export default function Login() {
const [isLoading, setIsLoading] = useState(false)
const [searchParams] = useSearchParams();
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [isLoading, setIsLoading] = useState(false);
const [loginToken, setLoginToken] = useState<string | null>(null);
const [terminalID, setTerminalID] = useState<string | null>(null);
const [isSuccessLogin, setIsSuccessLogin] = useState<boolean | null>(null);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [inputValidationMsg, setInputValidationMsg] = useState<InputValidationError[]>([]);
const doLogin = async () => {
let validationErrors = []
if(username.length <= 0){
validationErrors.push({type: "username", message: "Username tidak boleh kosong"})
}
if(password.length <= 0){
validationErrors.push({type: "password", message: "Password tidak boleh kosong"})
}
setInputValidationMsg(validationErrors)
if(validationErrors.length > 0)
return
setIsLoading(true)
try{
const {data} = await createAxiosInstance({}).post(endpoints.login, {
username,
password,
token: loginToken
});
setIsSuccessLogin(true);
return
}
catch(err){
if(err instanceof AxiosError){
if(err.response?.data?.meta?.is_error){
validationErrors.push({type: "username", message: err.response?.data?.meta?.message})
}
}
else
validationErrors.push({type: "username", message: "Terjadi kesalahan saat memproses data"})
}
finally{
setInputValidationMsg(validationErrors)
setIsLoading(false)
}
}
useEffect(() => {
setLoginToken(searchParams.get("state") ?? null);
setTerminalID(searchParams.get("terminal_id") ?? null);
});
return (
<div className="min-h-screen bg-gray-100 flex flex-col justify-center sm:py-12">
@ -29,67 +84,102 @@ export default function Login() {
</h1>
</div>
<div className="bg-white shadow w-full rounded-lg pt-4 pb-4">
<div className="px-5 py-2">
<label className="font-semibold text-sm text-gray-600 pb-1 block">
Username
</label>
<input
type="text"
className="border rounded-lg px-3 py-2 mt-1 mb-1 text-sm w-full"
onInput={e => setUsername(e.currentTarget.value)}
/>
{/* {
inputValidationMsg.find(x => x.type == "username") &&
<span className="text-red-500">{inputValidationMsg.find(x => x.type == "username")!.message}</span>
} */}
</div>
<div className="px-5 py-2">
<label className="font-semibold text-sm text-gray-600 pb-1 block">
Password
</label>
<input
type="password"
className="border rounded-lg px-3 py-2 mt-1 mb-1 text-sm w-full"
onInput={e => setPassword(e.currentTarget.value)}
/>
{/* {
inputValidationMsg.find(x => x.type == "password") &&
<span className="text-red-500">{inputValidationMsg.find(x => x.type == "password")!.message}</span>
} */}
</div>
<div className="px-5 py-2">
<button
// onClick={doLogin}
disabled={isLoading}
type="button"
className="transition duration-200 bg-blue-500 hover:bg-blue-600 focus:bg-blue-700 focus:shadow-sm focus:ring-4 focus:ring-blue-500 focus:ring-opacity-50 text-white w-full py-2.5 rounded-lg text-sm shadow-sm hover:shadow-md font-semibold text-center flex flex-row justify-center align-middle items-center disabled:bg-gray-300"
>
{
isLoading && <span className="animate-spin inline-block size-4 border-[3px] border-current border-t-transparent text-white rounded-full mr-2" role="status" aria-label="loading">
<span className="sr-only">Loading...</span>
</span>
}
<span className="inline-block mr-2">{isLoading ? "Mohon Tunggu" : "Login"}</span>
{
!isLoading && <svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="w-4 h-4 inline-block"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 8l4 4m0 0l-4 4m4-4H3"
{
(isSuccessLogin == null || !isSuccessLogin) && <>
<div className="px-5 py-2">
<label className="font-semibold text-sm text-gray-600 pb-1 block">
Terminal ID
</label>
<input
type="text"
className="border rounded-lg px-3 py-2 mt-1 mb-1 text-sm w-full"
value={terminalID ?? "N/A"}
disabled
/>
</svg>
}
</button>
</div>
{/* {
inputValidationMsg.find(x => x.type == "username") &&
<span className="text-red-500">{inputValidationMsg.find(x => x.type == "username")!.message}</span>
} */}
</div>
<div className="px-5 py-2">
<label className="font-semibold text-sm text-gray-600 pb-1 block">
Username
</label>
<input
type="text"
className="border rounded-lg px-3 py-2 mt-1 mb-1 text-sm w-full"
onInput={(e) => setUsername(e.currentTarget.value)}
/>
{
inputValidationMsg.find(x => x.type == "username") &&
<span className="text-red-500">{inputValidationMsg.find(x => x.type == "username")!.message}</span>
}
</div>
<div className="px-5 py-2">
<label className="font-semibold text-sm text-gray-600 pb-1 block">
Password
</label>
<input
type="password"
className="border rounded-lg px-3 py-2 mt-1 mb-1 text-sm w-full"
onInput={(e) => setPassword(e.currentTarget.value)}
/>
{
inputValidationMsg.find(x => x.type == "password") &&
<span className="text-red-500">{inputValidationMsg.find(x => x.type == "password")!.message}</span>
}
</div>
<div className="px-5 py-2">
{terminalID != null && loginToken != null && (
<button
onClick={doLogin}
disabled={isLoading}
type="button"
className="transition duration-200 bg-blue-500 hover:bg-blue-600 focus:bg-blue-700 focus:shadow-sm focus:ring-4 focus:ring-blue-500 focus:ring-opacity-50 text-white w-full py-2.5 rounded-lg text-sm shadow-sm hover:shadow-md font-semibold text-center flex flex-row justify-center align-middle items-center disabled:bg-gray-300"
>
{isLoading && (
<span
className="animate-spin inline-block size-4 border-[3px] border-current border-t-transparent text-white rounded-full mr-2"
role="status"
aria-label="loading"
>
<span className="sr-only">Loading...</span>
</span>
)}
<span className="inline-block mr-2">
{isLoading ? "Mohon Tunggu" : "Login"}
</span>
{!isLoading && (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
className="w-4 h-4 inline-block"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 8l4 4m0 0l-4 4m4-4H3"
/>
</svg>
)}
</button>
)}
{(terminalID == null || loginToken == null) && <span className="text-[#ff0015]">Terminal ID dan Token Akses tidak valid.</span>}
</div>
</>
}
{
isSuccessLogin && <div className="px-5 py-2">
<h4 className="text-xl text-green-600 text-center">Sukses</h4>
<h5 className="text-lg text-center mt-4">Berhasil melakukan login untuk penampil jadwal. Anda dapat menutup halaman ini.</h5>
</div>
}
</div>
</div>
</div>
)
);
}

82
src/pages/auth/qr-code-scan.tsx Executable file
View File

@ -0,0 +1,82 @@
import ferinesiaLogo from "@/assets/logos/ferinesia-logo-thin.png";
import { endpoints } from "@/config/host";
import { createAxiosInstance } from "@/functions/axios-instance";
import { useEffect, useState } from "react";
import QRCode from "react-qr-code";
import Cookies from "js-cookie";
export default function QRCodeLogin() {
const [isLoadingQR, setIsLoadingQR] = useState(true);
const [terminalID, setTerminalID] = useState<string | null>(null);
const [qrCodeLogin, setQRCodeLogin] = useState<string | null>(null);
const getQRCodeLogin = async () => {
try{
const {data} = await createAxiosInstance({}).get(endpoints.qr_code_login);
const loginToken = data.data.token;
setQRCodeLogin(`https://schedule-board.ferinesia.com/#/login?state=${loginToken}&terminal_id=${data.data.terminal_id}`);
setTerminalID(data.data.terminal_id);
setIsLoadingQR(false);
setInterval(async () => {
try{
const {data} = await createAxiosInstance({bearerToken: loginToken}).get(endpoints.me);
localStorage.setItem("HARBOR_NAME", data.data.harbor.name);
localStorage.setItem("HARBOR_ADDRESS", data.data.harbor.address);
Cookies.set("ferinesia_schedule_board_token", loginToken);
location.href = "/";
}
catch(err){
}
}, 5000);
setInterval(() => {
location.reload();
}, 5 * 60 * 1000);
}
catch(err){
alert("Tidak dapat mengambil QRCode, silahkan coba kembali.");
}
}
useEffect(() => {
getQRCodeLogin();
}, []);
return (
<>
<div className="min-h-screen bg-gray-100 flex flex-col justify-center sm:py-12">
<div className="ml-8 mr-8">
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6 border h-[600px] flex items-center justify-center flex-col">
{isLoadingQR && (
<div>
<div className="loader"></div>
</div>
)}
{!isLoadingQR && qrCodeLogin != null && (
<QRCode size={512} value={qrCodeLogin} />
)}
{
!isLoadingQR && <span className="mt-4 text-xl">Terminal ID: {terminalID ?? "N/A"}</span>
}
</div>
<div className="col-span-6 p-4 border">
<div className="flex items-center">
<img src={ferinesiaLogo} className="max-h-6" />
<h3 className="ml-2 text-2xl font-bold text-[#117AD1]">
FERI<span className="text-[#FFCE46]">NESIA</span>
</h3>
</div>
<h4 className="text-xl mt-8">
Silahkan melakukan login dengan mengunjungi URL yang tesedia
pada QRCode disebelah kiri.
</h4>
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -21,6 +21,11 @@ import { useEffect, useState } from "react";
import importData from "@/data/17_10_2024_TTE_BASTIONG.json";
import importCompanyInfo from "@/data/LogoLocation.json";
import { ScheduleStatus } from "@/constants/ScheduleStatus";
import Cookies from "js-cookie";
import { createAxiosInstance } from "@/functions/axios-instance";
import { AxiosError } from "axios";
import { endpoints } from "@/config/host";
import { CronJob } from 'cron';
dayjs.extend(utc);
dayjs.extend(timezone);
@ -35,11 +40,21 @@ interface Schedule {
ship_name: string;
}
interface AccountInfo {
harbor: {
name: string;
address: string;
code: string;
};
}
export function Index() {
const [timezone, setTimezone] = useState("N/A");
const [clock, setClock] = useState("00:00:00");
const [date, setDate] = useState("");
const [currentScheduleIndex, setCurrentScheduleIndex] = useState(0);
const [schedules, setSchedules] = useState<Schedule[]>([]);
const [marqueeTexts, setMarqueeTexts] = useState<string[]>([]);
const [grouppedSchedules, setGrouppedSchedules] = useState<
{
key: string;
@ -51,15 +66,7 @@ export function Index() {
importCompanyInfo
);
const [data, setData] = useState<{
harbor: {
name: string;
address: string;
};
embed_link: string[];
marquee_text: string[];
schedules: Schedule[];
}>(importData);
const [data, setData] = useState<AccountInfo | null>(null);
const chunkArray = (array: Schedule[], chunkSize: number) => {
const result = [];
@ -75,6 +82,7 @@ export function Index() {
dayjs(x.departure_datetime).unix() - dayjs(y.departure_datetime).unix()
);
const scheduleChunks = chunkArray(sortDates, chunkSize);
const tempResult = [];
const todayDatetime = dayjs();
@ -178,7 +186,7 @@ export function Index() {
setDate(_date);
}, 1000);
setGrouppedSchedules(groupDepartureDatetimes(data.schedules, 4));
setGrouppedSchedules(groupDepartureDatetimes(schedules, 4));
}, []);
useEffect(() => {
@ -191,6 +199,64 @@ export function Index() {
}
}, [grouppedSchedules]);
const checkAuth = async () => {
const authToken = Cookies.get("ferinesia_schedule_board_token");
try{
const {data} = await createAxiosInstance({bearerToken: authToken}).get(endpoints.me);
setData({...data, harbor: {name: data.data.harbor.name, address: data.data.harbor.address, code: data.data.harbor.code}});
setMarqueeTexts(data.data.marquee_texts);
}
catch(err){
if(err instanceof AxiosError){
if(err.response?.data?.meta?.code == "E_UNAUTHENTICATED"){
location.href = "/#/qr-login"
}
}
}
}
const fetchSchedules = async () => {
const authToken = Cookies.get("ferinesia_schedule_board_token");
const firstDatetime = dayjs().format("YYYY-MM-DD HH:mm:ss");
const secondDatetime = dayjs().add(7, 'day').format("YYYY-MM-DD HH:mm:ss");
try{
const {data} = await createAxiosInstance({bearerToken: authToken}).post(endpoints.schedule_entries, {
start_departure_date: firstDatetime,
end_departure_date: secondDatetime
});
let tempSchedules: Schedule[] = [];
for(const schedule of data.data.schedules){
tempSchedules.push({
departure_datetime: schedule.departure_datetime,
dock_name: "Dek 1",
destination: schedule.destination,
status: ScheduleStatus.AVAILABLE,
company_code: schedule.company_code,
ship_name: schedule.ship_name
});
}
setGrouppedSchedules(groupDepartureDatetimes(tempSchedules, 4));
}
catch(err){
}
}
useEffect(() => {
checkAuth();
// equivalent job using the "from" static method, providing parameters as an object
CronJob.from({
cronTime: '*/10 * * * *',
onTick: fetchSchedules,
start: true,
timeZone: 'Asia/Jakarta',
runOnInit: true
});
}, []);
return (
<>
<div className="h-screen pl-4 pr-4 pt-4">
@ -205,7 +271,7 @@ export function Index() {
</div>
<div className="col-span-4">
<h3 className="text-5xl font-bold text-center text-[#2F95FA]">
PELABUHAN BASTIONG
PELABUHAN {data?.harbor.name ?? "N/A"} [{data?.harbor.code ?? "N/A"}]
</h3>
</div>
<div className="col-span-4">
@ -233,7 +299,7 @@ export function Index() {
className="text-center text-4xl uppercase"
style={{ width: "10%", letterSpacing: "3px" }}
>
JAM
WAKTU BERANGKAT
</th>
<th
scope="col"
@ -370,7 +436,7 @@ export function Index() {
</div>
<div className="whitespace-nowrap flex items-center overflow-x-hidden ml-1">
<Marquee speed={100}>
{data.marquee_text.map((x) => (
{marqueeTexts.map((x) => (
<h3 className="text-5xl ml-4 font-bold">{x}</h3>
))}
</Marquee>

191
yarn.lock
View File

@ -562,6 +562,16 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/js-cookie@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95"
integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==
"@types/luxon@~3.4.0":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7"
integrity sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==
"@types/prop-types@*":
version "15.7.12"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
@ -746,6 +756,11 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
autoprefixer@^10.4.20:
version "10.4.20"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b"
@ -758,6 +773,15 @@ autoprefixer@^10.4.20:
picocolors "^1.0.1"
postcss-value-parser "^4.2.0"
axios@^1.8.4:
version "1.8.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447"
integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@ -800,6 +824,14 @@ browserslist@^4.23.1, browserslist@^4.23.3:
node-releases "^2.0.18"
update-browserslist-db "^1.1.0"
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@ -871,6 +903,13 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
commander@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
@ -886,6 +925,14 @@ convert-source-map@^2.0.0:
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
cron@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/cron/-/cron-4.1.3.tgz#42661cebdf15483f90f63e799c9dbd33a298265c"
integrity sha512-HETm5kgivcdfboOmBIzq0cfC9c5bRilWZ1p7PWwnOMmbWviwIU6mPgZbeqbj5i0AzNan6P68WDTDEDezhKjOng==
dependencies:
"@types/luxon" "~3.4.0"
luxon "~3.6.0"
cross-spawn@^7.0.0, cross-spawn@^7.0.2:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -922,6 +969,11 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
@ -932,6 +984,15 @@ dlv@^1.1.3:
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
es-errors "^1.3.0"
gopd "^1.2.0"
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@ -952,6 +1013,33 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
es-define-property@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
dependencies:
es-errors "^1.3.0"
get-intrinsic "^1.2.6"
has-tostringtag "^1.0.2"
hasown "^2.0.2"
esbuild@^0.21.3:
version "0.21.5"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
@ -1165,6 +1253,11 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
foreground-child@^3.1.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
@ -1173,6 +1266,16 @@ foreground-child@^3.1.0:
cross-spawn "^7.0.0"
signal-exit "^4.0.1"
form-data@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c"
integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
mime-types "^2.1.12"
fraction.js@^4.3.7:
version "4.3.7"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
@ -1193,6 +1296,30 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-intrinsic@^1.2.6:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
es-define-property "^1.0.1"
es-errors "^1.3.0"
es-object-atoms "^1.1.1"
function-bind "^1.1.2"
get-proto "^1.0.1"
gopd "^1.2.0"
has-symbols "^1.1.0"
hasown "^2.0.2"
math-intrinsics "^1.1.0"
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@ -1234,6 +1361,11 @@ globals@^15.9.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-15.9.0.tgz#e9de01771091ffbc37db5714dab484f9f69ff399"
integrity sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==
gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
graphemer@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
@ -1249,6 +1381,18 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-symbols@^1.0.3, has-symbols@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
dependencies:
has-symbols "^1.0.3"
hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
@ -1334,6 +1478,11 @@ jiti@^1.21.0:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
js-cookie@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -1432,6 +1581,16 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
luxon@~3.6.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.6.1.tgz#d283ffc4c0076cb0db7885ec6da1c49ba97e47b0"
integrity sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@ -1445,6 +1604,18 @@ micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.3"
picomatch "^2.3.1"
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@ -1659,7 +1830,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prop-types@^15.7.2:
prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -1668,11 +1839,21 @@ prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.13.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
qr.js@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
integrity sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@ -1716,6 +1897,14 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-qr-code@^2.0.15:
version "2.0.15"
resolved "https://registry.yarnpkg.com/react-qr-code/-/react-qr-code-2.0.15.tgz#fbfc12952c504bcd64275647e9d1ea63251742ce"
integrity sha512-MkZcjEXqVKqXEIMVE0mbcGgDpkfSdd8zhuzXEl9QzYeNcw8Hq2oVIzDLWuZN2PQBwM5PWjc2S31K8Q1UbcFMfw==
dependencies:
prop-types "^15.8.1"
qr.js "0.0.0"
react-refresh@^0.14.2:
version "0.14.2"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"