mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 15:26:06 +00:00
feat: finished up wish integration
This commit is contained in:
@@ -67,3 +67,6 @@ CLIENT_SECRET_GITLAB_LOGIN=
|
|||||||
CAPTCHA_SECRET=
|
CAPTCHA_SECRET=
|
||||||
|
|
||||||
NEXT_PUBLIC_CAPTCHA_SITE_KEY=
|
NEXT_PUBLIC_CAPTCHA_SITE_KEY=
|
||||||
|
|
||||||
|
PLAIN_API_KEY=
|
||||||
|
PLAIN_WISH_LABEL_IDS=
|
||||||
|
|||||||
Generated
+29
@@ -30,6 +30,7 @@
|
|||||||
"@peculiar/x509": "^1.10.0",
|
"@peculiar/x509": "^1.10.0",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
|
"@team-plain/typescript-sdk": "^4.6.1",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
@@ -3913,6 +3914,14 @@
|
|||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@graphql-typed-document-node/core": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@hapi/bourne": {
|
"node_modules/@hapi/bourne": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.1.0.tgz",
|
||||||
@@ -6198,6 +6207,18 @@
|
|||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@team-plain/typescript-sdk": {
|
||||||
|
"version": "4.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@team-plain/typescript-sdk/-/typescript-sdk-4.6.1.tgz",
|
||||||
|
"integrity": "sha512-Uy9QJXu9U7bJb6WXL9sArGk7FXPpzdqBd6q8tAF1vexTm8fbTJRqcikTKxGtZmNADt+C2SapH3cApM4oHpO4lQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@graphql-typed-document-node/core": "^3.2.0",
|
||||||
|
"ajv": "^8.12.0",
|
||||||
|
"ajv-formats": "^2.1.1",
|
||||||
|
"graphql": "^16.6.0",
|
||||||
|
"zod": "3.22.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||||
@@ -10314,6 +10335,14 @@
|
|||||||
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/graphql": {
|
||||||
|
"version": "16.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz",
|
||||||
|
"integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gtoken": {
|
"node_modules/gtoken": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
"@peculiar/x509": "^1.10.0",
|
"@peculiar/x509": "^1.10.0",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
|
"@team-plain/typescript-sdk": "^4.6.1",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
|
|||||||
Vendored
+2
@@ -65,6 +65,7 @@ import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin
|
|||||||
import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
import { TUserServiceFactory } from "@app/services/user/user-service";
|
import { TUserServiceFactory } from "@app/services/user/user-service";
|
||||||
|
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
@@ -157,6 +158,7 @@ declare module "fastify" {
|
|||||||
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
||||||
secretSharing: TSecretSharingServiceFactory;
|
secretSharing: TSecretSharingServiceFactory;
|
||||||
rateLimit: TRateLimitServiceFactory;
|
rateLimit: TRateLimitServiceFactory;
|
||||||
|
userEngagement: TUserEngagementServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
|||||||
@@ -135,7 +135,9 @@ const envSchema = z
|
|||||||
.optional(),
|
.optional(),
|
||||||
INFISICAL_CLOUD: zodStrBool.default("false"),
|
INFISICAL_CLOUD: zodStrBool.default("false"),
|
||||||
MAINTENANCE_MODE: zodStrBool.default("false"),
|
MAINTENANCE_MODE: zodStrBool.default("false"),
|
||||||
CAPTCHA_SECRET: zpStr(z.string().optional())
|
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
||||||
|
PLAIN_API_KEY: zpStr(z.string().optional()),
|
||||||
|
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional())
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
|||||||
@@ -82,3 +82,9 @@ export const publicSecretShareCreationLimit: RateLimitOptions = {
|
|||||||
max: 5,
|
max: 5,
|
||||||
keyGenerator: (req) => req.realIp
|
keyGenerator: (req) => req.realIp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const userEngagementLimit: RateLimitOptions = {
|
||||||
|
timeWindow: 60 * 1000,
|
||||||
|
max: 5,
|
||||||
|
keyGenerator: (req) => req.realIp
|
||||||
|
};
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-servi
|
|||||||
import { userDALFactory } from "@app/services/user/user-dal";
|
import { userDALFactory } from "@app/services/user/user-dal";
|
||||||
import { userServiceFactory } from "@app/services/user/user-service";
|
import { userServiceFactory } from "@app/services/user/user-service";
|
||||||
import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
|
import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
||||||
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
|
||||||
@@ -924,6 +925,10 @@ export const registerRoutes = async (
|
|||||||
oidcConfigDAL
|
oidcConfigDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userEngagementService = userEngagementServiceFactory({
|
||||||
|
userDAL
|
||||||
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
//
|
//
|
||||||
// setup the communication with license key server
|
// setup the communication with license key server
|
||||||
@@ -995,7 +1000,8 @@ export const registerRoutes = async (
|
|||||||
telemetry: telemetryService,
|
telemetry: telemetryService,
|
||||||
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
|
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
|
||||||
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
|
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
|
||||||
secretSharing: secretSharingService
|
secretSharing: secretSharingService,
|
||||||
|
userEngagement: userEngagementService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { registerSecretSharingRouter } from "./secret-sharing-router";
|
|||||||
import { registerSecretTagRouter } from "./secret-tag-router";
|
import { registerSecretTagRouter } from "./secret-tag-router";
|
||||||
import { registerSsoRouter } from "./sso-router";
|
import { registerSsoRouter } from "./sso-router";
|
||||||
import { registerUserActionRouter } from "./user-action-router";
|
import { registerUserActionRouter } from "./user-action-router";
|
||||||
|
import { registerUserEngagementRouter } from "./user-engagement-router";
|
||||||
import { registerUserRouter } from "./user-router";
|
import { registerUserRouter } from "./user-router";
|
||||||
import { registerWebhookRouter } from "./webhook-router";
|
import { registerWebhookRouter } from "./webhook-router";
|
||||||
|
|
||||||
@@ -77,4 +78,5 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerWebhookRouter, { prefix: "/webhooks" });
|
await server.register(registerWebhookRouter, { prefix: "/webhooks" });
|
||||||
await server.register(registerIdentityRouter, { prefix: "/identities" });
|
await server.register(registerIdentityRouter, { prefix: "/identities" });
|
||||||
await server.register(registerSecretSharingRouter, { prefix: "/secret-sharing" });
|
await server.register(registerSecretSharingRouter, { prefix: "/secret-sharing" });
|
||||||
|
await server.register(registerUserEngagementRouter, { prefix: "/user-engagement" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { userEngagementLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerUserEngagementRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/me/wish",
|
||||||
|
config: {
|
||||||
|
rateLimit: userEngagementLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
text: z.string().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
return server.services.userEngagement.createUserWish(req.permission.id, req.body.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { PlainClient } from "@team-plain/typescript-sdk";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { InternalServerError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
|
|
||||||
|
type TUserEngagementServiceFactoryDep = {
|
||||||
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUserEngagementServiceFactory = ReturnType<typeof userEngagementServiceFactory>;
|
||||||
|
|
||||||
|
export const userEngagementServiceFactory = ({ userDAL }: TUserEngagementServiceFactoryDep) => {
|
||||||
|
const createUserWish = async (userId: string, text: string) => {
|
||||||
|
const user = await userDAL.findById(userId);
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
if (!appCfg.PLAIN_API_KEY) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: "Plain is not configured."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new PlainClient({
|
||||||
|
apiKey: appCfg.PLAIN_API_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
const customerUpsertRes = await client.upsertCustomer({
|
||||||
|
identifier: {
|
||||||
|
emailAddress: user.email
|
||||||
|
},
|
||||||
|
onCreate: {
|
||||||
|
fullName: `${user.firstName} ${user.lastName}`,
|
||||||
|
shortName: user.firstName,
|
||||||
|
email: {
|
||||||
|
email: user.email as string,
|
||||||
|
isVerified: user.isEmailVerified as boolean
|
||||||
|
},
|
||||||
|
|
||||||
|
externalId: user.id
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdate: {
|
||||||
|
fullName: {
|
||||||
|
value: `${user.firstName} ${user.lastName}`
|
||||||
|
},
|
||||||
|
shortName: {
|
||||||
|
value: user.firstName
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
email: user.email as string,
|
||||||
|
isVerified: user.isEmailVerified as boolean
|
||||||
|
},
|
||||||
|
externalId: {
|
||||||
|
value: user.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (customerUpsertRes.error) {
|
||||||
|
throw new InternalServerError({ message: customerUpsertRes.error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
const createThreadRes = await client.createThread({
|
||||||
|
title: "Wish",
|
||||||
|
customerIdentifier: {
|
||||||
|
externalId: customerUpsertRes.data.customer.externalId
|
||||||
|
},
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
componentText: {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
labelTypeIds: appCfg.PLAIN_WISH_LABEL_IDS?.split(",")
|
||||||
|
});
|
||||||
|
|
||||||
|
if (createThreadRes.error) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: createThreadRes.error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
createUserWish
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { useCreateUserWish } from "./mutations";
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { TCreateUserWishDto } from "./types";
|
||||||
|
|
||||||
|
export const useCreateUserWish = () => {
|
||||||
|
return useMutation<{}, {}, TCreateUserWishDto>({
|
||||||
|
mutationFn: async (dto) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/user-engagement/me/wish", dto);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type TCreateUserWishDto = {
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
@@ -10,7 +10,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faGithub, faRocketchat, faSlack } from "@fortawesome/free-brands-svg-icons";
|
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { faStar } from "@fortawesome/free-regular-svg-icons";
|
import { faStar } from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
faAngleDown,
|
faAngleDown,
|
||||||
@@ -47,12 +47,8 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
ModalContent,
|
ModalContent,
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
TextArea,
|
|
||||||
UpgradePlanModal
|
UpgradePlanModal
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { UpgradeOverlay } from "@app/components/v2/UpgradeOverlay";
|
import { UpgradeOverlay } from "@app/components/v2/UpgradeOverlay";
|
||||||
@@ -81,6 +77,8 @@ import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
|
|||||||
import { navigateUserToOrg } from "@app/views/Login/Login.utils";
|
import { navigateUserToOrg } from "@app/views/Login/Login.utils";
|
||||||
import { CreateOrgModal } from "@app/views/Org/components";
|
import { CreateOrgModal } from "@app/views/Org/components";
|
||||||
|
|
||||||
|
import { WishForm } from "./components/WishForm/WishForm";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
@@ -764,35 +762,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/> */}
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/> */}
|
||||||
<Popover>
|
<WishForm />
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<div className="mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
|
||||||
<FontAwesomeIcon icon={faRocketchat} className="mr-3" />
|
|
||||||
Make a wish
|
|
||||||
</div>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent
|
|
||||||
hideCloseBtn
|
|
||||||
className="ml-2 w-auto border border-mineshaft-600 bg-mineshaft-800 p-3 drop-shadow-2xl"
|
|
||||||
sticky="always"
|
|
||||||
>
|
|
||||||
<FormControl className="mb-0">
|
|
||||||
<TextArea
|
|
||||||
className="border border-mineshaft-600 text-sm"
|
|
||||||
placeholder="Wish for anything! Help us improve the platform"
|
|
||||||
reSize="none"
|
|
||||||
rows={8}
|
|
||||||
cols={30}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div className="mt-2 flex justify-between">
|
|
||||||
<Button className="ml-2 w-min">Send</Button>
|
|
||||||
<Button className="mr-2 w-min" colorSchema="secondary">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
{router.asPath.includes("org") && (
|
{router.asPath.includes("org") && (
|
||||||
<div
|
<div
|
||||||
onKeyDown={() => null}
|
onKeyDown={() => null}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { faRocketchat } from "@fortawesome/free-brands-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
TextArea
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { useCreateUserWish } from "@app/hooks/api/userEngagement";
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
text: z.string().trim().min(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
type TFormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
export const WishForm = () => {
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors }
|
||||||
|
} = useForm<TFormData>({
|
||||||
|
resolver: zodResolver(formSchema)
|
||||||
|
});
|
||||||
|
const { mutateAsync } = useCreateUserWish();
|
||||||
|
const [isOpen, setIsOpen] = useToggle(false);
|
||||||
|
|
||||||
|
const createWish = async (data: TFormData) => {
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
text: data.text
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Your wish has been sent to the Infisical team!",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsOpen.off();
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: "An error occured while sending your wish to the Infisical team.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
onOpenChange={() => {
|
||||||
|
setIsOpen.toggle();
|
||||||
|
reset();
|
||||||
|
}}
|
||||||
|
open={isOpen}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div className="text-md mb-3 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||||
|
<FontAwesomeIcon icon={faRocketchat} className="mr-2" />
|
||||||
|
Make a wish
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
hideCloseBtn
|
||||||
|
className="ml-2 mb-2 w-auto border border-mineshaft-600 bg-mineshaft-900 p-4 drop-shadow-2xl"
|
||||||
|
sticky="always"
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit(createWish)}>
|
||||||
|
<FormControl
|
||||||
|
className="mb-0"
|
||||||
|
isError={Boolean(errors?.text)}
|
||||||
|
errorText={errors?.text?.message}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
className="border border-mineshaft-600 text-sm focus:ring-0"
|
||||||
|
variant="plain"
|
||||||
|
placeholder="Wish for anything! Help us improve the platform"
|
||||||
|
reSize="none"
|
||||||
|
rows={6}
|
||||||
|
cols={40}
|
||||||
|
{...register("text")}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="mt-2 flex justify-between border-t border-mineshaft-500 pt-4">
|
||||||
|
<PopoverPrimitive.Close>
|
||||||
|
<Button className="mr-2 w-min" colorSchema="secondary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</PopoverPrimitive.Close>
|
||||||
|
<Button
|
||||||
|
className="w-min"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user