mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-24 00:29:07 -05:00
chore: remove unused code (#5697)
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
import "server-only";
|
||||
import { cache } from "@/lib/cache";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { actionClassCache } from "./cache";
|
||||
import { getActionClass } from "./service";
|
||||
|
||||
export const canUserUpdateActionClass = (userId: string, actionClassId: string): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [actionClassId, ZId]);
|
||||
|
||||
try {
|
||||
if (!userId) return false;
|
||||
|
||||
const actionClass = await getActionClass(actionClassId);
|
||||
if (!actionClass) return false;
|
||||
|
||||
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, actionClass.environmentId);
|
||||
|
||||
if (!hasAccessToEnvironment) return false;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
[`canUserUpdateActionClass-${userId}-${actionClassId}`],
|
||||
{
|
||||
tags: [actionClassCache.tag.byId(actionClassId)],
|
||||
}
|
||||
)();
|
||||
@@ -1,39 +0,0 @@
|
||||
export const fetchRessource = async (url: string) => {
|
||||
const res = await fetch(url);
|
||||
|
||||
// If the status code is not in the range 200-299,
|
||||
// we still try to parse and throw it.
|
||||
if (!res.ok) {
|
||||
const error: any = new Error("An error occurred while fetching the data.");
|
||||
// Attach extra info to the error object.
|
||||
error.info = await res.json();
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res.json();
|
||||
};
|
||||
|
||||
export const fetcher = async (url: string) => {
|
||||
const res = await fetch(url);
|
||||
|
||||
// If the status code is not in the range 200-299,
|
||||
// we still try to parse and throw it.
|
||||
if (!res.ok) {
|
||||
const error: any = new Error("An error occurred while fetching the data.");
|
||||
// Attach extra info to the error object.
|
||||
error.info = await res.json();
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res.json();
|
||||
};
|
||||
|
||||
export const updateRessource = async (url: string, { arg }: { arg: any }) => {
|
||||
return fetch(url, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(arg),
|
||||
});
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import "server-only";
|
||||
import { structuredClone } from "@/lib/pollyfills/structuredClone";
|
||||
import { TI18nString } from "@formbricks/types/surveys/types";
|
||||
import { isI18nObject } from "./utils";
|
||||
|
||||
// Helper function to extract a regular string from an i18nString.
|
||||
const extractStringFromI18n = (i18nString: TI18nString, languageCode: string): string => {
|
||||
if (typeof i18nString === "object" && i18nString !== null) {
|
||||
return i18nString[languageCode] || "";
|
||||
}
|
||||
return i18nString;
|
||||
};
|
||||
|
||||
// Assuming I18nString and extraction logic are defined
|
||||
const reverseTranslateObject = <T extends Record<string, any>>(obj: T, languageCode: string): T => {
|
||||
const clonedObj = structuredClone(obj);
|
||||
for (let key in clonedObj) {
|
||||
const value = clonedObj[key];
|
||||
if (isI18nObject(value)) {
|
||||
// Now TypeScript knows `value` is I18nString, treat it accordingly
|
||||
clonedObj[key] = extractStringFromI18n(value, languageCode) as T[Extract<keyof T, string>];
|
||||
} else if (typeof value === "object" && value !== null) {
|
||||
// Recursively handle nested objects
|
||||
clonedObj[key] = reverseTranslateObject(value, languageCode);
|
||||
}
|
||||
}
|
||||
return clonedObj;
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import "server-only";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { cache } from "../cache";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { getIntegration } from "./service";
|
||||
|
||||
export const canUserAccessIntegration = async (userId: string, integrationId: string): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [integrationId, ZId]);
|
||||
if (!userId) return false;
|
||||
|
||||
try {
|
||||
const integration = await getIntegration(integrationId);
|
||||
if (!integration) return false;
|
||||
|
||||
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, integration.environmentId);
|
||||
if (!hasAccessToEnvironment) return false;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
[`canUserAccessIntegration-${userId}-${integrationId}`],
|
||||
{
|
||||
tags: [`integrations-${integrationId}`],
|
||||
}
|
||||
)();
|
||||
@@ -1,36 +0,0 @@
|
||||
import "server-only";
|
||||
import { cache } from "@/lib/cache";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { getSurvey } from "../survey/service";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { responseCache } from "./cache";
|
||||
import { getResponse } from "./service";
|
||||
|
||||
export const canUserAccessResponse = (userId: string, responseId: string): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [responseId, ZId]);
|
||||
|
||||
if (!userId) return false;
|
||||
|
||||
try {
|
||||
const response = await getResponse(responseId);
|
||||
if (!response) return false;
|
||||
|
||||
const survey = await getSurvey(response.surveyId);
|
||||
if (!survey) return false;
|
||||
|
||||
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, survey.environmentId);
|
||||
if (!hasAccessToEnvironment) return false;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`canUserAccessResponse-${userId}-${responseId}`],
|
||||
{
|
||||
tags: [responseCache.tag.byId(responseId)],
|
||||
}
|
||||
)();
|
||||
@@ -1,66 +0,0 @@
|
||||
import { cache } from "@/lib/cache";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { canUserAccessResponse } from "../response/auth";
|
||||
import { getResponse } from "../response/service";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { responseNoteCache } from "./cache";
|
||||
import { getResponseNote } from "./service";
|
||||
|
||||
export const canUserModifyResponseNote = async (userId: string, responseNoteId: string): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [responseNoteId, ZId]);
|
||||
|
||||
if (!userId || !responseNoteId) return false;
|
||||
|
||||
try {
|
||||
const responseNote = await getResponseNote(responseNoteId);
|
||||
if (!responseNote) return false;
|
||||
|
||||
return responseNote.user.id === userId;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`canUserModifyResponseNote-${userId}-${responseNoteId}`],
|
||||
{
|
||||
tags: [responseNoteCache.tag.byId(responseNoteId)],
|
||||
}
|
||||
)();
|
||||
|
||||
export const canUserResolveResponseNote = async (
|
||||
userId: string,
|
||||
responseId: string,
|
||||
responseNoteId: string
|
||||
): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [responseNoteId, ZId]);
|
||||
|
||||
if (!userId || !responseId || !responseNoteId) return false;
|
||||
|
||||
try {
|
||||
const response = await getResponse(responseId);
|
||||
|
||||
let noteExistsOnResponse = false;
|
||||
|
||||
response?.notes.forEach((note) => {
|
||||
if (note.id === responseNoteId) {
|
||||
noteExistsOnResponse = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!noteExistsOnResponse) return false;
|
||||
|
||||
const canAccessResponse = await canUserAccessResponse(userId, responseId);
|
||||
|
||||
return canAccessResponse;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`canUserResolveResponseNote-${userId}-${responseNoteId}`],
|
||||
{
|
||||
tags: [responseNoteCache.tag.byId(responseNoteId)],
|
||||
}
|
||||
)();
|
||||
@@ -1,113 +0,0 @@
|
||||
import { cache } from "@/lib/cache";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { canUserAccessSurvey } from "./auth";
|
||||
import { surveyCache } from "./cache";
|
||||
import { getSurvey } from "./service";
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@/lib/cache", () => ({
|
||||
cache: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/utils/validate", () => ({
|
||||
validateInputs: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./service", () => ({
|
||||
getSurvey: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../environment/auth", () => ({
|
||||
hasUserEnvironmentAccess: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./cache", () => ({
|
||||
surveyCache: {
|
||||
tag: {
|
||||
byId: vi.fn().mockReturnValue("survey-tag-id"),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("canUserAccessSurvey", () => {
|
||||
const userId = "user-123";
|
||||
const surveyId = "survey-456";
|
||||
const environmentId = "env-789";
|
||||
|
||||
const mockSurvey = {
|
||||
id: surveyId,
|
||||
environmentId: environmentId,
|
||||
} as TSurvey;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(cache).mockImplementation((fn) => () => fn());
|
||||
vi.mocked(getSurvey).mockResolvedValue(mockSurvey);
|
||||
vi.mocked(hasUserEnvironmentAccess).mockResolvedValue(true);
|
||||
vi.mocked(surveyCache.tag.byId).mockReturnValue(`survey-${surveyId}`);
|
||||
});
|
||||
|
||||
test("validates input parameters", async () => {
|
||||
await canUserAccessSurvey(userId, surveyId);
|
||||
expect(validateInputs).toHaveBeenCalledWith([surveyId, ZId], [userId, ZId]);
|
||||
});
|
||||
|
||||
test("returns false if userId is falsy", async () => {
|
||||
const result = await canUserAccessSurvey("", surveyId);
|
||||
expect(result).toBe(false);
|
||||
expect(getSurvey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns false if survey is not found", async () => {
|
||||
vi.mocked(getSurvey).mockResolvedValueOnce(null);
|
||||
|
||||
await expect(canUserAccessSurvey(userId, surveyId)).rejects.toThrowError("Survey not found");
|
||||
expect(getSurvey).toHaveBeenCalledWith(surveyId);
|
||||
expect(hasUserEnvironmentAccess).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("calls hasUserEnvironmentAccess with userId and survey's environmentId", async () => {
|
||||
await canUserAccessSurvey(userId, surveyId);
|
||||
|
||||
expect(getSurvey).toHaveBeenCalledWith(surveyId);
|
||||
expect(hasUserEnvironmentAccess).toHaveBeenCalledWith(userId, environmentId);
|
||||
});
|
||||
|
||||
test("returns false if user doesn't have access to the environment", async () => {
|
||||
vi.mocked(hasUserEnvironmentAccess).mockResolvedValueOnce(false);
|
||||
|
||||
const result = await canUserAccessSurvey(userId, surveyId);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(getSurvey).toHaveBeenCalledWith(surveyId);
|
||||
expect(hasUserEnvironmentAccess).toHaveBeenCalledWith(userId, environmentId);
|
||||
});
|
||||
|
||||
test("returns true if user has access to the environment", async () => {
|
||||
const result = await canUserAccessSurvey(userId, surveyId);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(getSurvey).toHaveBeenCalledWith(surveyId);
|
||||
expect(hasUserEnvironmentAccess).toHaveBeenCalledWith(userId, environmentId);
|
||||
});
|
||||
|
||||
test("rethrows errors that occur during execution", async () => {
|
||||
const error = new Error("Test error");
|
||||
vi.mocked(getSurvey).mockRejectedValueOnce(error);
|
||||
|
||||
await expect(canUserAccessSurvey(userId, surveyId)).rejects.toThrow(error);
|
||||
});
|
||||
|
||||
test("uses cache with correct cache key and tags", async () => {
|
||||
await canUserAccessSurvey(userId, surveyId);
|
||||
|
||||
expect(cache).toHaveBeenCalledWith(expect.any(Function), [`canUserAccessSurvey-${userId}-${surveyId}`], {
|
||||
tags: [`survey-${surveyId}`],
|
||||
});
|
||||
expect(surveyCache.tag.byId).toHaveBeenCalledWith(surveyId);
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
import { cache } from "@/lib/cache";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { surveyCache } from "./cache";
|
||||
import { getSurvey } from "./service";
|
||||
|
||||
export const canUserAccessSurvey = (userId: string, surveyId: string): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([surveyId, ZId], [userId, ZId]);
|
||||
|
||||
if (!userId) return false;
|
||||
|
||||
try {
|
||||
const survey = await getSurvey(surveyId);
|
||||
if (!survey) throw new Error("Survey not found");
|
||||
|
||||
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, survey.environmentId);
|
||||
if (!hasAccessToEnvironment) return false;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`canUserAccessSurvey-${userId}-${surveyId}`],
|
||||
{
|
||||
tags: [surveyCache.tag.byId(surveyId)],
|
||||
}
|
||||
)();
|
||||
@@ -1,21 +0,0 @@
|
||||
import "server-only";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { hasUserEnvironmentAccess } from "../environment/auth";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { getTag } from "./service";
|
||||
|
||||
export const canUserAccessTag = async (userId: string, tagId: string): Promise<boolean> => {
|
||||
validateInputs([userId, ZId], [tagId, ZId]);
|
||||
|
||||
try {
|
||||
const tag = await getTag(tagId);
|
||||
if (!tag) return false;
|
||||
|
||||
const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, tag.environmentId);
|
||||
if (!hasAccessToEnvironment) return false;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import "server-only";
|
||||
import { cache } from "@/lib/cache";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { canUserAccessResponse } from "../response/auth";
|
||||
import { canUserAccessTag } from "../tag/auth";
|
||||
import { validateInputs } from "../utils/validate";
|
||||
import { tagOnResponseCache } from "./cache";
|
||||
|
||||
export const canUserAccessTagOnResponse = (
|
||||
userId: string,
|
||||
tagId: string,
|
||||
responseId: string
|
||||
): Promise<boolean> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, ZId], [tagId, ZId], [responseId, ZId]);
|
||||
|
||||
try {
|
||||
const isAuthorizedForTag = await canUserAccessTag(userId, tagId);
|
||||
const isAuthorizedForResponse = await canUserAccessResponse(userId, responseId);
|
||||
|
||||
return isAuthorizedForTag && isAuthorizedForResponse;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`canUserAccessTagOnResponse-${userId}-${tagId}-${responseId}`],
|
||||
{
|
||||
tags: [tagOnResponseCache.tag.byResponseIdAndTagId(responseId, tagId)],
|
||||
}
|
||||
)();
|
||||
@@ -1,43 +0,0 @@
|
||||
import { cache } from "@/lib/cache";
|
||||
import { environmentCache } from "@/lib/environment/cache";
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { Environment, Prisma } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError } from "@formbricks/types/errors";
|
||||
|
||||
export const getEnvironment = reactCache(
|
||||
async (environmentId: string): Promise<Pick<Environment, "id" | "appSetupCompleted"> | null> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([environmentId, z.string().cuid2()]);
|
||||
|
||||
try {
|
||||
const environment = await prisma.environment.findUnique({
|
||||
where: {
|
||||
id: environmentId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
appSetupCompleted: true,
|
||||
},
|
||||
});
|
||||
|
||||
return environment;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
logger.error(error, "Error fetching environment");
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[`survey-lib-getEnvironment-${environmentId}`],
|
||||
{
|
||||
tags: [environmentCache.tag.byId(environmentId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -1,46 +0,0 @@
|
||||
import { cache } from "@/lib/cache";
|
||||
import { membershipCache } from "@/lib/membership/cache";
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { OrganizationRole, Prisma } from "@prisma/client";
|
||||
import { cache as reactCache } from "react";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { AuthorizationError, DatabaseError, UnknownError } from "@formbricks/types/errors";
|
||||
|
||||
export const getMembershipRoleByUserIdOrganizationId = reactCache(
|
||||
async (userId: string, organizationId: string): Promise<OrganizationRole> =>
|
||||
cache(
|
||||
async () => {
|
||||
validateInputs([userId, z.string()], [organizationId, z.string().cuid2()]);
|
||||
|
||||
try {
|
||||
const membership = await prisma.membership.findUnique({
|
||||
where: {
|
||||
userId_organizationId: {
|
||||
userId,
|
||||
organizationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
throw new AuthorizationError("You are not a member of this organization");
|
||||
}
|
||||
|
||||
return membership.role;
|
||||
} catch (error) {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
logger.error(error, "Error fetching membership role by user id and organization id");
|
||||
throw new DatabaseError(error.message);
|
||||
}
|
||||
|
||||
throw new UnknownError("Error while fetching membership");
|
||||
}
|
||||
},
|
||||
[`survey-getMembershipRoleByUserIdOrganizationId-${userId}-${organizationId}`],
|
||||
{
|
||||
tags: [membershipCache.tag.byUserId(userId), membershipCache.tag.byOrganizationId(organizationId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -1,40 +0,0 @@
|
||||
import { cache } from "@/lib/cache";
|
||||
import { environmentCache } from "@/lib/environment/cache";
|
||||
import { cache as reactCache } from "react";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
|
||||
export const getOrganizationIdByEnvironmentId = reactCache(
|
||||
async (environmentId: string): Promise<string | null> =>
|
||||
cache(
|
||||
async () => {
|
||||
const organization = await prisma.organization.findFirst({
|
||||
where: {
|
||||
projects: {
|
||||
some: {
|
||||
environments: {
|
||||
some: {
|
||||
id: environmentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!organization) {
|
||||
throw new ResourceNotFoundError("Organization", null);
|
||||
}
|
||||
|
||||
return organization.id;
|
||||
},
|
||||
|
||||
[`survey-list-getOrganizationIdByEnvironmentId-${environmentId}`],
|
||||
{
|
||||
tags: [environmentCache.tag.byId(environmentId)],
|
||||
}
|
||||
)()
|
||||
);
|
||||
@@ -1,122 +0,0 @@
|
||||
import { cn } from "@/lib/cn";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
const ZBadgeSelectOptionSchema = z.object({
|
||||
text: z.string(),
|
||||
type: z.enum(["warning", "success", "error", "gray"]),
|
||||
});
|
||||
|
||||
const ZBadgeSelectPropsSchema = z.object({
|
||||
text: z.string().optional(),
|
||||
type: z.enum(["warning", "success", "error", "gray"]).optional(),
|
||||
options: z.array(ZBadgeSelectOptionSchema).optional(),
|
||||
selectedIndex: z.number().optional(),
|
||||
onChange: z.function().args(z.number()).returns(z.void()).optional(),
|
||||
size: z.enum(["tiny", "normal", "large"]),
|
||||
className: z.string().optional(),
|
||||
isLoading: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type TBadgeSelectOption = z.infer<typeof ZBadgeSelectOptionSchema>;
|
||||
export type TBadgeSelectProps = z.infer<typeof ZBadgeSelectPropsSchema>;
|
||||
|
||||
export const BadgeSelect: React.FC<TBadgeSelectProps & { isLoading?: boolean }> = ({
|
||||
text,
|
||||
type,
|
||||
options,
|
||||
selectedIndex = 0,
|
||||
onChange,
|
||||
size,
|
||||
className,
|
||||
isLoading = false,
|
||||
}) => {
|
||||
const bgColor = {
|
||||
warning: "bg-amber-100",
|
||||
success: "bg-emerald-100",
|
||||
error: "bg-red-100",
|
||||
gray: "bg-slate-100",
|
||||
};
|
||||
|
||||
const borderColor = {
|
||||
warning: "border-amber-200",
|
||||
success: "border-emerald-200",
|
||||
error: "border-red-200",
|
||||
gray: "border-slate-200",
|
||||
};
|
||||
|
||||
const textColor = {
|
||||
warning: "text-amber-800",
|
||||
success: "text-emerald-800",
|
||||
error: "text-red-800",
|
||||
gray: "text-slate-600",
|
||||
};
|
||||
|
||||
const padding = {
|
||||
tiny: "px-1.5 py-0.5",
|
||||
normal: "px-2.5 py-0.5",
|
||||
large: "px-3.5 py-1",
|
||||
};
|
||||
|
||||
const textSize = size === "large" ? "text-sm" : "text-xs";
|
||||
|
||||
const currentOption = options ? options[selectedIndex] : { text, type: type || "gray" };
|
||||
|
||||
const renderContent = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<span className="animate-pulse" aria-busy="true">
|
||||
<span className={cn("inline-block h-2 w-8 rounded-full bg-black/10")}></span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{currentOption.text}
|
||||
{options && <ChevronDownIcon className="ml-1 h-3 w-3" aria-hidden="true" />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<span
|
||||
className={cn(
|
||||
"border-opacity-50 inline-flex items-center rounded-full border font-medium",
|
||||
options && !isLoading ? "hover:border-opacity-100 cursor-pointer" : "pointer-events-none",
|
||||
bgColor[currentOption.type],
|
||||
borderColor[currentOption.type],
|
||||
textColor[currentOption.type],
|
||||
padding[size],
|
||||
textSize,
|
||||
className
|
||||
)}>
|
||||
{renderContent()}
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
{options && (
|
||||
<DropdownMenuContent className="mt-1 bg-white shadow-lg">
|
||||
{options.map((option, index) => (
|
||||
<DropdownMenuItem
|
||||
key={index}
|
||||
className={cn("cursor-pointer px-4 py-2 hover:bg-slate-100", textSize)}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onChange?.(index);
|
||||
}}>
|
||||
{option.text}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { BadgeSelect } from "./index";
|
||||
|
||||
const meta = {
|
||||
title: "ui/BadgeSelect",
|
||||
component: BadgeSelect,
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
argTypes: {
|
||||
type: {
|
||||
control: "select",
|
||||
options: ["warning", "success", "error", "gray"],
|
||||
},
|
||||
size: { control: "select", options: ["small", "normal", "large"] },
|
||||
className: { control: "text" },
|
||||
},
|
||||
} satisfies Meta<typeof BadgeSelect>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Warning: Story = {
|
||||
args: {
|
||||
text: "Warning",
|
||||
type: "warning",
|
||||
size: "normal",
|
||||
},
|
||||
};
|
||||
|
||||
export const Success: Story = {
|
||||
args: {
|
||||
text: "Success",
|
||||
type: "success",
|
||||
size: "normal",
|
||||
},
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
args: {
|
||||
text: "Error",
|
||||
type: "error",
|
||||
size: "normal",
|
||||
},
|
||||
};
|
||||
|
||||
export const Gray: Story = {
|
||||
args: {
|
||||
text: "Gray",
|
||||
type: "gray",
|
||||
size: "normal",
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeWarning: Story = {
|
||||
args: {
|
||||
text: "Warning",
|
||||
type: "warning",
|
||||
size: "large",
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeSuccess: Story = {
|
||||
args: {
|
||||
text: "Success",
|
||||
type: "success",
|
||||
size: "large",
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeError: Story = {
|
||||
args: {
|
||||
text: "Error",
|
||||
type: "error",
|
||||
size: "large",
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeGray: Story = {
|
||||
args: {
|
||||
text: "Gray",
|
||||
type: "gray",
|
||||
size: "large",
|
||||
},
|
||||
};
|
||||
|
||||
export const TinyWarning: Story = {
|
||||
args: {
|
||||
text: "Warning",
|
||||
type: "warning",
|
||||
size: "tiny",
|
||||
},
|
||||
};
|
||||
|
||||
export const TinySuccess: Story = {
|
||||
args: {
|
||||
text: "Success",
|
||||
type: "success",
|
||||
size: "tiny",
|
||||
},
|
||||
};
|
||||
|
||||
export const TinyError: Story = {
|
||||
args: {
|
||||
text: "Error",
|
||||
type: "error",
|
||||
size: "tiny",
|
||||
},
|
||||
};
|
||||
|
||||
export const TinyGray: Story = {
|
||||
args: {
|
||||
text: "Gray",
|
||||
type: "gray",
|
||||
size: "tiny",
|
||||
},
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
import { cn } from "@/modules/ui/lib/utils";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
separator?: React.ReactNode;
|
||||
}
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
|
||||
Breadcrumb.displayName = "Breadcrumb";
|
||||
|
||||
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<"ol">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<ol
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-wrap items-center gap-1.5 break-words text-sm text-slate-500 sm:gap-2.5 dark:text-slate-400",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
BreadcrumbList.displayName = "BreadcrumbList";
|
||||
|
||||
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
|
||||
)
|
||||
);
|
||||
BreadcrumbItem.displayName = "BreadcrumbItem";
|
||||
|
||||
const BreadcrumbLink = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.ComponentPropsWithoutRef<"a"> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(({ asChild, className, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "a";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn("transition-colors hover:text-slate-950 dark:hover:text-slate-50", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
BreadcrumbLink.displayName = "BreadcrumbLink";
|
||||
|
||||
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("font-normal text-slate-950 dark:text-slate-50", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
BreadcrumbPage.displayName = "BreadcrumbPage";
|
||||
|
||||
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:h-3.5 [&>svg]:w-3.5", className)}
|
||||
{...props}>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
);
|
||||
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
|
||||
|
||||
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
);
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
export const Pagination = ({
|
||||
baseUrl,
|
||||
currentPage,
|
||||
totalItems,
|
||||
itemsPerPage,
|
||||
}: {
|
||||
baseUrl: string;
|
||||
currentPage: number;
|
||||
totalItems: number;
|
||||
itemsPerPage: number;
|
||||
}) => {
|
||||
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
||||
|
||||
const previousPageLink = currentPage === 1 ? "#" : `${baseUrl}?page=${currentPage - 1}`;
|
||||
const nextPageLink = currentPage === totalPages ? "#" : `${baseUrl}?page=${currentPage + 1}`;
|
||||
|
||||
const getDisplayedPages = () => {
|
||||
if (totalPages <= 20) {
|
||||
return Array.from({ length: totalPages }, (_, idx) => idx + 1);
|
||||
} else {
|
||||
let range = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2];
|
||||
return [1, ...range.filter((n) => n > 1 && n < totalPages), totalPages];
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<nav aria-label="Page navigation" className="flex justify-center">
|
||||
<ul className="mt-4 inline-flex -space-x-px text-sm">
|
||||
<li>
|
||||
<a
|
||||
href={previousPageLink}
|
||||
className={`ml-0 flex h-8 items-center justify-center rounded-l-lg border border-slate-300 bg-white px-3 text-slate-500 ${
|
||||
currentPage === 1
|
||||
? "cursor-not-allowed opacity-50"
|
||||
: "hover:bg-slate-100 hover:text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-white"
|
||||
}`}>
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{getDisplayedPages().map((pageNum) => {
|
||||
const pageLink = `${baseUrl}?page=${pageNum}`;
|
||||
return (
|
||||
<li key={pageNum} className="hidden sm:block">
|
||||
<a
|
||||
href={pageNum === currentPage ? "#" : pageLink}
|
||||
className={`flex h-8 items-center justify-center px-3 ${
|
||||
pageNum === currentPage ? "bg-blue-50 text-green-500" : "bg-white text-slate-500"
|
||||
} border border-slate-300 hover:bg-slate-100 hover:text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-white`}>
|
||||
{pageNum}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
<li>
|
||||
<a
|
||||
href={nextPageLink}
|
||||
className={`ml-0 flex h-8 items-center justify-center rounded-r-lg border border-slate-300 bg-white px-3 text-slate-500 ${
|
||||
currentPage === totalPages
|
||||
? "cursor-not-allowed opacity-50"
|
||||
: "hover:bg-slate-100 hover:text-slate-700 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-400 dark:hover:bg-slate-700 dark:hover:text-white"
|
||||
}`}>
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
interface ResponsiveVideoProps {
|
||||
src: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const ResponsiveVideo: React.FC<ResponsiveVideoProps> = ({ src, title }: ResponsiveVideoProps) => {
|
||||
return (
|
||||
<div className="relative" style={{ paddingTop: "56.25%" }}>
|
||||
<iframe
|
||||
className="absolute left-0 top-0 h-full w-full rounded"
|
||||
src={src}
|
||||
title={title}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen></iframe>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/modules/ui/lib/utils";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
import * as React from "react";
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-slate-200 dark:bg-slate-800",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
|
||||
export { Separator };
|
||||
@@ -1,119 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/cn";
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
const Sheet = SheetPrimitive.Root;
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger;
|
||||
|
||||
const SheetClose = SheetPrimitive.Close;
|
||||
|
||||
const SheetPortal = SheetPrimitive.Portal;
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
));
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||
bottom:
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-md",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-md",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: "right",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
interface SheetContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, SheetContentProps>(
|
||||
({ side = "right", className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
)
|
||||
);
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
||||
|
||||
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
||||
);
|
||||
SheetHeader.displayName = "SheetHeader";
|
||||
|
||||
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
SheetFooter.displayName = "SheetFooter";
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-foreground flex items-center text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName;
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetOverlay,
|
||||
SheetPortal,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
interface SimpleLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SimpleLayout = ({ children }: SimpleLayoutProps) => {
|
||||
return (
|
||||
<div className="max-w-8xl flex">
|
||||
<div className="w-full">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/cn";
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
import * as React from "react";
|
||||
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-lg bg-slate-100 p-1 text-slate-500 dark:bg-slate-800 dark:text-slate-400",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center rounded-lg px-3 py-1.5 text-sm font-medium whitespace-nowrap ring-offset-white transition-all focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-950 data-[state=active]:shadow-sm dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300 dark:data-[state=active]:bg-slate-950 dark:data-[state=active]:text-slate-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-white focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 focus-visible:outline-none dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea };
|
||||
@@ -1,52 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/cn";
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
import { type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import { toggleVariants } from "./toggle";
|
||||
|
||||
const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
});
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> & VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, children, ...props }, ref) => (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("flex items-center justify-center gap-1", className)}
|
||||
{...props}>
|
||||
<ToggleGroupContext.Provider value={{ variant, size }}>{children}</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
));
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & VariantProps<typeof toggleVariants>
|
||||
>(({ className, children, variant, size, ...props }, ref) => {
|
||||
const context = React.useContext(ToggleGroupContext);
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
toggleVariants({
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
@@ -1,39 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/cn";
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-white transition-colors hover:bg-slate-200 hover:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-slate-200 data-[state=on]:text-slate-900 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-400 dark:focus-visible:ring-slate-300 dark:data-[state=on]:bg-slate-800 dark:data-[state=on]:text-slate-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-slate-300 bg-transparent hover:bg-slate-200 hover:text-slate-900 dark:border-slate-800 dark:hover:bg-slate-800 dark:hover:text-slate-50",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3",
|
||||
sm: "h-9 px-2.5",
|
||||
lg: "h-11 px-5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root ref={ref} className={cn(toggleVariants({ variant, size, className }))} {...props} />
|
||||
));
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName;
|
||||
|
||||
export { Toggle, toggleVariants };
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
|
||||
import { KeyIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
interface UpgradePlanNoticeProps {
|
||||
message: string;
|
||||
url: string;
|
||||
textForUrl: string;
|
||||
}
|
||||
|
||||
export const UpgradePlanNotice = ({ message, url, textForUrl }: UpgradePlanNoticeProps) => {
|
||||
return (
|
||||
<Alert className="flex gap-2 bg-slate-50 p-2 [&:has(svg)]:pl-3">
|
||||
<div className="flex h-5 w-5 items-center justify-center rounded-sm border border-slate-200 bg-white">
|
||||
<KeyIcon className="h-3 w-3 text-slate-900" />
|
||||
</div>
|
||||
<AlertDescription>
|
||||
<span className="mr-1 text-slate-600">{message}</span>
|
||||
<span className="underline">
|
||||
<Link href={url} target="_blank">
|
||||
{textForUrl}
|
||||
</Link>
|
||||
</span>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
const REDIRECT_TIMEOUT = 5;
|
||||
|
||||
interface RedirectCountDownProps {
|
||||
redirectUrl: string | null;
|
||||
isRedirectDisabled: boolean;
|
||||
}
|
||||
|
||||
export function RedirectCountDown({ redirectUrl, isRedirectDisabled }: RedirectCountDownProps) {
|
||||
const [timeRemaining, setTimeRemaining] = useState(REDIRECT_TIMEOUT);
|
||||
|
||||
useEffect(() => {
|
||||
let interval: NodeJS.Timeout | undefined;
|
||||
if (redirectUrl) {
|
||||
interval = setInterval(() => {
|
||||
setTimeRemaining((prevTime) => {
|
||||
if (prevTime <= 0) {
|
||||
clearInterval(interval);
|
||||
if (!isRedirectDisabled) {
|
||||
window.top?.location.replace(redirectUrl);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return prevTime - 1;
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Clean up the interval when the component is unmounted
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [redirectUrl, isRedirectDisabled]);
|
||||
|
||||
if (!redirectUrl) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="fb-bg-accent-bg fb-text-subheading fb-mt-10 fb-rounded-md fb-p-2 fb-text-sm">
|
||||
<span>You're redirected in </span>
|
||||
<span>{timeRemaining}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user