mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: delete volume
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import { type Options, listVolumes, createVolume } from "../sdk.gen";
|
||||
import {
|
||||
queryOptions,
|
||||
type UseMutationOptions,
|
||||
type DefaultError,
|
||||
} from "@tanstack/react-query";
|
||||
import { type Options, listVolumes, createVolume, deleteVolume } from "../sdk.gen";
|
||||
import { queryOptions, type UseMutationOptions, type DefaultError } from "@tanstack/react-query";
|
||||
import type {
|
||||
ListVolumesData,
|
||||
CreateVolumeData,
|
||||
CreateVolumeResponse,
|
||||
DeleteVolumeData,
|
||||
DeleteVolumeResponse,
|
||||
} from "../types.gen";
|
||||
import { client as _heyApiClient } from "../client.gen";
|
||||
|
||||
@@ -29,9 +27,7 @@ const createQueryKey = <TOptions extends Options>(
|
||||
): [QueryKey<TOptions>[0]] => {
|
||||
const params: QueryKey<TOptions>[0] = {
|
||||
_id: id,
|
||||
baseUrl:
|
||||
options?.baseUrl ||
|
||||
(options?.client ?? _heyApiClient).getConfig().baseUrl,
|
||||
baseUrl: options?.baseUrl || (options?.client ?? _heyApiClient).getConfig().baseUrl,
|
||||
} as QueryKey<TOptions>[0];
|
||||
if (infinite) {
|
||||
params._infinite = infinite;
|
||||
@@ -54,8 +50,7 @@ const createQueryKey = <TOptions extends Options>(
|
||||
return [params];
|
||||
};
|
||||
|
||||
export const listVolumesQueryKey = (options?: Options<ListVolumesData>) =>
|
||||
createQueryKey("listVolumes", options);
|
||||
export const listVolumesQueryKey = (options?: Options<ListVolumesData>) => createQueryKey("listVolumes", options);
|
||||
|
||||
/**
|
||||
* List all volumes
|
||||
@@ -75,8 +70,7 @@ export const listVolumesOptions = (options?: Options<ListVolumesData>) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const createVolumeQueryKey = (options?: Options<CreateVolumeData>) =>
|
||||
createQueryKey("createVolume", options);
|
||||
export const createVolumeQueryKey = (options?: Options<CreateVolumeData>) => createQueryKey("createVolume", options);
|
||||
|
||||
/**
|
||||
* Create a new volume
|
||||
@@ -101,16 +95,8 @@ export const createVolumeOptions = (options?: Options<CreateVolumeData>) => {
|
||||
*/
|
||||
export const createVolumeMutation = (
|
||||
options?: Partial<Options<CreateVolumeData>>,
|
||||
): UseMutationOptions<
|
||||
CreateVolumeResponse,
|
||||
DefaultError,
|
||||
Options<CreateVolumeData>
|
||||
> => {
|
||||
const mutationOptions: UseMutationOptions<
|
||||
CreateVolumeResponse,
|
||||
DefaultError,
|
||||
Options<CreateVolumeData>
|
||||
> = {
|
||||
): UseMutationOptions<CreateVolumeResponse, DefaultError, Options<CreateVolumeData>> => {
|
||||
const mutationOptions: UseMutationOptions<CreateVolumeResponse, DefaultError, Options<CreateVolumeData>> = {
|
||||
mutationFn: async (localOptions) => {
|
||||
const { data } = await createVolume({
|
||||
...options,
|
||||
@@ -122,3 +108,22 @@ export const createVolumeMutation = (
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a volume
|
||||
*/
|
||||
export const deleteVolumeMutation = (
|
||||
options?: Partial<Options<DeleteVolumeData>>,
|
||||
): UseMutationOptions<DeleteVolumeResponse, DefaultError, Options<DeleteVolumeData>> => {
|
||||
const mutationOptions: UseMutationOptions<DeleteVolumeResponse, DefaultError, Options<DeleteVolumeData>> = {
|
||||
mutationFn: async (localOptions) => {
|
||||
const { data } = await deleteVolume({
|
||||
...options,
|
||||
...localOptions,
|
||||
throwOnError: true,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mutationOptions;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { ClientOptions } from "./types.gen";
|
||||
import {
|
||||
type Config,
|
||||
type ClientOptions as DefaultClientOptions,
|
||||
createClient,
|
||||
createConfig,
|
||||
} from "./client";
|
||||
import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from "./client";
|
||||
|
||||
/**
|
||||
* The `createClientConfig()` function will be called on client initialization
|
||||
@@ -16,10 +11,9 @@ import {
|
||||
* `setConfig()`. This is useful for example if you're using Next.js
|
||||
* to ensure your client always has the correct values.
|
||||
*/
|
||||
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> =
|
||||
(
|
||||
override?: Config<DefaultClientOptions & T>,
|
||||
) => Config<Required<DefaultClientOptions> & T>;
|
||||
export type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> = (
|
||||
override?: Config<DefaultClientOptions & T>,
|
||||
) => Config<Required<DefaultClientOptions> & T>;
|
||||
|
||||
export const client = createClient(
|
||||
createConfig<ClientOptions>({
|
||||
|
||||
@@ -26,12 +26,7 @@ export const createClient = (config: Config = {}): Client => {
|
||||
return getConfig();
|
||||
};
|
||||
|
||||
const interceptors = createInterceptors<
|
||||
Request,
|
||||
Response,
|
||||
unknown,
|
||||
ResolvedRequestOptions
|
||||
>();
|
||||
const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>();
|
||||
|
||||
const request: Client["request"] = async (options) => {
|
||||
const opts = {
|
||||
@@ -94,10 +89,7 @@ export const createClient = (config: Config = {}): Client => {
|
||||
};
|
||||
|
||||
if (response.ok) {
|
||||
if (
|
||||
response.status === 204 ||
|
||||
response.headers.get("Content-Length") === "0"
|
||||
) {
|
||||
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
||||
return opts.responseStyle === "data"
|
||||
? {}
|
||||
: {
|
||||
@@ -107,9 +99,7 @@ export const createClient = (config: Config = {}): Client => {
|
||||
}
|
||||
|
||||
const parseAs =
|
||||
(opts.parseAs === "auto"
|
||||
? getParseAs(response.headers.get("Content-Type"))
|
||||
: opts.parseAs) ?? "json";
|
||||
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
|
||||
|
||||
let data: any;
|
||||
switch (parseAs) {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { Auth } from "../core/auth.gen";
|
||||
import type {
|
||||
Client as CoreClient,
|
||||
Config as CoreConfig,
|
||||
} from "../core/types.gen";
|
||||
import type { Client as CoreClient, Config as CoreConfig } from "../core/types.gen";
|
||||
import type { Middleware } from "./utils.gen";
|
||||
|
||||
export type ResponseStyle = "data" | "fields";
|
||||
@@ -38,14 +35,7 @@ export interface Config<T extends ClientOptions = ClientOptions>
|
||||
*
|
||||
* @default 'auto'
|
||||
*/
|
||||
parseAs?:
|
||||
| "arrayBuffer"
|
||||
| "auto"
|
||||
| "blob"
|
||||
| "formData"
|
||||
| "json"
|
||||
| "stream"
|
||||
| "text";
|
||||
parseAs?: "arrayBuffer" | "auto" | "blob" | "formData" | "json" | "stream" | "text";
|
||||
/**
|
||||
* Should we return only data or multiple fields (data, error, response, etc.)?
|
||||
*
|
||||
@@ -103,32 +93,22 @@ export type RequestResult<
|
||||
? TData[keyof TData]
|
||||
: TData
|
||||
: {
|
||||
data: TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData;
|
||||
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
|
||||
request: Request;
|
||||
response: Response;
|
||||
}
|
||||
>
|
||||
: Promise<
|
||||
TResponseStyle extends "data"
|
||||
?
|
||||
| (TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData)
|
||||
| undefined
|
||||
? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined
|
||||
: (
|
||||
| {
|
||||
data: TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData;
|
||||
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData;
|
||||
error: undefined;
|
||||
}
|
||||
| {
|
||||
data: undefined;
|
||||
error: TError extends Record<string, unknown>
|
||||
? TError[keyof TError]
|
||||
: TError;
|
||||
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError;
|
||||
}
|
||||
) & {
|
||||
request: Request;
|
||||
@@ -202,11 +182,7 @@ export type Options<
|
||||
TData extends TDataShape = TDataShape,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
> = OmitKeys<
|
||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
||||
"body" | "path" | "query" | "url"
|
||||
> &
|
||||
Omit<TData, "url">;
|
||||
> = OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> & Omit<TData, "url">;
|
||||
|
||||
export type OptionsLegacyParser<
|
||||
TData = unknown,
|
||||
@@ -214,19 +190,12 @@ export type OptionsLegacyParser<
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
> = TData extends { body?: any }
|
||||
? TData extends { headers?: any }
|
||||
? OmitKeys<
|
||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
||||
"body" | "headers" | "url"
|
||||
> &
|
||||
TData
|
||||
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "headers" | "url"> & TData
|
||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "body" | "url"> &
|
||||
TData &
|
||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "headers">
|
||||
: TData extends { headers?: any }
|
||||
? OmitKeys<
|
||||
RequestOptions<TResponseStyle, ThrowOnError>,
|
||||
"headers" | "url"
|
||||
> &
|
||||
? OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "headers" | "url"> &
|
||||
TData &
|
||||
Pick<RequestOptions<TResponseStyle, ThrowOnError>, "body">
|
||||
: OmitKeys<RequestOptions<TResponseStyle, ThrowOnError>, "url"> & TData;
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import { getAuthToken } from "../core/auth.gen";
|
||||
import type {
|
||||
QuerySerializer,
|
||||
QuerySerializerOptions,
|
||||
} from "../core/bodySerializer.gen";
|
||||
import type { QuerySerializer, QuerySerializerOptions } from "../core/bodySerializer.gen";
|
||||
import { jsonBodySerializer } from "../core/bodySerializer.gen";
|
||||
import {
|
||||
serializeArrayParam,
|
||||
serializeObjectParam,
|
||||
serializePrimitiveParam,
|
||||
} from "../core/pathSerializer.gen";
|
||||
import type {
|
||||
Client,
|
||||
ClientOptions,
|
||||
Config,
|
||||
RequestOptions,
|
||||
} from "./types.gen";
|
||||
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.gen";
|
||||
import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen";
|
||||
|
||||
interface PathSerializer {
|
||||
path: Record<string, unknown>;
|
||||
@@ -58,10 +46,7 @@ const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
url = url.replace(
|
||||
match,
|
||||
serializeArrayParam({ explode, name, style, value }),
|
||||
);
|
||||
url = url.replace(match, serializeArrayParam({ explode, name, style, value }));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -90,20 +75,14 @@ const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
const replaceValue = encodeURIComponent(
|
||||
style === "label" ? `.${value as string}` : (value as string),
|
||||
);
|
||||
const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string));
|
||||
url = url.replace(match, replaceValue);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
export const createQuerySerializer = <T = unknown>({
|
||||
allowReserved,
|
||||
array,
|
||||
object,
|
||||
}: QuerySerializerOptions = {}) => {
|
||||
export const createQuerySerializer = <T = unknown>({ allowReserved, array, object }: QuerySerializerOptions = {}) => {
|
||||
const querySerializer = (queryParams: T) => {
|
||||
const search: string[] = [];
|
||||
if (queryParams && typeof queryParams === "object") {
|
||||
@@ -152,9 +131,7 @@ export const createQuerySerializer = <T = unknown>({
|
||||
/**
|
||||
* Infers parseAs value from provided Content-Type header.
|
||||
*/
|
||||
export const getParseAs = (
|
||||
contentType: string | null,
|
||||
): Exclude<Config["parseAs"], "auto"> => {
|
||||
export const getParseAs = (contentType: string | null): Exclude<Config["parseAs"], "auto"> => {
|
||||
if (!contentType) {
|
||||
// If no Content-Type header is provided, the best we can do is return the raw response body,
|
||||
// which is effectively the same as the 'stream' option.
|
||||
@@ -167,10 +144,7 @@ export const getParseAs = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
cleanContent.startsWith("application/json") ||
|
||||
cleanContent.endsWith("+json")
|
||||
) {
|
||||
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
|
||||
return "json";
|
||||
}
|
||||
|
||||
@@ -178,11 +152,7 @@ export const getParseAs = (
|
||||
return "formData";
|
||||
}
|
||||
|
||||
if (
|
||||
["application/", "audio/", "image/", "video/"].some((type) =>
|
||||
cleanContent.startsWith(type),
|
||||
)
|
||||
) {
|
||||
if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
|
||||
return "blob";
|
||||
}
|
||||
|
||||
@@ -202,11 +172,7 @@ const checkForExistence = (
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
options.headers.has(name) ||
|
||||
options.query?.[name] ||
|
||||
options.headers.get("Cookie")?.includes(`${name}=`)
|
||||
) {
|
||||
if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -301,17 +267,14 @@ export const mergeConfigs = (a: Config, b: Config): Config => {
|
||||
return config;
|
||||
};
|
||||
|
||||
export const mergeHeaders = (
|
||||
...headers: Array<Required<Config>["headers"] | undefined>
|
||||
): Headers => {
|
||||
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
||||
const mergedHeaders = new Headers();
|
||||
for (const header of headers) {
|
||||
if (!header || typeof header !== "object") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const iterator =
|
||||
header instanceof Headers ? header.entries() : Object.entries(header);
|
||||
const iterator = header instanceof Headers ? header.entries() : Object.entries(header);
|
||||
|
||||
for (const [key, value] of iterator) {
|
||||
if (value === null) {
|
||||
@@ -323,10 +286,7 @@ export const mergeHeaders = (
|
||||
} else if (value !== undefined) {
|
||||
// assume object headers are meant to be JSON stringified, i.e. their
|
||||
// content value in OpenAPI specification is 'application/json'
|
||||
mergedHeaders.set(
|
||||
key,
|
||||
typeof value === "object" ? JSON.stringify(value) : (value as string),
|
||||
);
|
||||
mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : (value as string));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,16 +300,9 @@ type ErrInterceptor<Err, Res, Req, Options> = (
|
||||
options: Options,
|
||||
) => Err | Promise<Err>;
|
||||
|
||||
type ReqInterceptor<Req, Options> = (
|
||||
request: Req,
|
||||
options: Options,
|
||||
) => Req | Promise<Req>;
|
||||
type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>;
|
||||
|
||||
type ResInterceptor<Res, Req, Options> = (
|
||||
response: Res,
|
||||
request: Req,
|
||||
options: Options,
|
||||
) => Res | Promise<Res>;
|
||||
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>;
|
||||
|
||||
class Interceptors<Interceptor> {
|
||||
_fns: (Interceptor | null)[];
|
||||
@@ -400,15 +353,9 @@ class Interceptors<Interceptor> {
|
||||
// `createInterceptors()` response, meant for external use as it does not
|
||||
// expose internals
|
||||
export interface Middleware<Req, Res, Err, Options> {
|
||||
error: Pick<
|
||||
Interceptors<ErrInterceptor<Err, Res, Req, Options>>,
|
||||
"eject" | "use"
|
||||
>;
|
||||
error: Pick<Interceptors<ErrInterceptor<Err, Res, Req, Options>>, "eject" | "use">;
|
||||
request: Pick<Interceptors<ReqInterceptor<Req, Options>>, "eject" | "use">;
|
||||
response: Pick<
|
||||
Interceptors<ResInterceptor<Res, Req, Options>>,
|
||||
"eject" | "use"
|
||||
>;
|
||||
response: Pick<Interceptors<ResInterceptor<Res, Req, Options>>, "eject" | "use">;
|
||||
}
|
||||
|
||||
// do not add `Middleware` as return type so we can use _fns internally
|
||||
|
||||
@@ -23,8 +23,7 @@ export const getAuthToken = async (
|
||||
auth: Auth,
|
||||
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
|
||||
): Promise<string | undefined> => {
|
||||
const token =
|
||||
typeof callback === "function" ? await callback(auth) : callback;
|
||||
const token = typeof callback === "function" ? await callback(auth) : callback;
|
||||
|
||||
if (!token) {
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type {
|
||||
ArrayStyle,
|
||||
ObjectStyle,
|
||||
SerializerOptions,
|
||||
} from "./pathSerializer.gen";
|
||||
import type { ArrayStyle, ObjectStyle, SerializerOptions } from "./pathSerializer.gen";
|
||||
|
||||
export type QuerySerializer = (query: Record<string, unknown>) => string;
|
||||
|
||||
@@ -16,11 +12,7 @@ export interface QuerySerializerOptions {
|
||||
object?: SerializerOptions<ObjectStyle>;
|
||||
}
|
||||
|
||||
const serializeFormDataPair = (
|
||||
data: FormData,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): void => {
|
||||
const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => {
|
||||
if (typeof value === "string" || value instanceof Blob) {
|
||||
data.append(key, value);
|
||||
} else if (value instanceof Date) {
|
||||
@@ -30,11 +22,7 @@ const serializeFormDataPair = (
|
||||
}
|
||||
};
|
||||
|
||||
const serializeUrlSearchParamsPair = (
|
||||
data: URLSearchParams,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): void => {
|
||||
const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => {
|
||||
if (typeof value === "string") {
|
||||
data.append(key, value);
|
||||
} else {
|
||||
@@ -43,9 +31,7 @@ const serializeUrlSearchParamsPair = (
|
||||
};
|
||||
|
||||
export const formDataBodySerializer = {
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
||||
body: T,
|
||||
): FormData => {
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): FormData => {
|
||||
const data = new FormData();
|
||||
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
@@ -65,15 +51,11 @@ export const formDataBodySerializer = {
|
||||
|
||||
export const jsonBodySerializer = {
|
||||
bodySerializer: <T>(body: T): string =>
|
||||
JSON.stringify(body, (_key, value) =>
|
||||
typeof value === "bigint" ? value.toString() : value,
|
||||
),
|
||||
JSON.stringify(body, (_key, value) => (typeof value === "bigint" ? value.toString() : value)),
|
||||
};
|
||||
|
||||
export const urlSearchParamsBodySerializer = {
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
|
||||
body: T,
|
||||
): string => {
|
||||
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): string => {
|
||||
const data = new URLSearchParams();
|
||||
|
||||
Object.entries(body).forEach(([key, value]) => {
|
||||
|
||||
@@ -83,10 +83,7 @@ const stripEmptySlots = (params: Params) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const buildClientParams = (
|
||||
args: ReadonlyArray<unknown>,
|
||||
fields: FieldsConfig,
|
||||
) => {
|
||||
export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
|
||||
const params: Params = {
|
||||
body: {},
|
||||
headers: {},
|
||||
@@ -123,19 +120,13 @@ export const buildClientParams = (
|
||||
const name = field.map || key;
|
||||
(params[field.in] as Record<string, unknown>)[name] = value;
|
||||
} else {
|
||||
const extra = extraPrefixes.find(([prefix]) =>
|
||||
key.startsWith(prefix),
|
||||
);
|
||||
const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix));
|
||||
|
||||
if (extra) {
|
||||
const [prefix, slot] = extra;
|
||||
(params[slot] as Record<string, unknown>)[
|
||||
key.slice(prefix.length)
|
||||
] = value;
|
||||
(params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value;
|
||||
} else {
|
||||
for (const [slot, allowed] of Object.entries(
|
||||
config.allowExtra ?? {},
|
||||
)) {
|
||||
for (const [slot, allowed] of Object.entries(config.allowExtra ?? {})) {
|
||||
if (allowed) {
|
||||
(params[slot as Slot] as Record<string, unknown>)[key] = value;
|
||||
break;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
interface SerializeOptions<T>
|
||||
extends SerializePrimitiveOptions,
|
||||
SerializerOptions<T> {}
|
||||
interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {}
|
||||
|
||||
interface SerializePrimitiveOptions {
|
||||
allowReserved?: boolean;
|
||||
@@ -76,9 +74,9 @@ export const serializeArrayParam = ({
|
||||
value: unknown[];
|
||||
}) => {
|
||||
if (!explode) {
|
||||
const joinedValues = (
|
||||
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
|
||||
).join(separatorArrayNoExplode(style));
|
||||
const joinedValues = (allowReserved ? value : value.map((v) => encodeURIComponent(v as string))).join(
|
||||
separatorArrayNoExplode(style),
|
||||
);
|
||||
switch (style) {
|
||||
case "label":
|
||||
return `.${joinedValues}`;
|
||||
@@ -105,16 +103,10 @@ export const serializeArrayParam = ({
|
||||
});
|
||||
})
|
||||
.join(separator);
|
||||
return style === "label" || style === "matrix"
|
||||
? separator + joinedValues
|
||||
: joinedValues;
|
||||
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
||||
};
|
||||
|
||||
export const serializePrimitiveParam = ({
|
||||
allowReserved,
|
||||
name,
|
||||
value,
|
||||
}: SerializePrimitiveParam) => {
|
||||
export const serializePrimitiveParam = ({ allowReserved, name, value }: SerializePrimitiveParam) => {
|
||||
if (value === undefined || value === null) {
|
||||
return "";
|
||||
}
|
||||
@@ -146,11 +138,7 @@ export const serializeObjectParam = ({
|
||||
if (style !== "deepObject" && !explode) {
|
||||
let values: string[] = [];
|
||||
Object.entries(value).forEach(([key, v]) => {
|
||||
values = [
|
||||
...values,
|
||||
key,
|
||||
allowReserved ? (v as string) : encodeURIComponent(v as string),
|
||||
];
|
||||
values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)];
|
||||
});
|
||||
const joinedValues = values.join(",");
|
||||
switch (style) {
|
||||
@@ -175,7 +163,5 @@ export const serializeObjectParam = ({
|
||||
}),
|
||||
)
|
||||
.join(separator);
|
||||
return style === "label" || style === "matrix"
|
||||
? separator + joinedValues
|
||||
: joinedValues;
|
||||
return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { Auth, AuthToken } from "./auth.gen";
|
||||
import type {
|
||||
BodySerializer,
|
||||
QuerySerializer,
|
||||
QuerySerializerOptions,
|
||||
} from "./bodySerializer.gen";
|
||||
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen";
|
||||
|
||||
export interface Client<
|
||||
RequestFn = never,
|
||||
Config = unknown,
|
||||
MethodFn = never,
|
||||
BuildUrlFn = never,
|
||||
> {
|
||||
export interface Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never> {
|
||||
/**
|
||||
* Returns the final request URL.
|
||||
*/
|
||||
@@ -50,31 +41,13 @@ export interface Config {
|
||||
*/
|
||||
headers?:
|
||||
| RequestInit["headers"]
|
||||
| Record<
|
||||
string,
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| (string | number | boolean)[]
|
||||
| null
|
||||
| undefined
|
||||
| unknown
|
||||
>;
|
||||
| Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined | unknown>;
|
||||
/**
|
||||
* The request method.
|
||||
*
|
||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
||||
*/
|
||||
method?:
|
||||
| "CONNECT"
|
||||
| "DELETE"
|
||||
| "GET"
|
||||
| "HEAD"
|
||||
| "OPTIONS"
|
||||
| "PATCH"
|
||||
| "POST"
|
||||
| "PUT"
|
||||
| "TRACE";
|
||||
method?: "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE";
|
||||
/**
|
||||
* A function for serializing request query parameters. By default, arrays
|
||||
* will be exploded in form style, objects will be exploded in deepObject
|
||||
@@ -114,7 +87,5 @@ type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
|
||||
: false;
|
||||
|
||||
export type OmitNever<T extends Record<string, unknown>> = {
|
||||
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
|
||||
? never
|
||||
: K]: T[K];
|
||||
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true ? never : K]: T[K];
|
||||
};
|
||||
|
||||
@@ -6,13 +6,15 @@ import type {
|
||||
ListVolumesResponses,
|
||||
CreateVolumeData,
|
||||
CreateVolumeResponses,
|
||||
DeleteVolumeData,
|
||||
DeleteVolumeResponses,
|
||||
} from "./types.gen";
|
||||
import { client as _heyApiClient } from "./client.gen";
|
||||
|
||||
export type Options<
|
||||
TData extends TDataShape = TDataShape,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
> = ClientOptions<TData, ThrowOnError> & {
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<
|
||||
TData,
|
||||
ThrowOnError
|
||||
> & {
|
||||
/**
|
||||
* You can provide a client instance returned by `createClient()` instead of
|
||||
* individual options. This might be also useful if you want to implement a
|
||||
@@ -29,14 +31,8 @@ export type Options<
|
||||
/**
|
||||
* List all volumes
|
||||
*/
|
||||
export const listVolumes = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<ListVolumesData, ThrowOnError>,
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).get<
|
||||
ListVolumesResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
export const listVolumes = <ThrowOnError extends boolean = false>(options?: Options<ListVolumesData, ThrowOnError>) => {
|
||||
return (options?.client ?? _heyApiClient).get<ListVolumesResponses, unknown, ThrowOnError>({
|
||||
url: "/api/v1/volumes",
|
||||
...options,
|
||||
});
|
||||
@@ -48,11 +44,7 @@ export const listVolumes = <ThrowOnError extends boolean = false>(
|
||||
export const createVolume = <ThrowOnError extends boolean = false>(
|
||||
options?: Options<CreateVolumeData, ThrowOnError>,
|
||||
) => {
|
||||
return (options?.client ?? _heyApiClient).post<
|
||||
CreateVolumeResponses,
|
||||
unknown,
|
||||
ThrowOnError
|
||||
>({
|
||||
return (options?.client ?? _heyApiClient).post<CreateVolumeResponses, unknown, ThrowOnError>({
|
||||
url: "/api/v1/volumes",
|
||||
...options,
|
||||
headers: {
|
||||
@@ -61,3 +53,15 @@ export const createVolume = <ThrowOnError extends boolean = false>(
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a volume
|
||||
*/
|
||||
export const deleteVolume = <ThrowOnError extends boolean = false>(
|
||||
options: Options<DeleteVolumeData, ThrowOnError>,
|
||||
) => {
|
||||
return (options.client ?? _heyApiClient).delete<DeleteVolumeResponses, unknown, ThrowOnError>({
|
||||
url: "/api/v1/volumes/{name}",
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,14 +14,13 @@ export type ListVolumesResponses = {
|
||||
200: {
|
||||
volumes: Array<{
|
||||
createdAt: number;
|
||||
mountpoint: string;
|
||||
name: string;
|
||||
path: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type ListVolumesResponse =
|
||||
ListVolumesResponses[keyof ListVolumesResponses];
|
||||
export type ListVolumesResponse = ListVolumesResponses[keyof ListVolumesResponses];
|
||||
|
||||
export type CreateVolumeData = {
|
||||
body?: {
|
||||
@@ -51,14 +50,36 @@ export type CreateVolumeResponses = {
|
||||
* Volume created successfully
|
||||
*/
|
||||
201: {
|
||||
createdAt: number;
|
||||
mountpoint: string;
|
||||
name: string;
|
||||
message: string;
|
||||
volume: {
|
||||
createdAt: number;
|
||||
name: string;
|
||||
path: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type CreateVolumeResponse =
|
||||
CreateVolumeResponses[keyof CreateVolumeResponses];
|
||||
export type CreateVolumeResponse = CreateVolumeResponses[keyof CreateVolumeResponses];
|
||||
|
||||
export type DeleteVolumeData = {
|
||||
body?: never;
|
||||
path: {
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: "/api/v1/volumes/{name}";
|
||||
};
|
||||
|
||||
export type DeleteVolumeResponses = {
|
||||
/**
|
||||
* Volume deleted successfully
|
||||
*/
|
||||
200: {
|
||||
message: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type DeleteVolumeResponse = DeleteVolumeResponses[keyof DeleteVolumeResponses];
|
||||
|
||||
export type ClientOptions = {
|
||||
baseUrl: "http://localhost:3000" | (string & {});
|
||||
|
||||
23
apps/client/app/components/ui/sonner.tsx
Normal file
23
apps/client/app/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
style={
|
||||
{
|
||||
"--normal-bg": "var(--popover)",
|
||||
"--normal-text": "var(--popover-foreground)",
|
||||
"--normal-border": "var(--border)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster };
|
||||
7
apps/client/app/lib/errors.ts
Normal file
7
apps/client/app/lib/errors.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const parseError = (error?: unknown) => {
|
||||
if (error && typeof error === "object" && "message" in error) {
|
||||
return { message: error.message as string };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@@ -1,12 +1,6 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import {
|
||||
isRouteErrorResponse,
|
||||
Links,
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
} from "react-router";
|
||||
import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
|
||||
import { Toaster } from "~/components/ui/sonner";
|
||||
|
||||
import type { Route } from "./+types/root";
|
||||
import "./app.css";
|
||||
@@ -43,6 +37,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<body className="h-screen">
|
||||
{children}
|
||||
<Toaster />
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
@@ -62,10 +57,7 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
|
||||
if (isRouteErrorResponse(error)) {
|
||||
message = error.status === 404 ? "404" : "Error";
|
||||
details =
|
||||
error.status === 404
|
||||
? "The requested page could not be found."
|
||||
: error.statusText || details;
|
||||
details = error.status === 404 ? "The requested page could not be found." : error.statusText || details;
|
||||
} else if (import.meta.env.DEV && error && error instanceof Error) {
|
||||
details = error.message;
|
||||
stack = error.stack;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Copy, Folder } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { listVolumes } from "~/api-client";
|
||||
import { useFetcher } from "react-router";
|
||||
import { createVolume, deleteVolume, listVolumes } from "~/api-client";
|
||||
import { CreateVolumeDialog } from "~/components/create-volume-dialog";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Input } from "~/components/ui/input";
|
||||
@@ -8,7 +9,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~
|
||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table";
|
||||
import { cn } from "~/lib/utils";
|
||||
import type { Route } from "./+types/home";
|
||||
import { useFetcher } from "react-router";
|
||||
import { parseError } from "~/lib/errors";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export function meta(_: Route.MetaArgs) {
|
||||
return [
|
||||
@@ -25,17 +27,29 @@ export async function clientAction({ request }: Route.ClientActionArgs) {
|
||||
const { _action, ...rest } = Object.fromEntries(formData.entries());
|
||||
|
||||
if (_action === "delete") {
|
||||
return { yolo: "swag", _action: "delete" as const };
|
||||
console.log("Delete action triggered", rest);
|
||||
// Delete volume logic
|
||||
const { error } = await deleteVolume({ path: { name: rest.name as string } });
|
||||
|
||||
if (error) {
|
||||
toast.error("Failed to delete volume", {
|
||||
description: parseError(error)?.message,
|
||||
});
|
||||
} else {
|
||||
toast.success("Volume deleted successfully");
|
||||
}
|
||||
|
||||
return { error: parseError(error), _action: "delete" as const };
|
||||
}
|
||||
|
||||
if (_action === "create") {
|
||||
console.log("Create action triggered", rest);
|
||||
return {
|
||||
error: "Volume with this name already exists.",
|
||||
_action: "create" as const,
|
||||
};
|
||||
const { error } = await createVolume({ body: { name: rest.name as string, config: { backend: "directory" } } });
|
||||
if (error) {
|
||||
toast.error("Failed to create volume", {
|
||||
description: parseError(error)?.message,
|
||||
});
|
||||
} else {
|
||||
toast.success("Volume created successfully");
|
||||
}
|
||||
return { error: parseError(error), _action: "create" as const };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +145,7 @@ export default function Home({ loaderData, actionData }: Route.ComponentProps) {
|
||||
<TableCell>
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-xs truncate bg-primary/10 rounded-md px-2 py-1">
|
||||
{volume.mountpoint}
|
||||
{volume.path}
|
||||
</span>
|
||||
<Copy size={10} />
|
||||
</span>
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ironmount/server": "workspace:*",
|
||||
"@hookform/resolvers": "^5.2.1",
|
||||
"@ironmount/server": "workspace:*",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
@@ -25,10 +25,12 @@
|
||||
"clsx": "^2.1.1",
|
||||
"isbot": "^5.1.27",
|
||||
"lucide-react": "^0.539.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-router": "^7.7.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"zod": "^4.0.17"
|
||||
},
|
||||
@@ -45,4 +47,3 @@
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,7 @@ const directoryConfigSchema = type({
|
||||
backend: "'directory'",
|
||||
});
|
||||
|
||||
export const volumeConfigSchema = nfsConfigSchema
|
||||
.or(smbConfigSchema)
|
||||
.or(directoryConfigSchema);
|
||||
export const volumeConfigSchema = nfsConfigSchema.or(smbConfigSchema).or(directoryConfigSchema);
|
||||
|
||||
export type BackendConfig = typeof volumeConfigSchema.infer;
|
||||
|
||||
@@ -36,11 +34,9 @@ export const volumesTable = sqliteTable("volumes_table", {
|
||||
name: text().notNull().unique(),
|
||||
path: text().notNull(),
|
||||
type: text().notNull(),
|
||||
createdAt: int("created_at").notNull().default(sql`(current_timestamp)`),
|
||||
updatedAt: int("updated_at").notNull().default(sql`(current_timestamp)`),
|
||||
config: text("config", { mode: "json" })
|
||||
.$type<typeof volumeConfigSchema.inferOut>()
|
||||
.notNull(),
|
||||
createdAt: int("created_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`),
|
||||
updatedAt: int("updated_at", { mode: "timestamp" }).notNull().default(sql`(unixepoch())`),
|
||||
config: text("config", { mode: "json" }).$type<typeof volumeConfigSchema.inferOut>().notNull(),
|
||||
});
|
||||
|
||||
export type Volume = typeof volumesTable.$inferSelect;
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { Hono } from "hono";
|
||||
import { validator } from "hono-openapi/arktype";
|
||||
import { handleServiceError } from "../../utils/errors";
|
||||
import { createVolumeBody, createVolumeDto, type ListVolumesResponseDto, listVolumesDto } from "./volume.dto";
|
||||
import {
|
||||
createVolumeBody,
|
||||
createVolumeDto,
|
||||
deleteVolumeDto,
|
||||
type ListVolumesResponseDto,
|
||||
listVolumesDto,
|
||||
} from "./volume.dto";
|
||||
import { volumeService } from "./volume.service";
|
||||
|
||||
export const volumeController = new Hono()
|
||||
@@ -11,8 +17,8 @@ export const volumeController = new Hono()
|
||||
const response = {
|
||||
volumes: volumes.map((volume) => ({
|
||||
name: volume.name,
|
||||
mountpoint: volume.path,
|
||||
createdAt: volume.createdAt,
|
||||
path: volume.path,
|
||||
createdAt: volume.createdAt.getTime(),
|
||||
})),
|
||||
} satisfies ListVolumesResponseDto;
|
||||
|
||||
@@ -29,6 +35,17 @@ export const volumeController = new Hono()
|
||||
|
||||
return c.json({ message: "Volume created", volume: res.volume });
|
||||
})
|
||||
.delete("/:name", deleteVolumeDto, async (c) => {
|
||||
const { name } = c.req.param();
|
||||
const res = await volumeService.deleteVolume(name);
|
||||
|
||||
if (res.error) {
|
||||
const { message, status } = handleServiceError(res.error);
|
||||
return c.json(message, status);
|
||||
}
|
||||
|
||||
return c.json({ message: "Volume deleted" });
|
||||
})
|
||||
.get("/:name", (c) => {
|
||||
return c.json({ message: `Details of volume ${c.req.param("name")}` });
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ import { volumeConfigSchema } from "../../db/schema";
|
||||
export const listVolumesResponse = type({
|
||||
volumes: type({
|
||||
name: "string",
|
||||
mountpoint: "string",
|
||||
path: "string",
|
||||
createdAt: "number",
|
||||
}).array(),
|
||||
});
|
||||
@@ -41,9 +41,12 @@ export const createVolumeBody = type({
|
||||
});
|
||||
|
||||
export const createVolumeResponse = type({
|
||||
name: "string",
|
||||
mountpoint: "string",
|
||||
createdAt: "number",
|
||||
message: "string",
|
||||
volume: type({
|
||||
name: "string",
|
||||
path: "string",
|
||||
createdAt: "number",
|
||||
}),
|
||||
});
|
||||
|
||||
export const createVolumeDto = describeRoute({
|
||||
@@ -62,3 +65,27 @@ export const createVolumeDto = describeRoute({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete a volume
|
||||
*/
|
||||
export const deleteVolumeResponse = type({
|
||||
message: "string",
|
||||
});
|
||||
|
||||
export const deleteVolumeDto = describeRoute({
|
||||
description: "Delete a volume",
|
||||
operationId: "deleteVolume",
|
||||
validateResponse: true,
|
||||
tags: ["Volumes"],
|
||||
responses: {
|
||||
200: {
|
||||
description: "Volume deleted successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(deleteVolumeResponse),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -39,6 +39,29 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
|
||||
return { volume: val[0], status: 201 };
|
||||
};
|
||||
|
||||
const deleteVolume = async (name: string) => {
|
||||
try {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
where: eq(volumesTable.name, name),
|
||||
});
|
||||
|
||||
if (!volume) {
|
||||
return { error: new NotFoundError("Volume not found") };
|
||||
}
|
||||
|
||||
const backend = createVolumeBackend(volume);
|
||||
await backend.unmount();
|
||||
await db.delete(volumesTable).where(eq(volumesTable.name, name));
|
||||
return { status: 200 };
|
||||
} catch (error) {
|
||||
return {
|
||||
error: new InternalServerError("Failed to delete volume", {
|
||||
cause: error,
|
||||
}),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const mountVolume = async (name: string) => {
|
||||
try {
|
||||
const volume = await db.query.volumesTable.findFirst({
|
||||
@@ -64,4 +87,5 @@ export const volumeService = {
|
||||
listVolumes,
|
||||
createVolume,
|
||||
mountVolume,
|
||||
deleteVolume,
|
||||
};
|
||||
|
||||
6
bun.lock
6
bun.lock
@@ -27,10 +27,12 @@
|
||||
"clsx": "^2.1.1",
|
||||
"isbot": "^5.1.27",
|
||||
"lucide-react": "^0.539.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-router": "^7.7.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"zod": "^4.0.17",
|
||||
},
|
||||
@@ -723,6 +725,8 @@
|
||||
|
||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
||||
|
||||
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
||||
@@ -851,6 +855,8 @@
|
||||
|
||||
"slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="],
|
||||
|
||||
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
Reference in New Issue
Block a user