Compare commits

..

5 Commits

Author SHA1 Message Date
Piyush Gupta
7540c64fdf fix: sonarqube target 2025-04-08 22:22:12 +05:30
Matti Nannt
3b815e22e3 chore: add docker build check github action (#4875)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-04-08 13:26:48 +00:00
Matti Nannt
4d4a5c0e64 fix: solve sonarqube security hotspots (#5292) 2025-04-08 14:58:24 +02:00
Anshuman Pandey
0e89293974 fix: appUrl fix in iOS and android packages (#5295) 2025-04-08 14:51:30 +02:00
Jakob Schott
c306911b3a fix: replace hard-coded alerts with alert component (#5156)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
2025-04-08 10:26:28 +00:00
14 changed files with 278 additions and 224 deletions

View File

@@ -0,0 +1,163 @@
name: Docker Build Validation
on:
pull_request:
branches:
- main
merge_group:
branches:
- main
workflow_dispatch:
permissions:
contents: read
jobs:
validate-docker-build:
name: Validate Docker Build
runs-on: ubuntu-latest
# Add PostgreSQL service container
services:
postgres:
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: formbricks
ports:
- 5432:5432
# Health check to ensure PostgreSQL is ready before using it
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker Image
uses: docker/build-push-action@v6
with:
context: .
file: ./apps/web/Dockerfile
push: false
load: true
tags: formbricks-test:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
secrets: |
database_url=${{ secrets.DUMMY_DATABASE_URL }}
encryption_key=${{ secrets.DUMMY_ENCRYPTION_KEY }}
- name: Verify PostgreSQL Connection
run: |
echo "Verifying PostgreSQL connection..."
# Install PostgreSQL client to test connection
sudo apt-get update && sudo apt-get install -y postgresql-client
# Test connection using psql
PGPASSWORD=test psql -h localhost -U test -d formbricks -c "\dt" || echo "Failed to connect to PostgreSQL"
# Show network configuration
echo "Network configuration:"
ip addr show
netstat -tulpn | grep 5432 || echo "No process listening on port 5432"
- name: Test Docker Image with Health Check
shell: bash
run: |
echo "🧪 Testing if the Docker image starts correctly..."
# Add extra docker run args to support host.docker.internal on Linux
DOCKER_RUN_ARGS="--add-host=host.docker.internal:host-gateway"
# Start the container with host.docker.internal pointing to the host
docker run --name formbricks-test \
$DOCKER_RUN_ARGS \
-p 3000:3000 \
-e DATABASE_URL="postgresql://test:test@host.docker.internal:5432/formbricks" \
-e ENCRYPTION_KEY="${{ secrets.DUMMY_ENCRYPTION_KEY }}" \
-d formbricks-test:${{ github.sha }}
# Give it more time to start up
echo "Waiting 45 seconds for application to start..."
sleep 45
# Check if the container is running
if [ "$(docker inspect -f '{{.State.Running}}' formbricks-test)" != "true" ]; then
echo "❌ Container failed to start properly!"
docker logs formbricks-test
exit 1
else
echo "✅ Container started successfully!"
fi
# Try connecting to PostgreSQL from inside the container
echo "Testing PostgreSQL connection from inside container..."
docker exec formbricks-test sh -c 'apt-get update && apt-get install -y postgresql-client && PGPASSWORD=test psql -h host.docker.internal -U test -d formbricks -c "\dt" || echo "Failed to connect to PostgreSQL from container"'
# Try to access the health endpoint
echo "🏥 Testing /health endpoint..."
MAX_RETRIES=10
RETRY_COUNT=0
HEALTH_CHECK_SUCCESS=false
set +e # Disable exit on error to allow for retries
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "Attempt $RETRY_COUNT of $MAX_RETRIES..."
# Show container logs before each attempt to help debugging
if [ $RETRY_COUNT -gt 1 ]; then
echo "📋 Current container logs:"
docker logs --tail 20 formbricks-test
fi
# Get detailed curl output for debugging
HTTP_OUTPUT=$(curl -v -s -m 30 http://localhost:3000/health 2>&1)
CURL_EXIT_CODE=$?
echo "Curl exit code: $CURL_EXIT_CODE"
echo "Curl output: $HTTP_OUTPUT"
if [ $CURL_EXIT_CODE -eq 0 ]; then
STATUS_CODE=$(echo "$HTTP_OUTPUT" | grep -oP "HTTP/\d(\.\d)? \K\d+")
echo "Status code detected: $STATUS_CODE"
if [ "$STATUS_CODE" = "200" ]; then
echo "✅ Health check successful!"
HEALTH_CHECK_SUCCESS=true
break
else
echo "❌ Health check returned non-200 status code: $STATUS_CODE"
fi
else
echo "❌ Curl command failed with exit code: $CURL_EXIT_CODE"
fi
echo "Waiting 15 seconds before next attempt..."
sleep 15
done
# Show full container logs for debugging
echo "📋 Full container logs:"
docker logs formbricks-test
# Clean up the container
echo "🧹 Cleaning up..."
docker rm -f formbricks-test
# Exit with failure if health check did not succeed
if [ "$HEALTH_CHECK_SUCCESS" != "true" ]; then
echo "❌ Health check failed after $MAX_RETRIES attempts"
exit 1
fi
echo "✨ Docker validation complete - all checks passed!"

View File

@@ -4,7 +4,7 @@ on:
push:
branches:
- main
pull_request:
pull_request_target:
types: [opened, synchronize, reopened]
merge_group:
permissions:

View File

@@ -22,7 +22,7 @@ RUN npm install -g corepack@latest
RUN corepack enable
# Install necessary build tools and compilers
RUN apk update && apk add --no-cache g++ cmake make gcc python3 openssl-dev jq
RUN apk update && apk add --no-cache cmake g++ gcc jq make openssl-dev python3
# BuildKit secret handling without hardcoded fallback values
# This approach relies entirely on secrets passed from GitHub Actions
@@ -40,8 +40,6 @@ RUN echo '#!/bin/sh' > /tmp/read-secrets.sh && \
echo 'exec "$@"' >> /tmp/read-secrets.sh && \
chmod +x /tmp/read-secrets.sh
ARG SENTRY_AUTH_TOKEN
# Increase Node.js memory limit as a regular build argument
ARG NODE_OPTIONS="--max_old_space_size=4096"
ENV NODE_OPTIONS=${NODE_OPTIONS}
@@ -87,31 +85,60 @@ RUN apk add --no-cache curl \
WORKDIR /home/nextjs
COPY --from=installer /app/apps/web/next.config.mjs .
COPY --from=installer /app/apps/web/package.json .
# Leverage output traces to reduce image size
# Ensure no write permissions are assigned to the copied resources
COPY --from=installer --chown=nextjs:nextjs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nextjs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nextjs /app/apps/web/public ./apps/web/public
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/schema.prisma ./packages/database/schema.prisma
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/package.json ./packages/database/package.json
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/migration ./packages/database/migration
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/src ./packages/database/src
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/node_modules ./packages/database/node_modules
COPY --from=installer --chown=nextjs:nextjs /app/packages/logger/dist ./packages/database/node_modules/@formbricks/logger/dist
RUN chmod -R 755 ./
COPY --from=installer /app/apps/web/next.config.mjs .
RUN chmod 644 ./next.config.mjs
COPY --from=installer /app/apps/web/package.json .
RUN chmod 644 ./package.json
COPY --from=installer --chown=nextjs:nextjs /app/apps/web/.next/static ./apps/web/.next/static
RUN chmod -R 755 ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nextjs /app/apps/web/public ./apps/web/public
RUN chmod -R 755 ./apps/web/public
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/schema.prisma ./packages/database/schema.prisma
RUN chmod 644 ./packages/database/schema.prisma
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/package.json ./packages/database/package.json
RUN chmod 644 ./packages/database/package.json
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/migration ./packages/database/migration
RUN chmod -R 755 ./packages/database/migration
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/src ./packages/database/src
RUN chmod -R 755 ./packages/database/src
COPY --from=installer --chown=nextjs:nextjs /app/packages/database/node_modules ./packages/database/node_modules
RUN chmod -R 755 ./packages/database/node_modules
COPY --from=installer --chown=nextjs:nextjs /app/packages/logger/dist ./packages/database/node_modules/@formbricks/logger/dist
RUN chmod -R 755 ./packages/database/node_modules/@formbricks/logger/dist
# Copy Prisma-specific generated files
COPY --from=installer --chown=nextjs:nextjs /app/node_modules/@prisma/client ./node_modules/@prisma/client
RUN chmod -R 755 ./node_modules/@prisma/client
COPY --from=installer --chown=nextjs:nextjs /app/node_modules/.prisma ./node_modules/.prisma
RUN chmod -R 755 ./node_modules/.prisma
COPY --from=installer --chown=nextjs:nextjs /prisma_version.txt .
COPY /docker/cronjobs /app/docker/cronjobs
RUN chmod 644 ./prisma_version.txt
COPY /docker/cronjobs /app/docker/cronjobs
RUN chmod -R 755 /app/docker/cronjobs
# Copy required dependencies
COPY --from=installer /app/node_modules/@paralleldrive/cuid2 ./node_modules/@paralleldrive/cuid2
RUN chmod -R 755 ./node_modules/@paralleldrive/cuid2
COPY --from=installer /app/node_modules/@noble/hashes ./node_modules/@noble/hashes
RUN chmod -R 755 ./node_modules/@noble/hashes
COPY --from=installer /app/node_modules/zod ./node_modules/zod
RUN chmod -R 755 ./node_modules/zod
RUN npm install -g tsx typescript prisma pino-pretty

View File

@@ -2,6 +2,7 @@
import { getOrganizationAccessKeyDisplayName } from "@/modules/organization/settings/api-keys/lib/utils";
import { TOrganizationProject } from "@/modules/organization/settings/api-keys/types/api-keys";
import { Alert, AlertTitle } from "@/modules/ui/components/alert";
import { Button } from "@/modules/ui/components/button";
import {
DropdownMenu,
@@ -15,7 +16,7 @@ import { Modal } from "@/modules/ui/components/modal";
import { Switch } from "@/modules/ui/components/switch";
import { ApiKeyPermission } from "@prisma/client";
import { useTranslate } from "@tolgee/react";
import { AlertTriangleIcon, ChevronDownIcon, Trash2Icon } from "lucide-react";
import { ChevronDownIcon, Trash2Icon } from "lucide-react";
import { Fragment, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
@@ -389,11 +390,9 @@ export const AddApiKeyModal = ({
</div>
</div>
</div>
<div className="flex items-center rounded-lg border border-slate-200 bg-slate-100 p-2 text-sm text-slate-700">
<AlertTriangleIcon className="mx-3 h-12 w-12 text-amber-500" />
<p>{t("environments.project.api_keys.api_key_security_warning")}</p>
</div>
<Alert variant="warning">
<AlertTitle>{t("environments.project.api_keys.api_key_security_warning")}</AlertTitle>
</Alert>
</div>
</div>
<div className="flex justify-end border-t border-slate-200 p-6">

View File

@@ -1,6 +1,7 @@
"use client";
import { getDefaultEndingCard } from "@/app/lib/templates";
import { Alert, AlertButton, AlertDescription, AlertTitle } from "@/modules/ui/components/alert";
import { Badge } from "@/modules/ui/components/badge";
import { Label } from "@/modules/ui/components/label";
import { RadioGroup, RadioGroupItem } from "@/modules/ui/components/radio-group";
@@ -8,8 +9,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { Environment } from "@prisma/client";
import * as Collapsible from "@radix-ui/react-collapsible";
import { useTranslate } from "@tolgee/react";
import { AlertCircleIcon, CheckIcon, LinkIcon, MonitorIcon } from "lucide-react";
import Link from "next/link";
import { CheckIcon, LinkIcon, MonitorIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { TSegment } from "@formbricks/types/segment";
@@ -106,7 +106,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
className="h-full w-full cursor-pointer"
id="howToSendCardTrigger">
<div className="inline-flex px-4 py-4">
<div className="flex items-center pl-2 pr-5">
<div className="flex items-center pr-5 pl-2">
<CheckIcon
strokeWidth={3}
className="h-7 w-7 rounded-full border border-green-300 bg-green-100 p-1.5 text-green-600"
@@ -171,23 +171,25 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
</div>
<p className="mt-2 text-xs font-normal text-slate-600">{option.description}</p>
{localSurvey.type === option.id && option.alert && (
<div className="mt-2 flex items-center space-x-3 rounded-lg border border-amber-200 bg-amber-50 px-4 py-2">
<AlertCircleIcon className="h-5 w-5 text-amber-500" />
<div className="text-amber-800">
<p className="text-xs font-semibold">
{t("environments.surveys.edit.formbricks_sdk_is_not_connected")}
</p>
<p className="text-xs font-normal">
<Link
href={`/environments/${environment.id}/project/${option.id}-connection`}
className="underline hover:text-amber-900"
target="_blank">
{t("common.connect_formbricks")}
</Link>{" "}
{t("environments.surveys.edit.and_launch_surveys_in_your_website_or_app")}
</p>
</div>
</div>
<Alert variant="warning" className="mt-2">
<AlertTitle>
{t("environments.surveys.edit.formbricks_sdk_is_not_connected")}
</AlertTitle>
<AlertDescription>
{t("common.connect_formbricks") +
" " +
t("environments.surveys.edit.and_launch_surveys_in_your_website_or_app")}
</AlertDescription>
<AlertButton
onClick={() =>
window.open(
`/environments/${environment.id}/project/${option.id}-connection`,
"_blank"
)
}>
{t("common.connect_formbricks")}
</AlertButton>
</Alert>
)}
</div>
</div>

View File

@@ -7,6 +7,7 @@ import {
} from "@/modules/survey/editor/types/survey-follow-up";
import FollowUpActionMultiEmailInput from "@/modules/survey/follow-ups/components/follow-up-action-multi-email-input";
import { getQuestionIconMap } from "@/modules/survey/lib/questions";
import { Alert, AlertTitle } from "@/modules/ui/components/alert";
import { Button } from "@/modules/ui/components/button";
import { Checkbox } from "@/modules/ui/components/checkbox";
import { Editor } from "@/modules/ui/components/editor";
@@ -423,17 +424,13 @@ export const FollowUpModal = ({
</Select>
{triggerType === "endings" && !localSurvey.endings.length ? (
<div className="mt-4 flex items-start text-yellow-600">
<TriangleAlertIcon
className="mr-2 h-5 min-h-5 w-5 min-w-5"
aria-hidden="true"
/>
<p className="text-sm">
<Alert variant="warning" size="small">
<AlertTitle>
{t(
"environments.surveys.edit.follow_ups_modal_trigger_type_ending_warning"
)}
</p>
</div>
</AlertTitle>
</Alert>
) : null}
</div>
</div>

View File

@@ -1,74 +0,0 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { Alert, AlertButton, AlertDescription, AlertTitle } from "./index";
describe("Alert", () => {
it("renders with default variant", () => {
render(
<Alert>
<AlertTitle>Test Title</AlertTitle>
<AlertDescription>Test Description</AlertDescription>
</Alert>
);
expect(screen.getByRole("alert")).toBeInTheDocument();
expect(screen.getByText("Test Title")).toBeInTheDocument();
expect(screen.getByText("Test Description")).toBeInTheDocument();
});
it("renders with different variants", () => {
const variants = ["default", "error", "warning", "info", "success"] as const;
variants.forEach((variant) => {
const { container } = render(
<Alert variant={variant}>
<AlertTitle>Test Title</AlertTitle>
</Alert>
);
expect(container.firstChild).toHaveClass(
variant === "default" ? "text-foreground" : `text-${variant}-foreground`
);
});
});
it("renders with different sizes", () => {
const sizes = ["default", "small"] as const;
sizes.forEach((size) => {
const { container } = render(
<Alert size={size}>
<AlertTitle>Test Title</AlertTitle>
</Alert>
);
expect(container.firstChild).toHaveClass(size === "default" ? "py-3" : "py-2");
});
});
it("renders with button and handles click", () => {
const handleClick = vi.fn();
render(
<Alert>
<AlertTitle>Test Title</AlertTitle>
<AlertButton onClick={handleClick}>Click me</AlertButton>
</Alert>
);
const button = screen.getByText("Click me");
expect(button).toBeInTheDocument();
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
it("applies custom className", () => {
const { container } = render(
<Alert className="custom-class">
<AlertTitle>Test Title</AlertTitle>
</Alert>
);
expect(container.firstChild).toHaveClass("custom-class");
});
});

View File

@@ -21,23 +21,23 @@ const AlertContext = createContext<AlertContextValue>({
const useAlertContext = () => useContext(AlertContext);
// Define alert styles with variants
const alertVariants = cva("relative w-full rounded-lg border [&>svg]:size-4 [&>svg]:text-foreground", {
const alertVariants = cva("relative w-full rounded-lg border [&>svg]:size-4", {
variants: {
variant: {
default: "text-foreground border-border",
error:
"text-error-foreground border-error/50 [&_button]:bg-error-background [&_button]:text-error-foreground [&_button:hover]:bg-error-background-muted",
"text-error-foreground [&>svg]:text-error border-error/50 [&_button]:bg-error-background [&_button]:text-error-foreground [&_button:hover]:bg-error-background-muted",
warning:
"text-warning-foreground border-warning/50 [&_button]:bg-warning-background [&_button]:text-warning-foreground [&_button:hover]:bg-warning-background-muted",
info: "text-info-foreground border-info/50 [&_button]:bg-info-background [&_button]:text-info-foreground [&_button:hover]:bg-info-background-muted",
"text-warning-foreground [&>svg]:text-warning border-warning/50 [&_button]:bg-warning-background [&_button]:text-warning-foreground [&_button:hover]:bg-warning-background-muted",
info: "text-info-foreground [&>svg]:text-info border-info/50 [&_button]:bg-info-background [&_button]:text-info-foreground [&_button:hover]:bg-info-background-muted",
success:
"text-success-foreground border-success/50 [&_button]:bg-success-background [&_button]:text-success-foreground [&_button:hover]:bg-success-background-muted",
"text-success-foreground [&>svg]:text-success border-success/50 [&_button]:bg-success-background [&_button]:text-success-foreground [&_button:hover]:bg-success-background-muted",
},
size: {
default:
"py-3 px-4 text-sm grid grid-cols-[1fr_auto] grid-rows-[auto_auto] gap-x-3 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7",
"py-3 px-4 text-sm grid grid-cols-[2fr_auto] grid-rows-[auto_auto] gap-y-0.5 gap-x-3 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7",
small:
"px-3 py-2 text-xs flex items-center justify-between gap-2 [&>svg]:flex-shrink-0 [&_button]:text-xs [&_button]:bg-transparent [&_button:hover]:bg-transparent [&>svg~*]:pl-0",
"px-4 py-2 text-xs flex items-center gap-2 [&>svg]:flex-shrink-0 [&_button]:bg-transparent [&_button:hover]:bg-transparent [&>svg~*]:pl-0",
},
},
defaultVariants: {
@@ -58,7 +58,7 @@ const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, size, ...props }, ref) => {
const variantIcon = variant ? (variant !== "default" ? alertVariantIcons[variant] : null) : null;
const variantIcon = variant && variant !== "default" ? alertVariantIcons[variant] : null;
return (
<AlertContext.Provider value={{ variant, size }}>
@@ -78,8 +78,8 @@ const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<H
<h5
ref={ref}
className={cn(
"col-start-1 row-start-1 font-medium leading-none tracking-tight",
size === "small" ? "min-w-0 flex-shrink truncate" : "col-start-1 row-start-1",
"col-start-1 row-start-1 font-medium tracking-tight",
size === "small" ? "flex-shrink truncate" : "col-start-1 row-start-1",
className
)}
{...props}
@@ -99,9 +99,7 @@ const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttrib
ref={ref}
className={cn(
"[&_p]:leading-relaxed",
size === "small"
? "hidden min-w-0 flex-shrink flex-grow truncate opacity-80 sm:block" // Hidden on very small screens, limited width
: "col-start-1 row-start-2",
size === "small" ? "flex-shrink flex-grow-0 truncate" : "col-start-1 row-start-2",
className
)}
{...props}
@@ -124,7 +122,7 @@ const AlertButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
className={cn(
"self-end",
alertSize === "small"
? "-my-2 -mr-3 ml-auto flex-shrink-0"
? "-my-2 -mr-4 ml-auto flex-shrink-0"
: "col-start-2 row-span-2 row-start-1 flex items-center justify-center"
)}>
<Button ref={ref} variant={buttonVariant} size={buttonSize} className={className} {...props}>

View File

@@ -1,62 +0,0 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { Default, Error, Info, Small, Success, Warning, withButtonAndIcon } from "./stories";
describe("Alert Stories", () => {
const renderStory = (Story: any) => {
return render(Story.render(Story.args));
};
afterEach(() => {
cleanup();
});
it("renders Default story", () => {
renderStory(Default);
expect(screen.getByText("Alert Title")).toBeInTheDocument();
expect(screen.getByText("This is an important notification.")).toBeInTheDocument();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
it("renders Small story", () => {
renderStory(Small);
expect(screen.getByText("Information Alert")).toBeInTheDocument();
expect(screen.getByRole("button")).toBeInTheDocument();
expect(screen.getByText("Learn more")).toBeInTheDocument();
});
it("renders withButtonAndIcon story", () => {
renderStory(withButtonAndIcon);
expect(screen.getByText("Alert Title")).toBeInTheDocument();
expect(screen.getByRole("button")).toBeInTheDocument();
expect(screen.getByText("Learn more")).toBeInTheDocument();
});
it("renders Error story", () => {
renderStory(Error);
expect(screen.getByText("Error Alert")).toBeInTheDocument();
expect(screen.getByText("Your session has expired. Please log in again.")).toBeInTheDocument();
expect(screen.getByText("Log in")).toBeInTheDocument();
});
it("renders Warning story", () => {
renderStory(Warning);
expect(screen.getByText("Warning Alert")).toBeInTheDocument();
expect(screen.getByText("You are editing sensitive data. Be cautious")).toBeInTheDocument();
expect(screen.getByText("Proceed")).toBeInTheDocument();
});
it("renders Info story", () => {
renderStory(Info);
expect(screen.getByText("Info Alert")).toBeInTheDocument();
expect(screen.getByText("There was an update to your application.")).toBeInTheDocument();
expect(screen.getByText("Refresh")).toBeInTheDocument();
});
it("renders Success story", () => {
renderStory(Success);
expect(screen.getByText("Success Alert")).toBeInTheDocument();
expect(screen.getByText("This worked! Please proceed.")).toBeInTheDocument();
expect(screen.getByText("Close")).toBeInTheDocument();
});
});

View File

@@ -1,5 +1,6 @@
import { Alert, AlertButton, AlertTitle } from "@/modules/ui/components/alert";
import { getTranslate } from "@/tolgee/server";
import { LightbulbIcon } from "lucide-react";
import Link from "next/link";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service";
@@ -19,19 +20,19 @@ export const EnvironmentNotice = async ({ environmentId, subPageUrl }: Environme
const otherEnvironmentId = environments.filter((e) => e.id !== environment.id)[0].id;
return (
<div className="mt-4 flex max-w-4xl items-center space-y-4 rounded-lg border border-blue-100 bg-blue-50 p-4 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base">
<LightbulbIcon className="mr-3 h-4 w-4 text-blue-400" />
<p className="text-sm">
{t("common.environment_notice", { environment: environment.type })}
<a
href={`${WEBAPP_URL}/environments/${otherEnvironmentId}${subPageUrl}`}
className="ml-1 cursor-pointer text-sm underline">
{t("common.switch_to", {
environment: environment.type === "production" ? "Development" : "Production",
})}
.
</a>
</p>
<div>
<Alert variant="info" size="small" className="max-w-4xl">
<AlertTitle>{t("common.environment_notice", { environment: environment.type })}</AlertTitle>
<AlertButton>
<Link
href={`${WEBAPP_URL}/environments/${otherEnvironmentId}${subPageUrl}`}
className="ml-1 cursor-pointer underline">
{t("common.switch_to", {
environment: environment.type === "production" ? "Development" : "Production",
})}
</Link>
</AlertButton>
</Alert>
</div>
);
};

View File

@@ -1,10 +1,10 @@
"use client";
import { AdvancedOptionToggle } from "@/modules/ui/components/advanced-option-toggle";
import { Alert, AlertTitle } from "@/modules/ui/components/alert";
import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { useTranslate } from "@tolgee/react";
import { AlertTriangle } from "lucide-react";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { checkForYoutubeUrl, convertToEmbedUrl, extractYoutubeId } from "@formbricks/lib/utils/videoUpload";
@@ -112,10 +112,9 @@ export const VideoSettings = ({
</div>
{showPlatformWarning && (
<div className="flex items-center space-x-2 rounded-md border bg-slate-100 p-2 text-xs text-slate-600">
<AlertTriangle className="h-6 w-6" />
<p>{t("environments.surveys.edit.invalid_video_url_warning")}</p>
</div>
<Alert variant="warning" size="small">
<AlertTitle>{t("environments.surveys.edit.invalid_video_url_warning")}</AlertTitle>
</Alert>
)}
{isYoutubeLink && (

View File

@@ -128,7 +128,7 @@ class FormbricksViewModel : ViewModel() {
val jsonObject = JsonObject()
environmentDataHolder.getSurveyJson(surveyId).let { jsonObject.add("survey", it) }
jsonObject.addProperty("isBrandingEnabled", true)
jsonObject.addProperty("apiHost", Formbricks.appUrl)
jsonObject.addProperty("appUrl", Formbricks.appUrl)
jsonObject.addProperty("languageCode", Formbricks.language)
jsonObject.addProperty("environmentId", Formbricks.environmentId)
jsonObject.addProperty("contactId", UserManager.contactId)

View File

@@ -92,7 +92,7 @@ private class WebViewData {
data["survey"] = environmentResponse.getSurveyJson(forSurveyId: surveyId)
data["isBrandingEnabled"] = true
data["languageCode"] = Formbricks.language
data["apiHost"] = Formbricks.appUrl
data["appUrl"] = Formbricks.appUrl
data["environmentId"] = Formbricks.environmentId
data["contactId"] = UserManager.shared.contactId

View File

@@ -10,6 +10,10 @@
"dependsOn": ["@formbricks/api#build"],
"persistent": true
},
"@formbricks/database#build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"@formbricks/database#lint": {
"dependsOn": ["@formbricks/logger#build"]
},