Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,8 @@
{
"name": "@comp/api",
"version": "0.0.1",
"description": "",
"version": "0.0.1",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"dev": "nest start --watch",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"typecheck": "tsc --noEmit",
"db:generate": "bun run db:getschema && prisma generate",
"db:getschema": "cp ../../node_modules/@trycompai/db/dist/schema.prisma prisma/schema.prisma",
"prebuild": "bun run db:generate"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.859.0",
"@aws-sdk/s3-request-presigner": "^3.859.0",
Expand Down Expand Up @@ -83,5 +62,26 @@
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
},
"license": "UNLICENSED",
"private": true,
"scripts": {
"build": "nest build",
"db:generate": "bun run db:getschema && prisma generate",
"db:getschema": "cp ../../node_modules/@trycompai/db/dist/schema.prisma prisma/schema.prisma",
"dev": "nest start --watch",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"prebuild": "bun run db:generate",
"start": "nest start",
"start:debug": "nest start --debug --watch",
"start:dev": "nest start --watch",
"start:prod": "node dist/main",
"test": "jest",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"test:watch": "jest --watch",
"typecheck": "tsc --noEmit"
}
}
2 changes: 1 addition & 1 deletion apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,4 @@
"trigger:dev": "npx [email protected] dev",
"typecheck": "tsc --noEmit"
}
}
}
27 changes: 27 additions & 0 deletions apps/app/src/app/(app)/onboarding/[orgId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ export default async function OnboardingPage({ params }: OnboardingPageProps) {
}
});

// Local-only: prefill onboarding fields to speed up development
const hdrs = await headers();
const host = hdrs.get('host') || '';
const isLocal =
process.env.NODE_ENV !== 'production' ||
host.includes('localhost') ||
host.startsWith('127.0.0.1') ||
host.startsWith('::1');

if (isLocal) {
Object.assign(initialData, {
describe:
initialData.describe ||
'comp ai is a grc platform saas that gets companies compliant with soc2 iso and hipaa in days',
industry: initialData.industry || 'SaaS',
teamSize: initialData.teamSize || '1-10',
devices: initialData.devices || 'Personal laptops',
authentication: initialData.authentication || 'Google Workspace',
software:
initialData.software || 'Rippling, HubSpot, Slack, Notion, Linear, GitHub, Figma, Stripe',
workLocation: initialData.workLocation || 'Fully remote',
infrastructure: initialData.infrastructure || 'AWS, Vercel',
dataTypes: initialData.dataTypes || 'Employee data',
geo: initialData.geo || 'North America,Europe (EU)',
});
}

// We'll use a modified version that starts at step 3
return <PostPaymentOnboarding organization={organization} initialData={initialData} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const onboardingCompletionSchema = z.object({
workLocation: z.string().min(1),
infrastructure: z.string().min(1),
dataTypes: z.string().min(1),
geo: z.string().min(1),
});

export const completeOnboarding = authActionClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@comp/ui/c
import { Form, FormControl, FormField, FormItem, FormMessage } from '@comp/ui/form';
import type { Organization } from '@db';
import { ArrowLeft, ArrowRight } from 'lucide-react';
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { usePostPaymentOnboarding } from '../hooks/usePostPaymentOnboarding';

