Compare commits

...

7 Commits

Author SHA1 Message Date
Johannes
b673044890 Add Blog for v1 Release - How we got here
Add Blog for v1 Release - How we got here
2023-07-14 06:05:44 -05:00
Shubham Palriwala
bf9189c1af fix: docker compose url for curl (#557) 2023-07-14 13:00:57 +02:00
Moritz Rengert
e262006f7e Add Demo-Data to newly created teams (#521)
* create people and survey on team create

* create people and responses / displays

* drafting GPT input

* add attributeClass, eventClass and people

* fix link in person detail page

* fix email instead of uid

* added two surveys

* add events, 3 more surveys

---------

Co-authored-by: Johannes <johannes@formbricks.com>
2023-07-14 12:55:41 +02:00
Johannes
d44ea1f32d add v1 blog 2023-07-14 12:51:44 +02:00
Matti Nannt
e24f6cd017 Improve documentation (#552)
* update nextjs app docs

* remove prisma extendedWhereUnique from schema

* change button titles in pricing table

* fix smaller bugs
2023-07-14 12:48:57 +02:00
Johannes
62d2c1af18 improve filter ux, update summary header (#555) 2023-07-14 10:37:42 +02:00
Nitesh Seram
1a83373099 Fix random 0 can appear in survey menubar (#553)
Co-authored-by: Seram Nitesh Singh <nitesh.s@auzmor.com>
2023-07-14 09:08:15 +02:00
33 changed files with 1527 additions and 53 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,171 @@
import Image from "next/image";
import LayoutMdx from "@/components/shared/LayoutMdx";
import { Callout } from "@/components/shared/Callout";
import TweetPeer from "./peer-tweet-typeform-open-source.png";
import SnoopForms from "./snoopforms-open-source-typeform-alternative.png";
import TwitterResult from "./twitter-results-PMF-cal.png";
import EmailResult from "./email-results-PMF-cal.png";
import TitleImage from "../github-accelerator-experience/formbricks-sponsored-by-github-accelerator-2023.webp";
import PMFDashboard from "./pmf-survey-dashboard.png";
import MattiJojo from "./matti-jojo.jpg";
export const meta = {
title: "Formbricks v1 - How we got here 🤸",
description:
"A lot has happened since Matti and I had a chat about open-source surveys in May last year. The release of Formbricks v1.0 is a perfect opportunity to look back on how it all started.",
date: "2023-07-14",
};
_A lot has happened since Matti and I had a chat about open-source surveys in May last year. The release of Formbricks v1.0 is a perfect opportunity to look back on how it all started._
Funnily enough, it started with a tweet:
<Image
src={TweetPeer}
alt="Peer tweets about the best open-source Typeform alternative"
className="rounded-lg"
/>
When I read this tweet, I was sitting in a WeWork in Mexico City, feeling a bit guilty for being such a cliché digital nomad. Sipping on my decaffeinated matcha with lactose-free goat milk, I slid into Matti's DMs. I knew he had built an open-source survey tool a few months ago and suggested he comment on the tweet. We started chatting.
Both Matti and I had been freelancing for several months. While the money was good, we missed working on a project of our own. We met a couple of years ago while working on our startups. I had an app and wanted to be GDPR compliant; Matti offered to help out. We've stayed in contact ever since, giving feedback on projects and ideas.
So we chatted about the opportunity of building a commercial open-source alternative to Typeform. After reading up on the advantages - and challenges - of open-source, we decided to build a side project together: **[snoopForms: The Open-Source Typeform Alternative](https://snoopforms.com/)** 🦝
<Image
src={SnoopForms}
alt="SnoopForms was the OS Typeform alternative with a twist"
className="rounded-lg"
/>
### **snoopForms and why OS Typeform isnt a good business**
We shipped a first version of the product and the landing page and shared it on ProductHunt and HackerNews. It stirred up quite some interest—1.5k people signed up over a few weeks. During the launch, we sold 10 early bird deals for $179 each. We got excited! Is it really that easy?
Well no, it is not. Apart from the Early Birds (🤍) we attracted a looooooooot of people who liked Typeform, but didnt want to pay the high price tag. Generally, it's not the best idea to build a product with high competition (100s of survey tools) for a price-sensitive target group. Even data privacy isnt a good selling point when Jotform offers self-hosting and Typeform is an EU-based company (GDPR). Weve written **[in-depth](https://formbricks.com/blog/open-source-qualtrics-beats-typeform)** about why we eventually decided to shift our focus away from open-source Typeform.
Nonetheless, we were hooked on open-source surveys; we just hadn't found the right application yet.
### **snoopForms → Formbricks, a detour, and going full-time**
We decided to wrap up our freelance gigs and go in full-time, to find the right angle quicker. We built a MVP for _Building Bricks for Forms and Surveys_, but while the idea made sense, we struggled to build an easy, opionated solution which can be used for several use cases. To help engineers build form and survey applications faster, it needed to pack form creation, form analytics, graphs, data handling, data storage, analysis, integrations, and ways to act on the insights for a variety of use cases 🤷 There's a reason why successful dev tools focus on one of these aspects and do them well. So we kept looking…
### **Data Processing vs. Experience Management**
We had talked to and surveyed a lot of people. Zooming out, we saw that there are two applications for forms: data processing and experience management.
**Data processing** is essentially getting information out of a human brain into a digital system. The focus here lies on high integrability with (legacy) systems, versatility, and a great developer experience. Libraries like React Hook Forms or the more productized form.io fall into this category. While there certainly is a need here, it was a less inspiring field for us, especially after reading how [tedious](https://medium.com/@jgee/what-i-learned-in-two-years-of-moving-government-forms-online-1edc4c2aa089) it can be to bring [legacy](https://medium.com/san-francisco-digital-services/how-to-make-a-form-d1d1b67d95d7) form systems up to speed.
**Experience Management** on the other hand is a lot more exciting! Helping teams build better products, services, and organizations is a mission both Matti and I could get behind ❤️
<Callout title="In a nutshell" type="note">
Experience Management, in a nutshell, is enabling teams to gather, analyze, and act on qualitative data
collected from their users, customers, and employees.
</Callout>
We researched what was out there and talked to more founders. Initially, we wanted to build a solution for a problem we had ourselves: Gathering qualitative insights along the user journey of early adopters. Its common sense to never launch without analytics, but why does everyone launch without a system to gather qualitative user insights continuously? Mostly, because there's no easy, cheap and quick way to do it (yet).
### **The Product-Market Fit Survey Tool**
Following Paul Graham's advice to start with a **[narrow but deep hole](http://paulgraham.com/startupideas.html)**, we narrowed it down to one specific problem every founder has: Measuring Product-Market Fit. Many know the **[article](https://review.firstround.com/how-superhuman-built-an-engine-to-find-product-market-fit)** on how Superhuman built an engine to measure and optimize PMF. However, to run it correctly within your product, you do have to do quite some custom coding. So we decided to offer a solution which works out of the box.
<Image
src={PMFDashboard}
alt="Peer tweets about the best open-source Typeform alternative"
className="rounded-lg"
/>
_We built a custom dashboard just to visualize the PMF survey results_
We shipped an MVP, launched it, and picked up the conversations with the founders who had tried to measure PMF
before. We quickly learned this:
- Our MVP was scoped so rigidly that we had to do quite some custom coding ourselves to get it implemented. Setting it up and maintaining it stood in no relation to the value it provided because…
- The PMF survey is for teams that "kinda" have PMF, not for teams which are just starting out. You need a few hundred recurring users in your app to be able to reach statistical significance and run the survey every 3 months without asking anyone twice. For teams who dont have that, talking to users 1:1 will remain the best way to go.
- Founders were reluctant to add another tool just for one survey and purpose.
After the rather disappointing launch of the PMF survey tool, we hit a low. The excitement about the initial snoopForm traction had passed. Since November, we had been working full-time for four months iterating on different products in the forms and survey space, and it didnt feel like we made much progress. But that wasnt really true.
### **"The essence of strategy is choosing what not to do.”**
It was quite a journey since Matti wrote the first line of code in July last year. Even though it didnt _feel_ like much progress, we had learned a lot. Most importantly, we knew what **not** to build.
With snoopForms, we learned that there is a latent need for an open-source standalone survey tool, with a Typeform-like feature set: Its a great opportunity to build a community and **not** a great business opportunity. The PMF survey tool taught us about the needs of early-stage teams and that surveys are **not** the best solution to their problems. However, they do need some form to gather qualitative feedback and a smooth way to work with it 😏
We took everything we knew and wrote a Masterplan. And then we sat down coded.
### **Open-source in-product surveys**
March and the first half of April flew by as we hacked together the MVP of what Formbricks is today: A tool which enables teams to target specific segments of their user base with versatile micro-surveys. Matti solved some tricky technical challenges around the Formbricks Widget and shipped the backend. ChatGPT and I warmed up with next.js and built frontend. Seeing the product come together was fun and felt like progress - exactly what we needed!
<Image
src={TitleImage}
alt="GitHub sponsors Formbricks to join their open-source accelerator program"
className="rounded-lg"
/>
A welcomed motivator came in the form of an email from GitHub: We we were chosen to take part in the first-ever batch of the GitHub Open-Source Accelerator! It was a really fun programme and we learned a lot! You can read about our experience [here.](https://formbricks.com/blog/github-accelerator-experience)
### **On the right track 🚆**
Once we were ready to share our MVP with the world, we knew we were on the right track! The feedback was really good and usage picked up right from the start. One morning we woke up to our analytics dashboard showing thousands of survey displays with 500+ responses in the past couple of hours! This is when we knew were onto something 🚀
A couple of weeks later, Peer from [Cal.com](http://Cal.com) came back to northern Germany to visit his family and friends. We hung out in the co-working space, where he did his first steps as an entrepreneur. As we were chatting about how to separate opinion from experience, Peer decided to shoot an email with a survey to the 12k most active users of Cal.com. A few days before, he asked his Twitter following the PMF question:
<Image
src={TwitterResult}
alt="SnoopForms was the OS Typeform alternative with a twist"
className="rounded-lg"
/>
We wanted to compare how the publicly asked survey (Twitter) compared to a survey among the community of users. The results were eye-opening:
<Image
src={EmailResult}
alt="SnoopForms was the OS Typeform alternative with a twist"
className="rounded-lg"
/>
_Anything above 40% is considered PMF, 60% is 🔥🔥🔥_
For us, this was not only a good test for our system but it was a great example that Experience Management does not only happen within apps, but on several touch points right from the start. If we want to offer a solution which can comprehensively measure and evaluate experiences, it cannot be confined to in-product surveys for too long.
### **Typeform sneaking back in?**
Given our early traction with snoopForms, its not a big surprise that community requests for a Typeform-like standalone survey popped up more and more. Since we had all the pieces in place, we shipped standalone surveys in a day. However, its not our focus right now, so we hand over most of the feature requests to the community. And the community ships! Just in the past couple of weeks, we got:
- **Prefill Data in Link Surveys** by Piyush - [Docs](https://formbricks.com/docs/link-surveys/data-prefilling)
- **Identify Users Link identification** by Zorig - [Docs](https://formbricks.com/docs/link-surveys/user-identification)
- **Close Survey on Date** by Piyush
- **Close Survey after x Responses** by Pradumn
- **Redirect on Link Survey Completion** by Dhruwang
- **Embed Link Survey via iframe** by Ankur
- **Set Custom Survey Closed Message** by Pradumn
- **Randomize Answer Options** by Shubdeep
We absolutely love the idea of being the platform for a community-developed standalone survey experience! Its a win-win: The community gets a free, self-hostable standalone survey product and we get a growing upper funnel for our Experience Management solution 🤸
### **Its coming together 🔥**
Looking back at the amount of value Formbricks can deliver after not even 5 months fills us with excitement. Matti and I have been hacking it together for the most part but we wouldnt be anywhere nearly as close without the support of our community and friends - thank you!
And were only getting started: We were able to hire two engineers from our community of contributors to build up more shipping velocity. We also started working with Kristian who runs our [open-source Design repo](https://github.com/formbricks/design) more closely and are super excited about what were cooking up. Well make sure Formbricks stays easy-to-use, accessible and simply beautiful as it grows more powerful! We can't tell you how excited we are about what's coming 😁
If youre curious to learn about all the things you can do with Formbricks today, weve written and keep updating a list of Best Practices incl. survey templates in our [Docs](https://formbricks.com/docs/best-practices/cancel-subscription).
### Sounds good? **Build with us!**
- **Eager to contribute?** [Join our Discord](https://formbricks.com/discord) and say Hi! Were more than happy to find the perfect issue for your level of experience 🙌 😊
- **Got an experience to measure?** Open-source is the way to go. [Lets have a chat 🤙](https://cal.com/johannes/15)
<Image
src={MattiJojo}
alt="SnoopForms was the OS Typeform alternative with a twist"
className="rounded-lg w-1/2"
/>
### All the best!
Johannes & Matti
export default ({ children }) => <LayoutMdx meta={meta}>{children}</LayoutMdx>;

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -36,22 +36,24 @@ pnpm add @formbricks/js
## Integrating with Next.js 13 App Directory
The Next.js 13 app directory requires us to initialize Formbricks differently than the pages directory. Specifically, the app directory server-side renders components by default, and the formbricks-js library is a client-side library. To make these work together, create a `formbricks.js` file (or formbricks.ts if you are using Typescript) and set up the FormbricksProvider with the 'use client' directive:
The Next.js 13 app directory requires us to initialize Formbricks differently than the pages directory. Specifically, the app directory server-side renders components by default, and the formbricks-js library is a client-side library. To make these work together, create a `formbricks.tsx` file (or `formbricks.js` if you don't use Typescript) and set up the FormbricksProvider with the 'use client' directive:
```tsx
// app/formbricks.js
// app/formbricks.tsx
"use client";
import formbricks from "@formbricks/js";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
if (typeof window !== "undefined") {
formbricks.init({
environmentId: "your-environment-id",
apiHost: "your-api-host", // e.g. https://app.formbricks.com
environmentId: "clj66eqzu00m5qu0g8leglrns",
apiHost: "https://app.formbricks.com", // e.g. https://app.formbricks.com
debug: true, // remove when in production
});
}
export default function Providers({ Component, pageProps }: AppProps) {
export default function FormbricksProvider() {
const pathname = usePathname();
const searchParams = useSearchParams();
@@ -63,21 +65,17 @@ export default function Providers({ Component, pageProps }: AppProps) {
}
```
Once we do this, we can then import the `provider.js` file in our `app/layout.js` file, and wrap our app in the Formbricks provider.
Once we do this, we can then import the `formbricks.tsx` file in our `app/layout.tsx` file, and wrap our app in the Formbricks provider.
```tsx
// app/layout.js
import Providers from "./formbricks";
// app/layout.tsx
import FormbricksProvider from "./formbricks";
import "./globals.css";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<Providers />
<FormbricksProvider />
<body>{children}</body>
</html>
);

View File

@@ -41,7 +41,7 @@ This is suitable for those who are testing Formbricks or running it with minimal
Download the docker-compose file directly from the Formbricks repository:
```bash
curl -o docker-compose.yml https://raw.githubusercontent.com/formbricks/formbricks/docker/main/docker-compose.yml
curl -o docker-compose.yml https://raw.githubusercontent.com/formbricks/formbricks/main/docker/docker-compose.yml
```
3. **Generate NextAuth Secret**

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,7 @@ export default function ResponseFeed({ person, sortByDate, environmentId }) {
<div className="flex items-center justify-center space-x-2 rounded-full bg-slate-50 px-3 py-1 text-sm text-slate-600">
<Link
className="hover:underline"
href={`environments/${environmentId}/surveys/${response.survey.id}/summary`}>
href={`/environments/${environmentId}/surveys/${response.survey.id}/summary`}>
{response.survey.name}
</Link>
<SurveyStatusIndicator

View File

@@ -17,7 +17,11 @@ export default async function PeoplePage({ params }) {
return (
<>
{people.length === 0 ? (
<EmptySpaceFiller type="table" environmentId={params.environmentId} />
<EmptySpaceFiller
type="table"
environmentId={params.environmentId}
emptyMessage="Your users will appear here as soon as they use your app ⏲️"
/>
) : (
<div className="rounded-lg border border-slate-200">
<div className="grid h-12 grid-cols-7 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">

View File

@@ -134,7 +134,7 @@ export default function PricingTable({ environmentId, session }: PricingTablePro
variant="secondary"
className="mt-6 w-full justify-center py-4 shadow-sm"
onClick={() => openCustomerPortal()}>
Change Plan
Manage Subscription
</Button>
) : (
<Button

View File

@@ -130,7 +130,7 @@ export function DeleteProduct({ environmentId }) {
if (deleteProductRes?.id?.length > 0) {
toast.success("Product deleted successfully.");
router.push("/environments");
router.push("/");
} else if (deleteProductRes?.message?.length > 0) {
toast.error(deleteProductRes.message);
setIsDeleteDialogOpen(false);

View File

@@ -54,8 +54,7 @@ export default function MultipleChoiceSummary({
}
function findEmail(person) {
const emailAttribute = person.attributes.email;
return emailAttribute ? emailAttribute.value : null;
return person.attributes?.email || null;
}
const addOtherChoice = (response, value) => {

View File

@@ -12,8 +12,7 @@ interface OpenTextSummaryProps {
}
function findEmail(person) {
const emailAttribute = person.attributes.email;
return emailAttribute ? emailAttribute.value : null;
return person.attributes?.email || null;
}
export default function OpenTextSummary({ questionSummary, environmentId }: OpenTextSummaryProps) {

View File

@@ -36,6 +36,10 @@ export default function SuccessMessage({ environmentId, survey }: SummaryMetadat
if (survey.type === "link") {
setShowLinkModal(true);
}
// Remove success param from url
const url = new URL(window.location.href);
url.searchParams.delete("success");
window.history.replaceState({}, "", url.toString());
}
}
}, [environment, searchParams, survey]);

View File

@@ -97,7 +97,7 @@ const QuestionFilterComboBox = ({
{filterOptions?.map((o, index) => (
<DropdownMenuItem
key={`${o}-${index}`}
className="px-0.5 py-1 text-slate-800 dark:bg-slate-700 dark:text-slate-300 dark:ring-slate-700"
className="px-0.5 py-1 dark:bg-slate-700 dark:text-slate-300 dark:ring-slate-700"
onClick={() => onChangeFilterValue(o)}>
{o}
</DropdownMenuItem>

View File

@@ -79,7 +79,7 @@ const SelectedCommandItem = ({ label, questionType, type }: Partial<QuestionOpti
if (type === OptionsType.ATTRIBUTES) {
return "bg-indigo-500";
} else if (type === OptionsType.QUESTIONS) {
return "bg-brand-light";
return "bg-brand-dark";
} else {
return "bg-amber-500";
}
@@ -99,7 +99,7 @@ const QuestionsComboBox = ({ options, selected, onChangeValue }: QuestionComboBo
useClickOutside(commandRef, () => setOpen(false));
return (
<Command ref={commandRef} className="h-10 overflow-visible bg-transparent">
<Command ref={commandRef} className="h-10 overflow-visible bg-transparent hover:bg-slate-50">
<div
onClick={() => setOpen(true)}
className="group flex cursor-pointer items-center justify-between rounded-md bg-white px-3 py-2 text-sm">

View File

@@ -58,8 +58,8 @@ const SummaryHeader = ({ surveyId, environmentId, survey }: SummaryHeaderProps)
return (
<div className="mb-11 mt-6 flex flex-wrap items-center justify-between">
<div>
<p className="text-3xl font-bold text-black">{product.name}</p>
<span className="text-base font-extralight text-black">*{survey.name}*</span>
<p className="text-3xl font-bold text-slate-800">{survey.name}</p>
<span className="text-base font-extralight text-slate-600">{product.name}</span>
</div>
<div className="hidden justify-end gap-x-1.5 sm:flex">
{survey.type === "link" && <LinkSurveyShareButton survey={survey} />}

View File

@@ -138,7 +138,7 @@ export default function SurveyMenuBar({
className="w-72 border-white hover:border-slate-200 "
/>
</div>
{localSurvey?.responseRate && (
{!!localSurvey?.responseRate && (
<div className="mx-auto flex items-center rounded-full border border-amber-200 bg-amber-100 p-2 text-sm text-amber-700 shadow-sm">
<ExclamationTriangleIcon className="mr-2 h-5 w-5 text-amber-400" />
This survey received responses. To keep the data consistent, make changes with caution.

View File

@@ -100,10 +100,6 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
return <div>Error</div>;
}
/* if (localSurvey.type === "link") {
return null;
} */
return (
<>
<Collapsible.Root

View File

@@ -85,7 +85,11 @@ export default function WhoToSendCard({ environmentId, localSurvey, setLocalSurv
<>
<Collapsible.Root
open={open}
onOpenChange={setOpen}
onOpenChange={(openState) => {
if (localSurvey.type !== "link") {
setOpen(openState);
}
}}
className="w-full rounded-lg border border-slate-300 bg-white">
<Collapsible.CollapsibleTrigger
asChild

View File

@@ -9,9 +9,15 @@ type EmptySpaceFillerProps = {
type: "table" | "response" | "event" | "linkResponse" | "tag";
environmentId: string;
noWidgetRequired?: boolean;
emptyMessage?: string;
};
const EmptySpaceFiller: React.FC<EmptySpaceFillerProps> = ({ type, environmentId, noWidgetRequired }) => {
const EmptySpaceFiller: React.FC<EmptySpaceFillerProps> = ({
type,
environmentId,
noWidgetRequired,
emptyMessage,
}) => {
const { environment, isErrorEnvironment, isLoadingEnvironment } = useEnvironment(environmentId);
if (isLoadingEnvironment) return <LoadingSpinner />;
@@ -34,7 +40,7 @@ const EmptySpaceFiller: React.FC<EmptySpaceFillerProps> = ({ type, environmentId
</span>
</Link>
)}
{(environment.widgetSetupCompleted || noWidgetRequired) &&
{((environment.widgetSetupCompleted || noWidgetRequired) && emptyMessage) ||
"Your data will appear here as soon as you receive your first response ⏲️"}
</div>

View File

@@ -55,7 +55,11 @@ export default function CreateTeamModal({ open, setOpen }: CreateTeamModalProps)
<div className="grid w-full gap-x-2">
<div>
<Label>Team Name</Label>
<Input placeholder="e.g. Power Puff Girls" {...register("name", { required: true })} />
<Input
autoFocus
placeholder="e.g. Power Puff Girls"
{...register("name", { required: true })}
/>
</div>
</div>
</div>

View File

@@ -123,23 +123,36 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
// DELETE
else if (req.method === "DELETE") {
// get teamId from product
const environment = await prisma.environment.findUnique({
where: { id: environmentId },
select: {
product: {
select: {
id: true,
teamId: true,
},
},
},
});
if (!environment) {
res.status(404).json({ error: "Environment not found" });
return;
}
const teamId = environment?.product.teamId;
const membership = await prisma.membership.findUnique({
where: {
userId_teamId: {
userId: currentUser.id,
teamId: currentUser.teamId,
teamId: teamId,
},
},
});
if (membership?.role !== "admin" && membership?.role !== "owner") {
return res.status(403).json({ message: "You are not allowed to delete products." });
}
const environment = await prisma.environment.findUnique({
where: { id: environmentId },
select: {
productId: true,
},
});
const productId = environment?.product.id;
if (environment === null) {
return res.status(404).json({ message: "This environment doesn't exist" });
@@ -147,7 +160,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
// Delete the product with
const prismaRes = await prisma.product.delete({
where: { id: environment.productId },
where: { id: productId },
});
return res.json(prismaRes);

View File

@@ -7,8 +7,7 @@ datasource db {
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["extendedWhereUnique"]
provider = "prisma-client-js"
//provider = "prisma-dbml-generator"
}

View File

@@ -115,9 +115,9 @@ export const initialize = async (
// continue for now - next sync will check complete state
}
} else {
logger.debug("No valid session found. Creating new config.");
logger.debug("No valid configuration found. Creating new config.");
// we need new config
config.update({ environmentId: c.environmentId, apiHost: c.apiHost });
config.update({ environmentId: c.environmentId, apiHost: c.apiHost, state: undefined });
logger.debug("Syncing.");
const syncResult = await sync();

View File

@@ -83,11 +83,11 @@ export const getPeople = cache(async (environmentId: string): Promise<TPerson[]>
throw new ResourceNotFoundError("Persons", "All Persons");
}
const transformedPersons: TPerson[] = personsPrisma
const transformedPeople: TPerson[] = personsPrisma
.map(transformPrismaPerson)
.filter((person: TPerson | null): person is TPerson => person !== null);
return transformedPersons;
return transformedPeople;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError("Database operation failed");

View File

@@ -105,7 +105,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
"aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm font-medium outline-none hover:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}