email
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
hardik 2025-10-08 09:40:05 +00:00
parent f698c10cd3
commit 11ce19c59d
15 changed files with 6257 additions and 200 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
VITE_RESEND_API_KEY=re_hCWjNNNZ_BNQ5aSKTT6DDJDvwN5v3QCSv
ZOHO_EMAIL=bookme@sangwaritaxi.com
ZOHO_PASSWORD=7qN53AdTPT1N

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ node_modules/
# Ignore build output
build/
.vercel

2899
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +1,85 @@
{
"hash": "c02a7a04",
"hash": "6da8a140",
"configHash": "d36eeadb",
"lockfileHash": "71693085",
"browserHash": "c81fa583",
"lockfileHash": "9b1da6e3",
"browserHash": "c29e10de",
"optimized": {
"react/jsx-dev-runtime": {
"src": "../../react/jsx-dev-runtime.js",
"file": "react_jsx-dev-runtime.js",
"fileHash": "96507cc9",
"fileHash": "a234d627",
"needsInterop": true
},
"@radix-ui/react-label": {
"src": "../../@radix-ui/react-label/dist/index.mjs",
"file": "@radix-ui_react-label.js",
"fileHash": "cd4d4396",
"fileHash": "ac69cd48",
"needsInterop": false
},
"@radix-ui/react-select": {
"src": "../../@radix-ui/react-select/dist/index.mjs",
"file": "@radix-ui_react-select.js",
"fileHash": "e3bccc02",
"fileHash": "d1e90438",
"needsInterop": false
},
"@radix-ui/react-separator": {
"src": "../../@radix-ui/react-separator/dist/index.mjs",
"file": "@radix-ui_react-separator.js",
"fileHash": "cccdf600",
"fileHash": "211b7b5a",
"needsInterop": false
},
"@radix-ui/react-slot": {
"src": "../../@radix-ui/react-slot/dist/index.mjs",
"file": "@radix-ui_react-slot.js",
"fileHash": "5a67462b",
"fileHash": "2170f3d2",
"needsInterop": false
},
"class-variance-authority": {
"src": "../../class-variance-authority/dist/index.mjs",
"file": "class-variance-authority.js",
"fileHash": "8de36a40",
"fileHash": "ec7350c2",
"needsInterop": false
},
"clsx": {
"src": "../../clsx/dist/clsx.mjs",
"file": "clsx.js",
"fileHash": "2471cc19",
"fileHash": "abf6854e",
"needsInterop": false
},
"lucide-react": {
"src": "../../lucide-react/dist/esm/lucide-react.js",
"file": "lucide-react.js",
"fileHash": "c54e7541",
"fileHash": "40a86d41",
"needsInterop": false
},
"motion/react": {
"src": "../../motion/dist/es/react.mjs",
"file": "motion_react.js",
"fileHash": "6aab3a22",
"fileHash": "702dc114",
"needsInterop": false
},
"react": {
"src": "../../react/index.js",
"file": "react.js",
"fileHash": "33971b8d",
"fileHash": "1a08a00e",
"needsInterop": true
},
"react-dom/client": {
"src": "../../react-dom/client.js",
"file": "react-dom_client.js",
"fileHash": "72f46ec7",
"fileHash": "345d9e3c",
"needsInterop": true
},
"react/jsx-runtime": {
"src": "../../react/jsx-runtime.js",
"file": "react_jsx-runtime.js",
"fileHash": "e468f34f",
"fileHash": "bfc11499",
"needsInterop": true
},
"tailwind-merge": {
"src": "../../tailwind-merge/dist/bundle-mjs.mjs",
"file": "tailwind-merge.js",
"fileHash": "165b6bce",
"fileHash": "497bb95d",
"needsInterop": false
}
},

2
node_modules/@types/node/README.md generated vendored
View File

@ -8,7 +8,7 @@ This package contains type definitions for node (https://nodejs.org/).
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node/v20.
### Additional Details
* Last updated: Thu, 18 Sep 2025 00:04:03 GMT
* Last updated: Tue, 30 Sep 2025 23:32:16 GMT
* Dependencies: [undici-types](https://npmjs.com/package/undici-types)
# Credits

18
node_modules/@types/node/assert.d.ts generated vendored
View File

@ -11,6 +11,24 @@ declare module "assert" {
*/
function assert(value: unknown, message?: string | Error): asserts value;
namespace assert {
type AssertMethodNames =
| "deepEqual"
| "deepStrictEqual"
| "doesNotMatch"
| "doesNotReject"
| "doesNotThrow"
| "equal"
| "fail"
| "ifError"
| "match"
| "notDeepEqual"
| "notDeepStrictEqual"
| "notEqual"
| "notStrictEqual"
| "ok"
| "rejects"
| "strictEqual"
| "throws";
/**
* Indicates the failure of an assertion. All errors thrown by the `node:assert` module will be instances of the `AssertionError` class.
*/

112
node_modules/@types/node/events.d.ts generated vendored
View File

@ -36,39 +36,6 @@
*/
declare module "events" {
import { AsyncResource, AsyncResourceOptions } from "node:async_hooks";
// NOTE: This class is in the docs but is **not actually exported** by Node.
// If https://github.com/nodejs/node/issues/39903 gets resolved and Node
// actually starts exporting the class, uncomment below.
// import { EventListener, EventListenerObject } from '__dom-events';
// /** The NodeEventTarget is a Node.js-specific extension to EventTarget that emulates a subset of the EventEmitter API. */
// interface NodeEventTarget extends EventTarget {
// /**
// * Node.js-specific extension to the `EventTarget` class that emulates the equivalent `EventEmitter` API.
// * The only difference between `addListener()` and `addEventListener()` is that addListener() will return a reference to the EventTarget.
// */
// addListener(type: string, listener: EventListener | EventListenerObject, options?: { once: boolean }): this;
// /** Node.js-specific extension to the `EventTarget` class that returns an array of event `type` names for which event listeners are registered. */
// eventNames(): string[];
// /** Node.js-specific extension to the `EventTarget` class that returns the number of event listeners registered for the `type`. */
// listenerCount(type: string): number;
// /** Node.js-specific alias for `eventTarget.removeListener()`. */
// off(type: string, listener: EventListener | EventListenerObject): this;
// /** Node.js-specific alias for `eventTarget.addListener()`. */
// on(type: string, listener: EventListener | EventListenerObject, options?: { once: boolean }): this;
// /** Node.js-specific extension to the `EventTarget` class that adds a `once` listener for the given event `type`. This is equivalent to calling `on` with the `once` option set to `true`. */
// once(type: string, listener: EventListener | EventListenerObject): this;
// /**
// * Node.js-specific extension to the `EventTarget` class.
// * If `type` is specified, removes all registered listeners for `type`,
// * otherwise removes all registered listeners.
// */
// removeAllListeners(type: string): this;
// /**
// * Node.js-specific extension to the `EventTarget` class that removes the listener for the given `type`.
// * The only difference between `removeListener()` and `removeEventListener()` is that `removeListener()` will return a reference to the `EventTarget`.
// */
// removeListener(type: string, listener: EventListener | EventListenerObject): this;
// }
interface EventEmitterOptions {
/**
* Enables automatic capturing of promise rejection.
@ -585,6 +552,85 @@ declare module "events" {
*/
readonly asyncResource: EventEmitterReferencingAsyncResource;
}
/**
* The `NodeEventTarget` is a Node.js-specific extension to `EventTarget`
* that emulates a subset of the `EventEmitter` API.
* @since v14.5.0
*/
export interface NodeEventTarget extends EventTarget {
/**
* Node.js-specific extension to the `EventTarget` class that emulates the
* equivalent `EventEmitter` API. The only difference between `addListener()` and
* `addEventListener()` is that `addListener()` will return a reference to the
* `EventTarget`.
* @since v14.5.0
*/
addListener(type: string, listener: (arg: any) => void): this;
/**
* Node.js-specific extension to the `EventTarget` class that dispatches the
* `arg` to the list of handlers for `type`.
* @since v15.2.0
* @returns `true` if event listeners registered for the `type` exist,
* otherwise `false`.
*/
emit(type: string, arg: any): boolean;
/**
* Node.js-specific extension to the `EventTarget` class that returns an array
* of event `type` names for which event listeners are registered.
* @since 14.5.0
*/
eventNames(): string[];
/**
* Node.js-specific extension to the `EventTarget` class that returns the number
* of event listeners registered for the `type`.
* @since v14.5.0
*/
listenerCount(type: string): number;
/**
* Node.js-specific extension to the `EventTarget` class that sets the number
* of max event listeners as `n`.
* @since v14.5.0
*/
setMaxListeners(n: number): void;
/**
* Node.js-specific extension to the `EventTarget` class that returns the number
* of max event listeners.
* @since v14.5.0
*/
getMaxListeners(): number;
/**
* Node.js-specific alias for `eventTarget.removeEventListener()`.
* @since v14.5.0
*/
off(type: string, listener: (arg: any) => void, options?: EventListenerOptions): this;
/**
* Node.js-specific alias for `eventTarget.addEventListener()`.
* @since v14.5.0
*/
on(type: string, listener: (arg: any) => void): this;
/**
* Node.js-specific extension to the `EventTarget` class that adds a `once`
* listener for the given event `type`. This is equivalent to calling `on`
* with the `once` option set to `true`.
* @since v14.5.0
*/
once(type: string, listener: (arg: any) => void): this;
/**
* Node.js-specific extension to the `EventTarget` class. If `type` is specified,
* removes all registered listeners for `type`, otherwise removes all registered
* listeners.
* @since v14.5.0
*/
removeAllListeners(type?: string): this;
/**
* Node.js-specific extension to the `EventTarget` class that removes the
* `listener` for the given `type`. The only difference between `removeListener()`
* and `removeEventListener()` is that `removeListener()` will return a reference
* to the `EventTarget`.
* @since v14.5.0
*/
removeListener(type: string, listener: (arg: any) => void, options?: EventListenerOptions): this;
}
}
global {
namespace NodeJS {

View File

@ -1,6 +1,6 @@
{
"name": "@types/node",
"version": "20.19.17",
"version": "20.19.19",
"description": "TypeScript definitions for node",
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node",
"license": "MIT",
@ -135,6 +135,6 @@
"undici-types": "~6.21.0"
},
"peerDependencies": {},
"typesPublisherContentHash": "18a95350e2e7399cc51ac86ea7180283f82d3faf51d4545bc9de4d0e79a6395d",
"typesPublisherContentHash": "f137b9afbfbc3918867ab07269ee57c43fa45f74d9054590bed60377448b37ef",
"typeScriptVersion": "5.2"
}

24
node_modules/@types/node/test.d.ts generated vendored
View File

@ -79,6 +79,7 @@
* @see [source](https://github.com/nodejs/node/blob/v20.13.1/lib/test.js)
*/
declare module "node:test" {
import { AssertMethodNames } from "node:assert";
import { Readable } from "node:stream";
import TestFn = test.TestFn;
import TestOptions = test.TestOptions;
@ -933,28 +934,7 @@ declare module "node:test" {
*/
readonly mock: MockTracker;
}
interface TestContextAssert extends
Pick<
typeof import("assert"),
| "deepEqual"
| "deepStrictEqual"
| "doesNotMatch"
| "doesNotReject"
| "doesNotThrow"
| "equal"
| "fail"
| "ifError"
| "match"
| "notDeepEqual"
| "notDeepStrictEqual"
| "notEqual"
| "notStrictEqual"
| "ok"
| "rejects"
| "strictEqual"
| "throws"
>
{}
interface TestContextAssert extends Pick<typeof import("assert"), AssertMethodNames> {}
/**
* An instance of `SuiteContext` is passed to each suite function in order to
* interact with the test runner. However, the `SuiteContext` constructor is not

View File

@ -51,6 +51,7 @@ interface EventListenerObject {
handleEvent(object: Event): void;
}
type _EventListenerOptions = typeof globalThis extends { onmessage: any } ? {} : EventListenerOptions;
interface EventListenerOptions {
capture?: boolean;
}
@ -85,6 +86,8 @@ declare global {
new(type: string, eventInitDict?: EventInit): Event;
};
interface EventListenerOptions extends _EventListenerOptions {}
interface EventTarget extends _EventTarget {}
var EventTarget: typeof globalThis extends { onmessage: any; EventTarget: infer T } ? T
: {

View File

@ -53,7 +53,7 @@
*/
declare module "worker_threads" {
import { Context } from "node:vm";
import { EventEmitter } from "node:events";
import { EventEmitter, NodeEventTarget } from "node:events";
import { EventLoopUtilityFunction } from "node:perf_hooks";
import { FileHandle } from "node:fs/promises";
import { Readable, Writable } from "node:stream";
@ -106,7 +106,7 @@ declare module "worker_threads" {
* This implementation matches [browser `MessagePort`](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) s.
* @since v10.5.0
*/
class MessagePort extends EventEmitter {
class MessagePort implements EventTarget {
/**
* Disables further sending of messages on either side of the connection.
* This method can be called when no further communication will happen over this `MessagePort`.
@ -214,42 +214,32 @@ declare module "worker_threads" {
* @since v10.5.0
*/
start(): void;
addListener(event: "close", listener: () => void): this;
addListener(event: "close", listener: (ev: Event) => void): this;
addListener(event: "message", listener: (value: any) => void): this;
addListener(event: "messageerror", listener: (error: Error) => void): this;
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
emit(event: "close"): boolean;
addListener(event: string, listener: (arg: any) => void): this;
emit(event: "close", ev: Event): boolean;
emit(event: "message", value: any): boolean;
emit(event: "messageerror", error: Error): boolean;
emit(event: string | symbol, ...args: any[]): boolean;
on(event: "close", listener: () => void): this;
emit(event: string, arg: any): boolean;
off(event: "close", listener: (ev: Event) => void, options?: EventListenerOptions): this;
off(event: "message", listener: (value: any) => void, options?: EventListenerOptions): this;
off(event: "messageerror", listener: (error: Error) => void, options?: EventListenerOptions): this;
off(event: string, listener: (arg: any) => void, options?: EventListenerOptions): this;
on(event: "close", listener: (ev: Event) => void): this;
on(event: "message", listener: (value: any) => void): this;
on(event: "messageerror", listener: (error: Error) => void): this;
on(event: string | symbol, listener: (...args: any[]) => void): this;
once(event: "close", listener: () => void): this;
on(event: string, listener: (arg: any) => void): this;
once(event: "close", listener: (ev: Event) => void): this;
once(event: "message", listener: (value: any) => void): this;
once(event: "messageerror", listener: (error: Error) => void): this;
once(event: string | symbol, listener: (...args: any[]) => void): this;
prependListener(event: "close", listener: () => void): this;
prependListener(event: "message", listener: (value: any) => void): this;
prependListener(event: "messageerror", listener: (error: Error) => void): this;
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
prependOnceListener(event: "close", listener: () => void): this;
prependOnceListener(event: "message", listener: (value: any) => void): this;
prependOnceListener(event: "messageerror", listener: (error: Error) => void): this;
prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this;
removeListener(event: "close", listener: () => void): this;
removeListener(event: "message", listener: (value: any) => void): this;
removeListener(event: "messageerror", listener: (error: Error) => void): this;
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
off(event: "close", listener: () => void): this;
off(event: "message", listener: (value: any) => void): this;
off(event: "messageerror", listener: (error: Error) => void): this;
off(event: string | symbol, listener: (...args: any[]) => void): this;
addEventListener: EventTarget["addEventListener"];
dispatchEvent: EventTarget["dispatchEvent"];
removeEventListener: EventTarget["removeEventListener"];
once(event: string, listener: (arg: any) => void): this;
removeListener(event: "close", listener: (ev: Event) => void, options?: EventListenerOptions): this;
removeListener(event: "message", listener: (value: any) => void, options?: EventListenerOptions): this;
removeListener(event: "messageerror", listener: (error: Error) => void, options?: EventListenerOptions): this;
removeListener(event: string, listener: (arg: any) => void, options?: EventListenerOptions): this;
}
interface MessagePort extends NodeEventTarget {}
interface WorkerOptions {
/**
* List of arguments which would be stringified and appended to
@ -418,24 +408,6 @@ declare module "worker_threads" {
* @since v10.5.0
*/
postMessage(value: any, transferList?: readonly Transferable[]): void;
/**
* Sends a value to another worker, identified by its thread ID.
* @param threadId The target thread ID. If the thread ID is invalid, a `ERR_WORKER_MESSAGING_FAILED` error will be thrown.
* If the target thread ID is the current thread ID, a `ERR_WORKER_MESSAGING_SAME_THREAD` error will be thrown.
* @param value The value to send.
* @param transferList If one or more `MessagePort`-like objects are passed in value, a `transferList` is required for those items
* or `ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST` is thrown. See `port.postMessage()` for more information.
* @param timeout Time to wait for the message to be delivered in milliseconds. By default it's `undefined`, which means wait forever.
* If the operation times out, a `ERR_WORKER_MESSAGING_TIMEOUT` error is thrown.
* @since v20.19.0
*/
postMessageToThread(threadId: number, value: any, timeout?: number): Promise<void>;
postMessageToThread(
threadId: number,
value: any,
transferList: readonly Transferable[],
timeout?: number,
): Promise<void>;
/**
* Opposite of `unref()`, calling `ref()` on a previously `unref()`ed worker does _not_ let the program exit if it's the only active handle left (the default
* behavior). If the worker is `ref()`ed, calling `ref()` again has

3208
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@
"@radix-ui/react-toggle": "^1.1.2",
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@vercel/node": "^5.3.26",
"class-variance-authority": "^0.7.1",
"clsx": "*",
"cmdk": "^1.1.1",
@ -39,6 +40,7 @@
"lucide-react": "^0.487.0",
"motion": "*",
"next-themes": "^0.4.6",
"nodemailer": "^7.0.9",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
@ -50,7 +52,8 @@
"vaul": "^1.1.2"
},
"devDependencies": {
"@types/node": "^20.10.0",
"@types/node": "^20.19.19",
"@types/nodemailer": "^7.0.2",
"@types/react": "^19.1.14",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react-swc": "^3.10.2",

View File

@ -2,7 +2,7 @@ import { Button } from "./ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { Separator } from "./ui/separator";
import { Badge } from "./ui/badge";
import { CheckCircle, MapPin, Calendar, Clock, Users, Car, Phone, User } from "lucide-react";
import { CheckCircle, MapPin, Calendar, Users, Car, Phone, User } from "lucide-react";
interface BookingData {
pickup: string;
@ -33,8 +33,49 @@ interface BookingConfirmationProps {
onEdit: () => void;
}
export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, onEdit }: BookingConfirmationProps) {
const totalFare = selectedVehicle.price;
export function BookingConfirmation({
bookingData,
selectedVehicle,
onConfirm,
onEdit,
}: BookingConfirmationProps) {
// --- POST booking data to Vercel backend ---
const handleConfirm = async () => {
try {
const res = await fetch(
"https://sangwari-backend-6fperqp5u-sangwari-taxis-projects.vercel.app/api/sendBookingEmail",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
pickup: bookingData.pickup,
dropoff: bookingData.dropoff,
date: bookingData.date,
time: bookingData.time,
passengers: bookingData.passengers,
contactName: bookingData.contactName,
contactNumber: bookingData.contactNumber,
}),
}
);
if (res.ok) {
alert("Booking email sent successfully!");
onConfirm(); // optional: trigger parent callback
} else {
const data = await res.json();
console.error("Error response:", data);
alert("Failed to send booking email.");
}
} catch (err) {
console.error("Error sending booking:", err);
alert("Error sending booking. Check console for details.");
}
};
return (
<Card className="w-full max-w-2xl">
@ -48,7 +89,6 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
{/* Trip Details */}
<div className="space-y-4">
<h3>Trip Details</h3>
<div className="grid gap-3">
<div className="flex items-center gap-3">
<MapPin className="h-4 w-4 text-green-500" />
@ -57,7 +97,6 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
<div>{bookingData.pickup}</div>
</div>
</div>
<div className="flex items-center gap-3">
<MapPin className="h-4 w-4 text-red-500" />
<div>
@ -65,7 +104,6 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
<div>{bookingData.dropoff}</div>
</div>
</div>
<div className="flex items-center gap-3">
<Calendar className="h-4 w-4" />
<div>
@ -73,7 +111,6 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
<div>{new Date(bookingData.date).toLocaleDateString()} at {bookingData.time}</div>
</div>
</div>
<div className="flex items-center gap-3">
<Users className="h-4 w-4" />
<div>
@ -81,7 +118,6 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
<div>{bookingData.passengers}</div>
</div>
</div>
<div className="flex items-center gap-3">
<User className="h-4 w-4" />
<div>
@ -89,7 +125,6 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
<div>{bookingData.contactName}</div>
</div>
</div>
<div className="flex items-center gap-3">
<Phone className="h-4 w-4" />
<div>
@ -105,7 +140,6 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
{/* Vehicle Details */}
<div className="space-y-4">
<h3>Selected Vehicle</h3>
<div className="flex items-center gap-4">
<div className="w-12 h-12 bg-gray-100 rounded-lg flex items-center justify-center">
<Car className="h-6 w-6 text-gray-600" />
@ -131,16 +165,16 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
{/* Booking Info */}
<div className="space-y-4">
<h3>Booking Information</h3>
<div className="flex justify-between items-center p-4 bg-green-50 rounded-lg">
<span className="font-medium">{bookingData.dropoff.includes('Airport Taxi') ? 'Airport Taxi Fee' : 'Customize Price'}</span>
<span className="font-medium">
{bookingData.dropoff.includes("Airport Taxi") ? "Airport Taxi Fee" : "Customize Price"}
</span>
<span className="text-xl font-semibold text-green-600">
{bookingData.dropoff.includes('Airport Taxi') ? '₹1099' : 'Call for Quote'}
{bookingData.dropoff.includes("Airport Taxi") ? "₹1099" : "Call for Quote"}
</span>
</div>
<div className="text-sm text-muted-foreground text-center">
{bookingData.dropoff.includes('Airport Taxi')
{bookingData.dropoff.includes("Airport Taxi")
? `₹1099 fixed rate for airport taxi. Driver will contact you at ${bookingData.contactNumber} for pickup details.`
: `Driver will contact you at ${bookingData.contactNumber} for final pricing and trip details.`}
</div>
@ -151,11 +185,11 @@ export function BookingConfirmation({ bookingData, selectedVehicle, onConfirm, o
<Button variant="outline" onClick={onEdit} className="flex-1">
Edit Booking
</Button>
<Button onClick={onConfirm} className="flex-1 bg-green-600 hover:bg-green-700">
<Button onClick={handleConfirm} className="flex-1 bg-green-600 hover:bg-green-700">
Book Now
</Button>
</div>
</CardContent>
</Card>
);
}
}

11
vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string;
readonly VITE_APP_TITLE?: string;
// Add other env variables here...
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}