Save WIP
This commit is contained in:
parent
49cb191254
commit
03f583b021
15 changed files with 441 additions and 11 deletions
|
@ -28,6 +28,8 @@ tags:
|
|||
description: Manage integrations
|
||||
- name: User
|
||||
description: Manage the user's profile and settings
|
||||
- name: Team
|
||||
description: Group users into teams
|
||||
paths:
|
||||
/api/auth/signin:
|
||||
get:
|
||||
|
@ -144,4 +146,9 @@ paths:
|
|||
description: Updates a user's profile.
|
||||
summary: Updates a user's profile
|
||||
tags:
|
||||
- User
|
||||
- User
|
||||
/api/availability/schedule:
|
||||
path:
|
||||
description: "Updates a schedule"
|
||||
tags:
|
||||
- Availability
|
6
components/Schedule.model.tsx
Normal file
6
components/Schedule.model.tsx
Normal file
|
@ -0,0 +1,6 @@
|
|||
import {Dayjs} from "dayjs";
|
||||
|
||||
interface Schedule {
|
||||
startDate: Dayjs;
|
||||
endDate: Dayjs;
|
||||
}
|
7
components/modal/DateOverrideModal.tsx
Normal file
7
components/modal/DateOverrideModal.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
|
||||
/*export default function DateOverrideModal(props) {
|
||||
return (
|
||||
|
||||
);
|
||||
}*/
|
100
components/modal/SetTimesModal.tsx
Normal file
100
components/modal/SetTimesModal.tsx
Normal file
|
@ -0,0 +1,100 @@
|
|||
import {ClockIcon} from "@heroicons/react/outline";
|
||||
import {useRef} from "react";
|
||||
|
||||
export default function SetTimesModal(props) {
|
||||
|
||||
const isNew = props.isNew || false;
|
||||
const {startDate, endDate} = props.schedule;
|
||||
|
||||
const startHoursRef = useRef<HTMLInputElement>();
|
||||
const startMinsRef = useRef<HTMLInputElement>();
|
||||
const endHoursRef = useRef<HTMLInputElement>();
|
||||
const endMinsRef = useRef<HTMLInputElement>();
|
||||
|
||||
function updateStartEndTimesHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const enteredStartHours = parseInt(startHoursRef.current.value);
|
||||
const enteredStartMins = parseInt(startMinsRef.current.value);
|
||||
const enteredEndHours = parseInt(endHoursRef.current.value);
|
||||
const enteredEndMins = parseInt(endMinsRef.current.value);
|
||||
|
||||
props.onChange({
|
||||
startDate: startDate.minute(enteredStartMins).hour(enteredStartHours),
|
||||
endDate: endDate.minute(enteredEndMins).hour(enteredEndHours),
|
||||
});
|
||||
|
||||
props.onExit(0);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<div
|
||||
className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div className="sm:flex sm:items-start mb-4">
|
||||
<div
|
||||
className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ClockIcon className="h-6 w-6 text-blue-600"/>
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
|
||||
Change when you are available for bookings
|
||||
</h3>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">
|
||||
Set your work schedule
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={updateStartEndTimesHandler} noValidate>
|
||||
<div className="flex mb-4">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">Start time</label>
|
||||
<div>
|
||||
<label htmlFor="startHours" className="sr-only">Hours</label>
|
||||
<input ref={startHoursRef} type="number" min="0" max="23" maxLength="2" name="hours" id="startHours"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="9" defaultValue={startDate.format('H')} />
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="startMinutes" className="sr-only">Minutes</label>
|
||||
<input ref={startMinsRef} type="number" min="0" max="59" step="15" maxLength="2" name="minutes" id="startMinutes"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="30" defaultValue={startDate.format('m')} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<label className="w-1/4 pt-2 block text-sm font-medium text-gray-700">End time</label>
|
||||
<div>
|
||||
<label htmlFor="endHours" className="sr-only">Hours</label>
|
||||
<input ref={endHoursRef} type="number" min="0" max="23" maxLength="2" name="hours" id="endHours"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="17" defaultValue={endDate.format('H')} />
|
||||
</div>
|
||||
<span className="mx-2 pt-1">:</span>
|
||||
<div>
|
||||
<label htmlFor="endMinutes" className="sr-only">Minutes</label>
|
||||
<input ref={endMinsRef} type="number" min="0" max="59" maxLength="2" step="15" name="minutes" id="endMinutes"
|
||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
placeholder="30" defaultValue={endDate.format('m')} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Save
|
||||
</button>
|
||||
<button onClick={props.onExit} type="button" className="btn btn-white mr-2">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
101
components/ui/Scheduler.tsx
Normal file
101
components/ui/Scheduler.tsx
Normal file
|
@ -0,0 +1,101 @@
|
|||
import React, {useEffect, useState} from "react";
|
||||
import TimezoneSelect from "react-timezone-select";
|
||||
import {PencilAltIcon, TrashIcon} from "@heroicons/react/outline";
|
||||
import {WeekdaySelect} from "./WeekdaySelect";
|
||||
import SetTimesModal from "../modal/SetTimesModal";
|
||||
import Schedule from '../../lib/schedule.model';
|
||||
import dayjs, {Dayjs} from "dayjs";
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export const Scheduler = (props) => {
|
||||
|
||||
const [ showSetTimesModal, setShowSetTimesModal ]: boolean = useState(false);
|
||||
const [ schedules, setSchedules ]: Schedule[] = useState(
|
||||
props.schedules.map( (schedule, idx) => ({
|
||||
startDate: dayjs(schedule.startDate),
|
||||
endDate: dayjs(schedule.startDate).startOf('day').add(schedule.length, 'minutes'),
|
||||
key: idx
|
||||
}) )
|
||||
);
|
||||
|
||||
const [ timeZone, setTimeZone ] = useState(props.timeZone);
|
||||
const [ selectedSchedule, setSelectedSchedule ]: Schedule | null = useState(null);
|
||||
|
||||
const addNewSchedule = () => {
|
||||
setSelectedSchedule({
|
||||
startDate: dayjs().startOf('day').add(0, 'minutes'),
|
||||
endDate: dayjs().startOf('day').add(1439, 'minutes'),
|
||||
});
|
||||
setShowSetTimesModal(true);
|
||||
}
|
||||
|
||||
const upsertSchedule = (changed: Schedule) => {
|
||||
if (changed.key) {
|
||||
schedules.splice(
|
||||
schedules.findIndex( (schedule) => changed.key === schedule.key ), 1, changed
|
||||
)
|
||||
setSchedules([].concat(schedules)); // update
|
||||
}
|
||||
else {
|
||||
console.log(changed);
|
||||
setSchedules(schedules.concat([changed])); // insert
|
||||
}
|
||||
}
|
||||
|
||||
const removeSchedule = (toRemove: Schedule) => {
|
||||
schedules.splice(schedules.findIndex( (schedule) => schedule.key === toRemove.key ), 1);
|
||||
setSchedules([].concat(schedules));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded border flex">
|
||||
<div className="w-3/5">
|
||||
<div className="w-3/4 p-2">
|
||||
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
|
||||
Timezone
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<TimezoneSelect id="timeZone" value={timeZone} onChange={setTimeZone} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
{schedules.length > 0 && schedules.map( (schedule) =>
|
||||
<li key={schedule.key} className="py-2 flex justify-between border-t">
|
||||
<div className="inline-flex ml-2">
|
||||
<WeekdaySelect />
|
||||
<button className="ml-2 text-sm px-2" type="button" onClick={() => { setSelectedSchedule(schedule); setShowSetTimesModal(true) }}>
|
||||
{schedule.startDate.format(schedule.startDate.minute() === 0 ? 'ha' : 'h:mma')} until {schedule.endDate.format(schedule.endDate.minute() === 0 ? 'ha' : 'h:mma')}
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" onClick={() => removeSchedule(schedule)}
|
||||
className="btn-sm bg-transparent px-2 py-1 ml-1">
|
||||
<TrashIcon className="h-6 w-6 inline text-gray-400 -mt-1" />
|
||||
</button>
|
||||
</li>)}
|
||||
</ul>
|
||||
<hr />
|
||||
<button type="button" onClick={addNewSchedule} className="btn-white btn-sm m-2">Add another</button>
|
||||
</div>
|
||||
<div className="border-l p-2 w-2/5 text-sm bg-gray-50">
|
||||
{/*<p className="font-bold mb-2">Add date overrides</p>
|
||||
<p className="mb-2">
|
||||
Add dates when your availability changes from your weekly hours
|
||||
</p>
|
||||
<button className="btn-sm btn-white">Add a date override</button>*/}
|
||||
</div>
|
||||
</div>
|
||||
{showSetTimesModal &&
|
||||
<SetTimesModal schedule={selectedSchedule}
|
||||
onChange={upsertSchedule}
|
||||
onExit={() => setShowSetTimesModal(false)} />
|
||||
}
|
||||
{/*{showDateOverrideModal &&
|
||||
<DateOverrideModal />
|
||||
}*/}
|
||||
</div>
|
||||
);
|
||||
}
|
39
components/ui/WeekdaySelect.tsx
Normal file
39
components/ui/WeekdaySelect.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import React, {useState} from "react";
|
||||
|
||||
export const WeekdaySelect = (props) => {
|
||||
|
||||
const [ activeDays, setActiveDays ] = useState([false, true, true, true, true, true, false]);
|
||||
const days = [ 'S', 'M', 'T', 'W', 'T', 'F', 'S' ];
|
||||
|
||||
const toggleDay = (e, idx: number) => {
|
||||
e.preventDefault();
|
||||
activeDays[idx] = !activeDays[idx];
|
||||
console.log(activeDays);
|
||||
setActiveDays([].concat(activeDays));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="weekdaySelect">
|
||||
<div className="inline-flex">
|
||||
{days.map( (day, idx) => activeDays[idx] ?
|
||||
<button key={idx} onClick={(e) => toggleDay(e, idx)}
|
||||
style={ {"marginLeft": "-2px"} }
|
||||
className={`
|
||||
active focus:outline-none border-2 border-blue-500 px-2 py-1 rounded
|
||||
${activeDays[idx+1] ? 'rounded-r-none': ''}
|
||||
${activeDays[idx-1] ? 'rounded-l-none': ''}
|
||||
${idx === 0 ? 'rounded-l' : ''}
|
||||
${idx === days.length-1 ? 'rounded-r' : ''}
|
||||
`}>
|
||||
{day}
|
||||
</button>
|
||||
:
|
||||
<button key={idx} onClick={(e) => toggleDay(e, idx)}
|
||||
style={ {"marginTop": "1px", "marginBottom": "1px"} }
|
||||
className={`border focus:outline-none px-2 py-1 rounded-none ${idx === 0 ? 'rounded-l' : 'border-l-0'} ${idx === days.length-1 ? 'rounded-r' : ''}`}>
|
||||
{day}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
7
lib/schedule.model.tsx
Normal file
7
lib/schedule.model.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {Dayjs} from "dayjs";
|
||||
|
||||
export default interface Schedule {
|
||||
key: number;
|
||||
startDate: Dayjs;
|
||||
endDate: Dayjs;
|
||||
}
|
45
pages/api/availability/schedule.ts
Normal file
45
pages/api/availability/schedule.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { getSession } from 'next-auth/client';
|
||||
import prisma from '../../../lib/prisma';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getSession({req: req});
|
||||
|
||||
if (!session) {
|
||||
res.status(401).json({message: "Not authenticated"});
|
||||
return;
|
||||
}
|
||||
|
||||
PUT /api/availability/schedule/{id}/timezone
|
||||
{
|
||||
"timeZone": "Europe/London"
|
||||
}
|
||||
|
||||
PATCH /api/availability/schedule {
|
||||
"schedules": [
|
||||
{
|
||||
|
||||
}
|
||||
],
|
||||
"overrides": {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method == "PATCH") {
|
||||
const startMins = req.body.start;
|
||||
const endMins = req.body.end;
|
||||
|
||||
const updateDay = await prisma.schedule.update({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
data: {
|
||||
startTime: startMins,
|
||||
endTime: endMins
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json({message: 'Start and end times updated successfully'});
|
||||
}
|
||||
}
|
0
pages/api/availability/schedule/[type].ts
Normal file
0
pages/api/availability/schedule/[type].ts
Normal file
18
pages/api/availability/schedule/[type]/timezone.ts
Normal file
18
pages/api/availability/schedule/[type]/timezone.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import prisma from '../../../lib/prisma';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { user } = req.query
|
||||
|
||||
const schedules = await prisma.schedule.find({
|
||||
where: {
|
||||
eventTypeId: req.query.type,
|
||||
},
|
||||
select: {
|
||||
credentials: true,
|
||||
timeZone: true
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(202).send(null);
|
||||
}
|
30
pages/api/availability/week.ts
Normal file
30
pages/api/availability/week.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { getSession } from 'next-auth/client';
|
||||
import prisma from '../../../lib/prisma';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getSession({req: req});
|
||||
|
||||
if (!session) {
|
||||
res.status(401).json({message: "Not authenticated"});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method == "PATCH") {
|
||||
|
||||
const startMins = req.body.start;
|
||||
const endMins = req.body.end;
|
||||
|
||||
const updateWeek = await prisma.schedule.update({
|
||||
where: {
|
||||
id: session.user.id,
|
||||
},
|
||||
data: {
|
||||
startTime: startMins,
|
||||
endTime: endMins
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).json({message: 'Start and end times updated successfully'});
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@ import prisma from '../../../lib/prisma';
|
|||
import { LocationType } from '../../../lib/location';
|
||||
import Shell from '../../../components/Shell';
|
||||
import { useSession, getSession } from 'next-auth/client';
|
||||
import {Scheduler} from "../../../components/ui/Scheduler";
|
||||
|
||||
import {
|
||||
LocationMarkerIcon,
|
||||
PlusCircleIcon,
|
||||
|
@ -14,6 +16,12 @@ import {
|
|||
PhoneIcon,
|
||||
} from '@heroicons/react/outline';
|
||||
|
||||
import dayjs, {Dayjs} from "dayjs";
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
dayjs.extend(utc);
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export default function EventType(props) {
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -32,6 +40,8 @@ export default function EventType(props) {
|
|||
return <p className="text-gray-400">Loading...</p>;
|
||||
}
|
||||
|
||||
console.log(props);
|
||||
|
||||
async function updateEventTypeHandler(event) {
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -142,7 +152,7 @@ export default function EventType(props) {
|
|||
<Shell heading={'Event Type - ' + props.eventType.title}>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="col-span-3 sm:col-span-2">
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg mb-4">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<form onSubmit={updateEventTypeHandler}>
|
||||
<div className="mb-4">
|
||||
|
@ -232,7 +242,7 @@ export default function EventType(props) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="my-8">
|
||||
<div className="my-6 mb-4">
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex items-center h-5">
|
||||
<input
|
||||
|
@ -252,9 +262,16 @@ export default function EventType(props) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" className="btn btn-primary">Update</button>
|
||||
<Link href="/availability"><a className="ml-2 btn btn-white">Cancel</a></Link>
|
||||
</form>
|
||||
<hr className="my-4"/>
|
||||
<div>
|
||||
<h3 className="mb-2">How do you want to offer your availability for this event type?</h3>
|
||||
<Scheduler timeZone={props.user.timeZone} schedules={props.schedules} />
|
||||
<div className="py-4 flex justify-end">
|
||||
<Link href="/availability"><a className="mr-2 btn btn-white">Cancel</a></Link>
|
||||
<button type="submit" className="btn btn-primary">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -332,7 +349,10 @@ export async function getServerSideProps(context) {
|
|||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
username: true
|
||||
username: true,
|
||||
timeZone: true,
|
||||
startTime: true,
|
||||
endTime: true,
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -351,10 +371,21 @@ export async function getServerSideProps(context) {
|
|||
}
|
||||
});
|
||||
|
||||
const utcOffset = dayjs().tz(user.timeZone).utcOffset();
|
||||
|
||||
const schedules = [
|
||||
{
|
||||
key: 0,
|
||||
startDate: dayjs.utc().startOf('day').add(user.startTime - utcOffset, 'minutes').format(),
|
||||
length: user.endTime,
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
props: {
|
||||
user,
|
||||
eventType
|
||||
},
|
||||
props: {
|
||||
user,
|
||||
eventType,
|
||||
schedules
|
||||
},
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ model EventType {
|
|||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
bookings Booking[]
|
||||
availability Interval[]
|
||||
}
|
||||
|
||||
model Credential {
|
||||
|
@ -49,6 +50,8 @@ model User {
|
|||
credentials Credential[]
|
||||
teams Membership[]
|
||||
bookings Booking[]
|
||||
availability Interval[]
|
||||
|
||||
@@map(name: "users")
|
||||
}
|
||||
|
||||
|
@ -119,4 +122,16 @@ model Booking {
|
|||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime?
|
||||
}
|
||||
}
|
||||
|
||||
model Interval {
|
||||
id Int @default(autoincrement()) @id
|
||||
label String?
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
eventType EventType? @relation(fields: [eventTypeId], references: [id])
|
||||
eventTypeId Int?
|
||||
startTime DateTime
|
||||
length Int
|
||||
isOverride Boolean @default(false)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
|
||||
table tbody tr:nth-child(odd) {
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
|
||||
.highlight-odd > *:nth-child(odd) {
|
||||
@apply bg-gray-50;
|
||||
}
|
|
@ -24,4 +24,24 @@ body {
|
|||
|
||||
#timeZone input:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.weekdaySelect {
|
||||
font-family: "Courier New", sans-serif;
|
||||
}
|
||||
|
||||
.weekdaySelect button.active:first-child {
|
||||
margin-left: -1px !important;
|
||||
}
|
||||
|
||||
.weekdaySelect button:not(.active) {
|
||||
padding-left: calc(0.5rem + 0px);
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.weekdaySelect button.active + button.active {
|
||||
border-color: rgba(3, 169, 244, var(--tw-border-opacity))
|
||||
rgba(3, 169, 244, var(--tw-border-opacity))
|
||||
rgba(3, 169, 244, var(--tw-border-opacity))
|
||||
white;
|
||||
}
|
Loading…
Reference in a new issue