Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 02fb454

Browse files
committed
Add environment configuration for production, including API keys, database connection, and email settings. Implement organization creation modal with multi-step form for details and integration selection, along with integration configuration forms for Azure DevOps. Introduce utility functions for color management and step indicators for user navigation.
1 parent 463040b commit 02fb454

9 files changed

Lines changed: 964 additions & 0 deletions

File tree

.env.production

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# -----------------------------------------------------------------------------
2+
# App - Don't add "/" in the end of the url (same in production)
3+
# -----------------------------------------------------------------------------
4+
NEXT_PUBLIC_APP_URL=http://localhost:3000
5+
6+
# -----------------------------------------------------------------------------
7+
# Authentication (NextAuth.js)
8+
# -----------------------------------------------------------------------------
9+
AUTH_SECRET="your-auth-secret"
10+
11+
GOOGLE_CLIENT_ID=your-google-client-id
12+
GOOGLE_CLIENT_SECRET=your-google-client-secret
13+
14+
# -----------------------------------------------------------------------------
15+
# LLM API
16+
# -----------------------------------------------------------------------------
17+
GROQ_API_KEY="your-groq-api-key"
18+
19+
# -----------------------------------------------------------------------------
20+
# LLM Models
21+
# -----------------------------------------------------------------------------
22+
GROQ_MODEL="meta-llama/llama-4-scout-17b-16e-instruct"
23+
24+
# -----------------------------------------------------------------------------
25+
# Database (Postgresql - Neon DB)
26+
# -----------------------------------------------------------------------------
27+
DATABASE_URL="postgresql://username:password@hostname/database?sslmode=require"
28+
29+
# -----------------------------------------------------------------------------
30+
# Subscriptions (Stripe)
31+
# -----------------------------------------------------------------------------
32+
STRIPE_API_KEY=your-stripe-api-key
33+
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
34+
35+
NEXT_PUBLIC_STRIPE_PRO_MONTHLY_PLAN_ID=your-stripe-pro-monthly-plan-id
36+
NEXT_PUBLIC_STRIPE_PRO_YEARLY_PLAN_ID=your-stripe-pro-yearly-plan-id
37+
38+
NEXT_PUBLIC_STRIPE_BUSINESS_MONTHLY_PLAN_ID=your-stripe-business-monthly-plan-id
39+
NEXT_PUBLIC_STRIPE_BUSINESS_YEARLY_PLAN_ID=your-stripe-business-yearly-plan-id
40+
41+
# -----------------------------------------------------------------------------
42+
# AWS S3
43+
# -----------------------------------------------------------------------------
44+
AWS_ACCESS_KEY_ID=your-aws-access-key-id
45+
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key
46+
AWS_REGION=your-aws-region
47+
AWS_S3_BUCKET=your-s3-bucket
48+
AWS_BEDROCK_ACCESS_KEY_ID=your-aws-bedrock-access-key-id
49+
AWS_BEDROCK_SECRET_ACCESS_KEY=your-aws-bedrock-secret-access-key
50+
51+
# Add these variables for file upload functionality
52+
CLOUD_AWS_ACCESS_KEY_ID=your-cloud-aws-access-key-id
53+
CLOUD_AWS_SECRET_ACCESS_KEY=your-cloud-aws-secret-access-key
54+
CLOUD_AWS_REGION=your-cloud-aws-region
55+
CLOUD_AWS_S3_BUCKET=your-cloud-aws-s3-bucket
56+
57+
# -----------------------------------------------------------------------------
58+
# Email
59+
# -----------------------------------------------------------------------------
60+
EMAIL_SERVER_USER=your-email-server-user
61+
EMAIL_SERVER_PASSWORD=your-email-server-password
62+
EMAIL_SERVER_HOST=your-email-server-host
63+
EMAIL_SERVER_PORT=587
64+
EMAIL_FROM=info@example.com
65+
EMAIL_SERVER=smtp://user:password@host:port
66+
67+
# -----------------------------------------------------------------------------
68+
# PDF.co API
69+
# -----------------------------------------------------------------------------
70+
OCR_API_KEY=your-ocr-api-key
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Brand colors object with hex values
2+
export const brandColors = {
3+
blue: "#0078d4",
4+
indigo: "#6366f1",
5+
pink: "#ec4899",
6+
red: "#ef4444",
7+
orange: "#f97316",
8+
amber: "#f59e0b",
9+
emerald: "#10b981",
10+
};
11+
12+
// Helper function to get color name from hex value
13+
export const getColorNameFromValue = (value: string): string => {
14+
const entry = Object.entries(brandColors).find(([_, hexValue]) => hexValue === value);
15+
return entry ? entry[0] : "blue"; // Default to blue if not found
16+
};
17+
18+
// Helper to ensure we have a valid hex color
19+
export const getHexColor = (color: string): string => {
20+
// If the color is a key in brandColors, use its hex value
21+
if (color in brandColors) {
22+
return brandColors[color as keyof typeof brandColors];
23+
}
24+
25+
// If it's already a valid hex color, return it
26+
if (color.startsWith('#') || color.startsWith('rgb')) {
27+
return color;
28+
}
29+
30+
// Default to blue if we can't parse the color
31+
return brandColors.blue;
32+
};
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"use client";;
2+
import { zodResolver } from "@hookform/resolvers/zod";
3+
import { ArrowRight, Loader2 } from "lucide-react";
4+
import { useForm } from "react-hook-form";
5+
6+
import { Button } from "@/components/ui/button";
7+
import {
8+
Form,
9+
FormControl,
10+
FormDescription,
11+
FormField,
12+
FormItem,
13+
FormLabel,
14+
FormMessage,
15+
} from "@/components/ui/form";
16+
import { Input } from "@/components/ui/input";
17+
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
18+
19+
import { brandColors, getColorNameFromValue } from "./color-utils";
20+
import { organizationFormSchema, OrganizationFormValues } from "./types";
21+
22+
interface OrganizationDetailsFormProps {
23+
onSubmit: (values: OrganizationFormValues) => void;
24+
initialData?: OrganizationFormValues | null;
25+
loading?: boolean;
26+
}
27+
28+
export function OrganizationDetailsForm({
29+
onSubmit,
30+
initialData,
31+
loading = false,
32+
}: OrganizationDetailsFormProps) {
33+
const form = useForm<OrganizationFormValues>({
34+
resolver: zodResolver(organizationFormSchema),
35+
defaultValues: initialData || {
36+
name: "",
37+
email: "",
38+
color: "blue",
39+
},
40+
});
41+
42+
const handleSubmit = (values: OrganizationFormValues) => {
43+
onSubmit(values);
44+
};
45+
46+
return (
47+
<Form {...form}>
48+
<form
49+
onSubmit={form.handleSubmit(handleSubmit)}
50+
className="flex flex-col space-y-6 py-6"
51+
>
52+
<FormField
53+
control={form.control}
54+
name="name"
55+
render={({ field }) => (
56+
<FormItem>
57+
<FormLabel className="text-base font-medium">
58+
Organization Name
59+
</FormLabel>
60+
<FormControl>
61+
<Input placeholder="Acme Inc." {...field} className="h-10" />
62+
</FormControl>
63+
<FormMessage />
64+
</FormItem>
65+
)}
66+
/>
67+
68+
<FormField
69+
control={form.control}
70+
name="email"
71+
render={({ field }) => (
72+
<FormItem>
73+
<FormLabel className="text-base font-medium">
74+
Contact Email
75+
</FormLabel>
76+
<FormControl>
77+
<Input
78+
type="email"
79+
placeholder="admin@yourcompany.com"
80+
{...field}
81+
className="h-10"
82+
/>
83+
</FormControl>
84+
<FormDescription>
85+
This is the email address that will be used for display
86+
purposes.
87+
</FormDescription>
88+
<FormMessage />
89+
</FormItem>
90+
)}
91+
/>
92+
93+
<FormField
94+
control={form.control}
95+
name="color"
96+
render={({ field }) => (
97+
<FormItem className="space-y-3">
98+
<FormLabel className="text-base font-medium">
99+
Brand Color
100+
</FormLabel>
101+
<FormControl>
102+
<RadioGroup
103+
className="flex flex-wrap gap-3 pt-1"
104+
value={
105+
Object.keys(brandColors).includes(field.value)
106+
? field.value
107+
: getColorNameFromValue(field.value)
108+
}
109+
onValueChange={(value) => {
110+
field.onChange(
111+
brandColors[value as keyof typeof brandColors] || value,
112+
);
113+
}}
114+
>
115+
{Object.entries(brandColors).map(
116+
([colorName, colorValue]) => (
117+
<div
118+
key={colorName}
119+
className="flex flex-col items-center gap-1.5"
120+
>
121+
<RadioGroupItem
122+
value={colorName}
123+
id={`color-${colorName}`}
124+
aria-label={colorName}
125+
className={`size-8 rounded-full border-2 shadow-sm transition-all hover:scale-110 ${
126+
field.value === colorValue ||
127+
field.value === colorName
128+
? "ring-2 ring-offset-2"
129+
: ""
130+
}`}
131+
style={{
132+
backgroundColor: colorValue,
133+
borderColor: colorValue,
134+
}}
135+
/>
136+
<span className="text-xs capitalize text-muted-foreground">
137+
{colorName}
138+
</span>
139+
</div>
140+
),
141+
)}
142+
</RadioGroup>
143+
</FormControl>
144+
<FormMessage />
145+
</FormItem>
146+
)}
147+
/>
148+
149+
<div className="flex justify-end pt-2">
150+
<Button type="submit" className="min-w-24 gap-1" disabled={loading}>
151+
{loading ? (
152+
<>
153+
<Loader2 className="size-4 animate-spin" />
154+
<span>Creating...</span>
155+
</>
156+
) : (
157+
<>
158+
<span>Continue</span>
159+
<ArrowRight className="size-4" />
160+
</>
161+
)}
162+
</Button>
163+
</div>
164+
</form>
165+
</Form>
166+
);
167+
}

0 commit comments

Comments
 (0)