interface PostPaymentOnboardingProps {
Expand All @@ -33,12 +33,24 @@ export function PostPaymentOnboarding({
isLastStep,
currentStepNumber,
totalSteps,
completeNow,
} = usePostPaymentOnboarding({
organizationId: organization.id,
organizationName: organization.name,
initialData,
});

const isLocal = useMemo(() => {
if (typeof window === 'undefined') return false;
const host = window.location.host || '';
return (
process.env.NODE_ENV !== 'production' ||
host.includes('localhost') ||
host.startsWith('127.0.0.1') ||
host.startsWith('::1')
);
}, []);

// Dispatch custom event for background animation when step changes
useEffect(() => {
if (typeof window !== 'undefined') {
Expand Down Expand Up @@ -124,24 +136,37 @@ export function PostPaymentOnboarding({
Back
</Button>

<Button
type="submit"
form="onboarding-form"
disabled={isOnboarding || isFinalizing || isLoading}
className="group transition-all hover:pl-3"
data-testid="onboarding-next-button"
>
{isFinalizing ? (
'Setting up...'
) : isLastStep ? (
'Complete Setup'
) : (
<>
Next
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-0.5" />
</>
<div className="flex items-center gap-2">
{isLocal && (
<Button
type="button"
variant="secondary"
onClick={completeNow}
disabled={isOnboarding || isFinalizing || isLoading}
className="group transition-all"
>
Complete now
</Button>
)}
</Button>
<Button
type="submit"
form="onboarding-form"
disabled={isOnboarding || isFinalizing || isLoading}
className="group transition-all hover:pl-3"
data-testid="onboarding-next-button"
>
{isFinalizing ? (
'Setting up...'
) : isLastStep ? (
'Complete Setup'
) : (
<>
Next
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-0.5" />
</>
)}
</Button>
</div>
</div>
<div className="w-full border-t border-border/30 pt-3">
<p className="text-center text-xs text-muted-foreground/70">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,21 @@ export function usePostPaymentOnboarding({
workLocation: allAnswers.workLocation || '',
infrastructure: allAnswers.infrastructure || '',
dataTypes: allAnswers.dataTypes || '',
geo: allAnswers.geo || '',
});
};

const completeNow = () => {
const currentValues = form.getValues();
const allAnswers: Partial<CompanyDetails> = {
...savedAnswers,
...currentValues,
organizationName,
} as Partial<CompanyDetails>;

handleCompleteOnboarding(allAnswers);
};

const onSubmit = (data: OnboardingFormFields) => {
const newAnswers: OnboardingFormFields = { ...savedAnswers, ...data };

Expand Down Expand Up @@ -215,5 +227,6 @@ export function usePostPaymentOnboarding({
isLastStep,
currentStepNumber: stepIndex + 1, // Display as steps 1-9
totalSteps: postPaymentSteps.length, // Total 9 steps for post-payment
completeNow,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const createOrganizationMinimal = authActionClientWithoutOrg
name: parsedInput.organizationName,
website: parsedInput.website,
onboardingCompleted: false, // Explicitly set to false
// Local-only: default access for faster local development
...(process.env.NODE_ENV !== 'production' && { hasAccess: true }),
members: {
create: {
userId: session.user.id,
Expand Down
16 changes: 16 additions & 0 deletions apps/app/src/app/(app)/setup/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const companyDetailsSchema = z.object({
devices: z.string().min(1, 'Please select device types'),
authentication: z.string().min(1, 'Please select authentication methods'),
workLocation: z.string().min(1, 'Please select work arrangement'),
geo: z.string().min(1, 'Please select where your data is located'),
});

export const steps: Step[] = [
Expand Down Expand Up @@ -121,6 +122,21 @@ export const steps: Step[] = [
'Other',
],
},
{
key: 'geo',
question: 'Where is your data located?',
placeholder: 'e.g., North America',
options: [
'North America',
'Europe (EU)',
'United Kingdom',
'Asia-Pacific',
'South America',
'Africa',
'Middle East',
'Australia/New Zealand',
],
},
];

export const welcomeText = [
Expand Down
1 change: 1 addition & 0 deletions apps/app/src/app/(app)/setup/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type CompanyDetails = {
infrastructure: string;
dataTypes: string;
software: string;
geo: string;
};

export type ChatBubble = {
Expand Down
76 changes: 51 additions & 25 deletions apps/app/src/jobs/lib/prompts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Policy } from '@db';
import { FrameworkEditorFramework, Policy } from '@db';
import type { JSONContent } from '@tiptap/react';
import { logger } from '@trigger.dev/sdk';

Expand All @@ -8,53 +8,79 @@ export const generatePrompt = ({
contextHub,
companyName,
companyWebsite,
frameworks,
}: {
contextHub: string;
companyName: string;
companyWebsite: string;
policy: Policy;
existingPolicyContent: JSONContent | JSONContent[];
frameworks: FrameworkEditorFramework[];
}) => {
logger.info(`Generating prompt for policy ${policy.name}`);
logger.info(`Company Name: ${companyName}`);
logger.info(`Company Website: ${companyWebsite}`);
logger.info(`Context: ${contextHub}`);
logger.info(`Existing Policy Content: ${JSON.stringify(existingPolicyContent)}`);
logger.info(
`Frameworks: ${JSON.stringify(
frameworks.map((f) => ({ id: f.id, name: f.name, version: f.version })),
)}`,
);

return `
Company details:

Company Name: ${companyName}
Company Website: ${companyWebsite}
const frameworkList =
frameworks.length > 0
? frameworks.map((f) => `${f.name} v${f.version}`).join(', ')
: 'None explicitly selected';
const hasHIPAA = frameworks.some((f) => f.name.toLowerCase().includes('hipaa'));
const hasSOC2 = frameworks.some(
(f) => /soc\s*2/i.test(f.name) || f.name.toLowerCase().includes('soc'),
);

Knowledge Base for ${companyName}:
return `
Company: ${companyName} (${companyWebsite})
Frameworks selected: ${frameworkList}

Knowledge base:
${contextHub}

Tailoring rules:
Create or update a policy based on strict alignment with SOC 2 standards and controls.
Task: Edit the provided TipTap JSON template to produce the final policy TipTap JSON. Apply ONLY the rules below.

Contextualise every section with company Secure-specific systems, regions, and roles.
Replace office-centric language with cloud and home-office equivalents.
Build control statements that directly mitigate the listed risks; remove irrelevant clauses.
Use mandatory language such as “must” or “shall”; specify measurable review cycles (quarterly, annually).
End with a bullet list of auditor evidence artefacts (logs, tickets, approvals, screenshots).
Limit to three-sentence executive summary and maximum 600-word main body.
Wrap any unresolved detail in <<TO REVIEW>>.
Required rules (keep this simple):

1.Remove Document Version Control section altogether(if present) and also adjust numbering accordingly
2. Make a table of contents (in tiptap format)
3. Give me executive summary on top of the document
4. Wrap any unresolved detail in <<TO REVIEW>>
5. Number 1 in Table of Contents will be Document Content Page
6. I want to document to be strictly aligned with SOC 2 standards and controls
1) Company details
- If the template contains placeholders like {{...}}, replace ANY placeholder with information you actually have (from the knowledge base, company name, company website, frameworks context).
- If a specific placeholder cannot be resolved, set it to "N/A" (do not invent values).
- Only fill placeholders where the template asks; do not add new fields beyond the placeholders.
- Placeholder legend (map values from the knowledge base Q&A where available):
- {{COMPANY}} ⇐ Company Name
- {{COMPANYINFO}} ⇐ Describe your company in a few sentences
- {{INDUSTRY}} ⇐ What Industry is your company in?
- {{EMPLOYEES}} ⇐ How many employees do you have
- {{DEVICES}} ⇐ What Devices do your team members use
- {{SOFTWARE}} ⇐ What software do you use
- {{LOCATION}} ⇐ How does your team work
- {{CRITICAL}} ⇐ Where do you host your application and data
- {{DATA}} ⇐ What type of data do you handle
- {{GEO}} ⇐ Where is your data located
- If multiple answers exist, choose the most specific/concise form. If no answer is found for a placeholder, set it to "N/A".

Policy Title: ${policy.name}
Policy: ${policy.description}
2) Structure & style
- Keep the same section order and general layout as the template (headings or bold titles as-is).
- Do NOT copy instruction cue lines (e.g., "Add a HIPAA checklist...", "State that...", "Clarify that..."). Convert such cues into real policy language, and then remove the cue line entirely. If a cue precedes bullet points, keep the bullets but delete the cue line.

3) Handlebars-style conditionals
- The template may contain conditional blocks using {{#if var}}...{{/if}} syntax (e.g., {{#if soc2}}, {{#if hipaa}}).
- Evaluate these using the selected frameworks:
- soc2 is ${hasSOC2 ? 'true' : 'false'}
- hipaa is ${hasHIPAA ? 'true' : 'false'}
- If the condition is true: keep only the inner content and remove the {{#if}}/{{/if}} markers.
- If the condition is false: remove the entire block including its content.
- For any other unknown {{#if X}} variables: assume false and remove the block.

Here is the initial template policy to edit:
Output: Return ONLY the final TipTap JSON document.

Template (TipTap JSON) to edit:
${JSON.stringify(existingPolicyContent)}
`;
};
Loading