Compare commits

..

9 Commits

Author SHA1 Message Date
Johannes d0f2be1469 fix embed 2024-05-13 18:18:38 +02:00
Johannes 09a3284682 remove old nav 2024-05-13 17:44:53 +02:00
Johannes 4845af3f2c Merge branch 'main' of https://github.com/formbricks/formbricks into shubham/saturn-integration-v2 2024-05-13 17:42:05 +02:00
Shubham Palriwala 6b0f70a80c Merge branch 'main' into shubham/saturn-integration-v2 2024-05-07 11:26:56 +05:30
Johannes e2ad434b93 update menu items 2024-04-16 09:59:59 +02:00
Johannes 000b950ee6 Merge branch 'main' into shubham/saturn-integration-v2 2024-04-16 09:05:37 +02:00
ShubhamPalriwala 7562072cd4 chore: remove console log 2024-04-16 11:36:25 +05:30
ShubhamPalriwala 9814e87a41 fix: show toast if opening chat & its already opened 2024-04-16 11:35:20 +05:30
ShubhamPalriwala c7c5a28395 init: saturn integration 2024-04-15 16:13:58 +05:30
667 changed files with 6404 additions and 7162 deletions
@@ -0,0 +1,26 @@
name: Build formbricks-com
on:
workflow_call:
jobs:
build:
name: Build Formbricks-com
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Install pnpm
uses: pnpm/action-setup@v2
- name: Install dependencies
run: pnpm install --config.platform=linux --config.architecture=x64
- name: Build Formbricks-com
run: pnpm build --filter=formbricks-com...
+131
View File
@@ -0,0 +1,131 @@
name: Kamal Deploy
concurrency:
group: deploy-to-kamal
cancel-in-progress: false
on:
workflow_dispatch:
push:
branches:
- main
jobs:
Deploy:
runs-on: ubuntu-latest
environment: production
env:
DOCKER_BUILDKIT: 1
IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }}
WEBAPP_URL: ${{ vars.WEBAPP_URL }}
MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }}
NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }}
MAIL_FROM: ${{ secrets.MAIL_FROM }}
SMTP_HOST: ${{ secrets.SMTP_HOST }}
SMTP_PORT: ${{ secrets.SMTP_PORT }}
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
PRIVACY_URL: ${{ vars.PRIVACY_URL }}
TERMS_URL: ${{ vars.TERMS_URL }}
IMPRINT_URL: ${{ vars.IMPRINT_URL }}
GITHUB_ID: ${{ secrets.FB_GITHUB_ID }}
GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }}
AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }}
AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }}
OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }}
OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }}
OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }}
OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }}
OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }}
NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }}
NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }}
SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }}
SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }}
GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }}
GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }}
AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }}
ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }}
DEFAULT_TEAM_ID: ${{ vars.DEFAULT_TEAM_ID }}
ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }}
CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }}
CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }}
NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }}
NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }}
NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }}
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }}
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }}
NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NODE_ENV: production
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
S3_REGION: ${{ vars.S3_REGION }}
S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }}
OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }}
RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }}
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_NAME: ${{ secrets.DB_NAME }}
REDIS_URL: ${{ secrets.REDIS_URL }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.0
bundler-cache: true
- name: Install dependencies
run: |
gem install kamal
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- name: Create builder
run: docker buildx create --use --name formbricks-gh-actions-builder
if: steps.buildx.outputs.should_create_builder == 'true'
- name: Push env variables to Kamal
run: |
kamal() { command kamal "$@" -c kamal/deploy.yml; }
kamal env push
- name: Run deploy command
run: |
kamal() { command kamal "$@" -c kamal/deploy.yml; }
set +e
DEPLOY_OUTPUT=$(kamal deploy 2>&1)
DEPLOY_EXIT_CODE=$?
echo "$DEPLOY_OUTPUT"
if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then
echo "Deployment reported healthy container. Considering as success."
kamal lock release
exit 0
else
exit $DEPLOY_EXIT_CODE
fi
shell: bash
+128
View File
@@ -0,0 +1,128 @@
name: Kamal Setup
concurrency:
group: setup-kamal
cancel-in-progress: false
on:
workflow_dispatch: # Only to be triggered when accessories are updated
jobs:
Setup:
runs-on: ubuntu-latest
environment: production
env:
DOCKER_BUILDKIT: 1
IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }}
WEBAPP_URL: ${{ vars.WEBAPP_URL }}
NEXTAUTH_URL: ${{ vars.NEXTAUTH_URL }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }}
SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }}
MAIL_FROM: ${{ secrets.MAIL_FROM }}
SMTP_HOST: ${{ secrets.SMTP_HOST }}
SMTP_PORT: ${{ secrets.SMTP_PORT }}
SMTP_USER: ${{ secrets.SMTP_USER }}
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
PRIVACY_URL: ${{ vars.PRIVACY_URL }}
TERMS_URL: ${{ vars.TERMS_URL }}
IMPRINT_URL: ${{ vars.IMPRINT_URL }}
GITHUB_ID: ${{ secrets.FB_GITHUB_ID }}
GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }}
AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }}
AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }}
OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }}
OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }}
OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }}
OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }}
OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }}
CRON_SECRET: ${{ secrets.CRON_SECRET }}
ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }}
NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }}
NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }}
SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }}
SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}
GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }}
GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }}
GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }}
AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }}
ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }}
DEFAULT_TEAM_ID: ${{ vars.DEFAULT_TEAM_ID }}
ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }}
CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }}
CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }}
NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }}
NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }}
NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }}
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }}
NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }}
NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NODE_ENV: production
CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }}
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
S3_REGION: ${{ vars.S3_REGION }}
S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }}
OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }}
RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }}
KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
DB_HOST: ${{ secrets.DB_HOST }}
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_NAME: ${{ secrets.DB_NAME }}
REDIS_URL: ${{ secrets.REDIS_URL }}
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.0
bundler-cache: true
- name: Install dependencies
run: |
gem install kamal
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- name: Create builder
run: docker buildx create --use --name formbricks-gh-actions-builder
if: steps.buildx.outputs.should_create_builder == 'true'
- name: Push env variables to Kamal
run: |
kamal() { command kamal "$@" -c kamal/deploy.yml; }
kamal env push
- name: Run setup command
run: |
kamal() { command kamal "$@" -c kamal/deploy.yml; }
set +e
DEPLOY_OUTPUT=$(kamal setup 2>&1)
DEPLOY_EXIT_CODE=$?
echo "$DEPLOY_OUTPUT"
if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then
echo "Deployment reported healthy container. Considering as success."
kamal lock release
exit 0
else
exit $DEPLOY_EXIT_CODE
fi
shell: bash
+6 -13
View File
@@ -1,4 +1,9 @@
name: Docker Release to GitHub
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
workflow_dispatch:
@@ -48,17 +53,6 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set tags based on event type
id: set-tags
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
echo "::set-output name=tags::latest,${{ github.ref }}"
fi
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "::set-output name=tags::experimental"
fi
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
@@ -66,7 +60,6 @@ jobs:
uses: docker/metadata-action@v5 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: ${{ steps.set-tags.outputs.tags }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
+44
View File
@@ -0,0 +1,44 @@
name: Release on Dockerhub
on:
push:
tags:
- "v*"
jobs:
release-image-on-dockerhub:
name: Release on Dockerhub
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/formbricks?schema=public"
steps:
- name: Checkout Repo
uses: actions/checkout@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Get Release Tag
id: extract_release_tag
run: |
TAG=${{ github.ref }}
TAG=${TAG#refs/tags/v}
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
file: ./apps/web/Dockerfile
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/formbricks:${{ env.RELEASE_TAG }}
${{ secrets.DOCKER_USERNAME }}/formbricks:latest
+3 -3
View File
@@ -1,6 +1,6 @@
import { Sidebar } from "./Sidebar";
import Sidebar from "./Sidebar";
export const LayoutApp = ({ children }: { children: React.ReactNode }) => {
export default function LayoutApp({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-full">
{/* Static sidebar for desktop */}
@@ -10,4 +10,4 @@ export const LayoutApp = ({ children }: { children: React.ReactNode }) => {
<div className="flex flex-1 flex-col lg:pl-64">{children}</div>
</div>
);
};
}
+2 -2
View File
@@ -26,7 +26,7 @@ const secondaryNavigation = [
{ name: "Privacy", href: "#", icon: ShieldCheckIcon },
];
export const Sidebar = () => {
export default function Sidebar({}) {
return (
<div className="flex flex-grow flex-col overflow-y-auto bg-cyan-700 pb-4 pt-5">
<nav
@@ -63,4 +63,4 @@ export const Sidebar = () => {
</nav>
</div>
);
};
}
+2 -2
View File
@@ -1,3 +1,3 @@
export const classNames = (...classes: any) => {
export function classNames(...classes: any) {
return classes.filter(Boolean).join(" ");
};
}
+2 -4
View File
@@ -3,7 +3,7 @@ import Head from "next/head";
import "../styles/globals.css";
const App = ({ Component, pageProps }: AppProps) => {
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Head>
@@ -18,6 +18,4 @@ const App = ({ Component, pageProps }: AppProps) => {
<Component {...pageProps} />
</>
);
};
export default App;
}
+2 -4
View File
@@ -1,6 +1,6 @@
import { Head, Html, Main, NextScript } from "next/document";
const Document = () => {
export default function Document() {
return (
<Html lang="en" className="h-full bg-slate-50">
<Head />
@@ -10,6 +10,4 @@ const Document = () => {
</body>
</Html>
);
};
export default Document;
}
+3 -9
View File
@@ -9,7 +9,7 @@ import fbsetup from "../../public/fb-setup.png";
declare const window: any;
const AppPage = ({}) => {
export default function AppPage({}) {
const [darkMode, setDarkMode] = useState(false);
const router = useRouter();
@@ -36,11 +36,7 @@ const AppPage = ({}) => {
if (process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID && process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST) {
const userId = "THIS-IS-A-VERY-LONG-USER-ID-FOR-TESTING";
const userInitAttributes = {
language: "de",
"Init Attribute 1": "eight",
"Init Attribute 2": "two",
};
const userInitAttributes = { language: "de", "Init Attribute 1": "eight", "Init Attribute 2": "two" };
formbricks.init({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
@@ -238,6 +234,4 @@ const AppPage = ({}) => {
</div>
</div>
);
};
export default AppPage;
}
+2 -4
View File
@@ -9,7 +9,7 @@ import fbsetup from "../../public/fb-setup.png";
declare const window: any;
const AppPage = ({}) => {
export default function AppPage({}) {
const [darkMode, setDarkMode] = useState(false);
const router = useRouter();
@@ -139,6 +139,4 @@ const AppPage = ({}) => {
</div>
</div>
);
};
export default AppPage;
}
@@ -33,7 +33,7 @@ const libraries = [
},
];
export const Libraries = () => {
export function Libraries() {
return (
<div className="my-16 xl:max-w-none">
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-slate-900/5 sm:grid-cols-2 xl:max-w-none xl:grid-cols-3 dark:border-white/5">
@@ -57,4 +57,4 @@ export const Libraries = () => {
</div>
</div>
);
};
}
@@ -80,11 +80,6 @@ formbricks.init({
You can use the setAttribute function to set any custom attribute for the user (e.g. name, plan, etc.):
<Note>
Please note that the number of different attribute classes (e.g., "Plan," "First Name," etc.) is currently
limited to 150 attributes per environment.
</Note>
<Col>
<CodeGroup title="Setting Custom Attributes">
@@ -120,4 +115,4 @@ formbricks.logout();
```
</CodeGroup>
</Col>
</Col>
@@ -135,7 +135,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui/Popover"
import { handleFeedbackSubmit, updateFeedback } from "../../lib/handleFeedbackSubmit";
export const DocsFeedback = () => {
export default function DocsFeedback() {
const router = useRouter();
const [isOpen, setIsOpen] = useState(false);
const [sharedFeedback, setSharedFeedback] = useState(false);
@@ -199,7 +199,7 @@ export const DocsFeedback = () => {
)}
</div>
);
};
}
```
</CodeGroup>
@@ -1,6 +1,6 @@
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@formbricks/ui/Accordion";
import { FaqJsonLdComponent } from "./FAQPageJsonLd";
import FaqJsonLdComponent from "./FAQPageJsonLd";
const FAQ_DATA = [
{
@@ -62,7 +62,7 @@ export const faqJsonLdData = FAQ_DATA.map((faq) => ({
acceptedAnswerText: faq.answer(),
}));
export const FAQ = () => {
export default function FAQ() {
return (
<>
<FaqJsonLdComponent data={faqJsonLdData} />
@@ -76,4 +76,4 @@ export const FAQ = () => {
</Accordion>
</>
);
};
}
@@ -2,11 +2,11 @@
import { FAQPageJsonLd } from "next-seo";
export const FaqJsonLdComponent = ({ data }) => {
export default function FaqJsonLdComponent({ data }) {
const faqEntities = data.map(({ question, answer }) => ({
questionName: question,
acceptedAnswerText: answer,
}));
return <FAQPageJsonLd mainEntity={faqEntities} />;
};
}
@@ -24,7 +24,8 @@ export const metadata = {
The Airtable integration allows you to automatically send responses to an Airtable of your choice.
<Note>
If you are on a self-hosted instance, you will need to configure this integration separately. Please follow the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance.
This feature is enabled by default in Formbricks Cloud but needs to be self-configured when running a
self-hosted version of Formbricks.
</Note>
## Formbricks Cloud
@@ -21,7 +21,9 @@ export const metadata = {
The Google Sheets integration allows you to automatically send responses to a Google Sheet of your choice.
<Note>
If you are on a self-hosted instance, you will need to configure this integration separately. Please follow the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance.
This feature is enabled by default in Formbricks Cloud but needs to be self-configured when running a
self-hosted version of Formbricks. For self-configuration, see additional setup
[below](#setup-in-self-hosted-formbricks).
</Note>
## Connect Google Sheets
@@ -27,7 +27,7 @@ export const metadata = {
Make is a powerful tool to send information between Formbricks and thousands of apps. Here's how to set it up.
<Note>
Nailed down your survey?? Any changes in the survey cause additional work in the _Scenario_. It
### Nail down your survey first ? Any changes in the survey cause additional work in the _Scenario_. It
makes sense to first settle on the survey you want to run and then get to setting up Make.
</Note>
@@ -29,7 +29,7 @@ export const metadata = {
n8n allows you to build flexible workflows focused on deep data integration. And with sharable templates and a user-friendly UI, the less technical people on your team can collaborate on them too. Unlike other tools, complexity is not a limitation. So you can build whatever you want — without stressing over budget. Hook up Formbricks with n8n and you can send your data to 350+ other apps. Here is how to do it.
<Note>
Nail down your survey? Any changes in the survey cause additional work in the n8n node. It makes
### Nail down your survey first Any changes in the survey cause additional work in the n8n node. It makes
sense to first settle on the survey you want to run and then get to setting up n8n.
</Note>
@@ -21,7 +21,8 @@ export const metadata = {
The notion integration allows you to automatically send responses to a Notion database of your choice.
<Note>
If you are on a self-hosted instance, you will need to configure this integration separately. Please follow the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance.
This feature is enabled by default in Formbricks Cloud but needs to be self-configured when running a
self-hosted version of Formbricks.
</Note>
## Formbricks Cloud
@@ -10,7 +10,7 @@ export const metadata = {
At Formbricks, we understand the importance of integrating with third-party applications. We have step-by-step guides to configure our third-party integrations with a your Formbricks instance. We currently support the below integrations, click on them to see their individual guides:
<Note>
If you are on a self-hosted instance, you will need to configure these integrations manually. Please follow the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance.
If you are on a self-hosted instance, you will need to configure these integrations manually. Please follow the guides [here](/self-hosting/integrations) to configure them.
</Note>
- [Airtable](/developer-docs/integrations/airtable): Automatically send responses to an Airtable of your choice.
@@ -22,7 +22,8 @@ export const metadata = {
The slack integration allows you to automatically send responses to a Slack channel of your choice.
<Note>
If you are on a self-hosted instance, you will need to configure this integration separately. Please follow the guides [here](/self-hosting/integrations) to configure integrations on your self-hosted instance.
This feature is enabled by default in Formbricks Cloud but needs to be self-configured when running a
self-hosted version of Formbricks.
</Note>
## Formbricks Cloud
@@ -26,7 +26,7 @@ const gettingStarted = [
},
];
export const GettingStarted = () => {
export function GettingStarted() {
return (
<div className="my-16 xl:max-w-none">
<Heading level={2} id="getting-started">
@@ -47,4 +47,4 @@ export const GettingStarted = () => {
</div>
</div>
);
};
}
+2 -4
View File
@@ -15,7 +15,7 @@ export const metadata: Metadata = {
const jost = Jost({ subsets: ["latin"] });
const RootLayout = async ({ children }: { children: React.ReactNode }) => {
export default async function RootLayout({ children }: { children: React.ReactNode }) {
let pages = await glob("**/*.mdx", { cwd: "src/app" });
let allSectionsEntries = (await Promise.all(
pages.map(async (filename) => [
@@ -36,6 +36,4 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
</body>
</html>
);
};
export default RootLayout;
}
+2 -4
View File
@@ -1,7 +1,7 @@
import { Button } from "@/components/Button";
import { HeroPattern } from "@/components/HeroPattern";
const NotFound = () => {
export default function NotFound() {
return (
<>
<HeroPattern />
@@ -17,6 +17,4 @@ const NotFound = () => {
</div>
</>
);
};
export default NotFound;
}
+6 -6
View File
@@ -3,18 +3,18 @@
import { ThemeProvider, useTheme } from "next-themes";
import { useEffect } from "react";
const ThemeWatcher = () => {
function ThemeWatcher() {
let { resolvedTheme, setTheme } = useTheme();
useEffect(() => {
let media = window.matchMedia("(prefers-color-scheme: dark)");
const onMediaChange = () => {
function onMediaChange() {
let systemTheme = media.matches ? "dark" : "light";
if (resolvedTheme === systemTheme) {
setTheme("system");
}
};
}
onMediaChange();
media.addEventListener("change", onMediaChange);
@@ -25,13 +25,13 @@ const ThemeWatcher = () => {
}, [resolvedTheme, setTheme]);
return null;
};
}
export const Providers = ({ children }: { children: React.ReactNode }) => {
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" disableTransitionOnChange>
<ThemeWatcher />
{children}
</ThemeProvider>
);
};
}
+17 -21
View File
@@ -8,7 +8,7 @@ export const metadata = {
# Advanced Setup
Quickly set up and start using Formbricks with our [official Docker image](https://github.com/formbricks/formbricks/pkgs/container/formbricks) that we've already built for you.
Quickly set up and start using Formbricks with our [official Docker image](https://github.com/formbricks/formbricks/pkgs/container/formbricks) that we've already built for you.
The pre-built image is ready-to-run, and it only requires minimal configuration on your part. It's as easy as downloading the Docker image and firing up the container.
@@ -104,25 +104,7 @@ You're now ready to start the Formbricks Docker setup. The following command wil
## Update
<Note>
Please take a look at our [migration guide](/self-hosting/migration-guide) for version specific steps to
update Formbricks.
</Note>
1. Pull the latest Formbricks image
<Col>
<CodeGroup title="Pull the changes into docker">
```bash
docker compose pull
```
</CodeGroup>
</Col>
2. Stop the Formbricks stack
1. Stop the Formbricks stack
<Col>
<CodeGroup title="Stop the docker instance">
@@ -135,7 +117,21 @@ You're now ready to start the Formbricks Docker setup. The following command wil
</Col>
3. Re-start the Formbricks stack with the updated image
2. Pull the latest changes
<Col>
<CodeGroup title="Pull the changes into docker">
```bash
docker compose pull
```
</CodeGroup>
</Col>
3. Update env vars as necessary in the docker-compose file.
4. Re-start the Formbricks stack
<Col>
<CodeGroup title="Relaunch the Docker Instance">
@@ -39,10 +39,10 @@ We have step-by-step guides to configure our third-party integrations with a sel
- [Airtable](#airtable)
- [Google Sheets](#google-sheets)
- [Notion](#notion)
- Make: We do not support for self-hosted instances yet.
- Make: We do not support [Make.com](http://Make.com) for Self-hosted instances yet! Please follow our Cloud guide [here](/integrations#make)
- [n8n](#n8n)
- [Slack](#slack)
- Wordpress: Wordpress setup is similar to the [Cloud setup](/developer-docs/integrations/wordpress), just change the API Host to your self-hosted URL.
- [Wordpress]: Wordpress setup is similar to steps mentioned in Cloud [here](/integrations#wordpress), just change the API Host to your self-hosted URL.
- [Zapier](#zapier)
<Note>
@@ -17,14 +17,6 @@ Formbricks v2.0 comes with huge features such as Multi-Language Surveys and Adva
the upgrade. Follow the below steps thoroughly to upgrade your Formbricks instance to v2.0.
</Note>
and
<Note>
If you've used the Formbricks Enterprise Edition with a free beta license key, your instance will be
downgraded to the Community Edition 2.0. You find all license details on the [license
page.](/self-hosting/license/)
</Note>
### Steps to Migrate
This guide is for users who are self-hosting Formbricks using our one-click setup. If you are using a different setup, you might adjust the commands accordingly.
@@ -37,7 +29,7 @@ To run all these steps, please navigate to the `formbricks` folder where your `d
<CodeGroup title="Backup Postgres">
```bash
docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.0_$(date +%Y%m%d_%H%M%S).dump
docker exec formbricks-quickstart-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbricks_pre_v2.0_$(date +%Y%m%d_%H%M%S).dump
```
</CodeGroup>
@@ -53,19 +45,7 @@ docker exec formbricks-postgres-1 pg_dump -Fc -U postgres -d formbricks > formbr
restore scenario you will need to use `psql` then with an empty `formbricks` database.
</Note>
2. Pull the latest version of Formbricks:
<Col>
<CodeGroup title="Stop the containers">
```bash
docker-compose pull
```
</CodeGroup>
</Col>
3. Stop the running Formbricks instance & remove the related containers:
2. Stop the running Formbricks instance & remove the related containers:
<Col>
<CodeGroup title="Stop the containers">
@@ -77,7 +57,7 @@ docker-compose down
</CodeGroup>
</Col>
4. Restarting the containers with the latest version of Formbricks:
3. Restarting the containers will automatically pull the latest version of Formbricks:
<Col>
<CodeGroup title="Restart the containers">
@@ -89,7 +69,7 @@ docker-compose up -d
</CodeGroup>
</Col>
5. Now let's migrate the data to the latest schema:
4. Now let's migrate the data to the latest schema:
<Note>To find your Docker Network name for your Postgres Database, find it using `docker network ls`</Note>
@@ -97,7 +77,6 @@ docker-compose up -d
<CodeGroup title="Migrate the data">
```bash
docker pull ghcr.io/formbricks/data-migrations:latest && \
docker run --rm \
--network=formbricks_default \
-e DATABASE_URL="postgresql://postgres:postgres@postgres:5432/formbricks?schema=public" \
@@ -110,7 +89,7 @@ docker run --rm \
The above command will migrate your data to the latest schema. This is a crucial step to migrate your existing data to the new structure. Only if the script runs successful, changes are made to the database. The script can safely run multiple times.
6. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
5. That's it! Once the migration is complete, you can **now access your Formbricks instance** at the same URL as before.
### App Surveys with @formbricks/js
@@ -33,7 +33,7 @@ const libraries = [
},
];
export const Libraries = () => {
export function Libraries() {
return (
<div className="my-16 xl:max-w-none">
<div className="not-prose mt-4 grid grid-cols-1 gap-x-6 gap-y-10 border-slate-900/5 sm:grid-cols-2 xl:max-w-none xl:grid-cols-3 dark:border-white/5">
@@ -57,4 +57,4 @@ export const Libraries = () => {
</div>
</div>
);
};
}
+4 -4
View File
@@ -1,7 +1,7 @@
import clsx from "clsx";
import Link from "next/link";
const ArrowIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function ArrowIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
<path
@@ -12,7 +12,7 @@ const ArrowIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
const variantStyles = {
primary:
@@ -34,7 +34,7 @@ type ButtonProps = {
| (React.ComponentPropsWithoutRef<"button"> & { href?: undefined })
);
export const Button = ({ variant = "primary", className, children, arrow, ...props }: ButtonProps) => {
export function Button({ variant = "primary", className, children, arrow, ...props }: ButtonProps) {
className = clsx(
"inline-flex gap-0.5 justify-center items-center overflow-hidden font-medium transition text-center",
variantStyles[variant],
@@ -74,4 +74,4 @@ export const Button = ({ variant = "primary", className, children, arrow, ...pro
{inner}
</Link>
);
};
}
+27 -27
View File
@@ -17,7 +17,7 @@ const languageNames: Record<string, string> = {
go: "Go",
};
const getPanelTitle = ({ title, language }: { title?: string; language?: string }) => {
function getPanelTitle({ title, language }: { title?: string; language?: string }) {
if (title) {
return title;
}
@@ -25,9 +25,9 @@ const getPanelTitle = ({ title, language }: { title?: string; language?: string
return languageNames[language];
}
return "Code";
};
}
const ClipboardIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function ClipboardIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -41,9 +41,9 @@ const ClipboardIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
const CopyButton = ({ code }: { code: string }) => {
function CopyButton({ code }: { code: string }) {
let [copyCount, setCopyCount] = useState(0);
let copied = copyCount > 0;
@@ -89,9 +89,9 @@ const CopyButton = ({ code }: { code: string }) => {
</span>
</button>
);
};
}
const CodePanelHeader = ({ tag, label }: { tag?: string; label?: string }) => {
function CodePanelHeader({ tag, label }: { tag?: string; label?: string }) {
if (!tag && !label) {
return null;
}
@@ -107,9 +107,9 @@ const CodePanelHeader = ({ tag, label }: { tag?: string; label?: string }) => {
{label && <span className="font-mono text-xs text-slate-400">{label}</span>}
</div>
);
};
}
const CodePanel = ({
function CodePanel({
children,
tag,
label,
@@ -119,7 +119,7 @@ const CodePanel = ({
tag?: string;
label?: string;
code?: string;
}) => {
}) {
let child = Children.only(children);
if (isValidElement(child)) {
@@ -141,9 +141,9 @@ const CodePanel = ({
</div>
</div>
);
};
}
const CodeGroupHeader = ({
function CodeGroupHeader({
title,
children,
selectedIndex,
@@ -151,7 +151,7 @@ const CodeGroupHeader = ({
title: string;
children: React.ReactNode;
selectedIndex: number;
}) => {
}) {
let hasTabs = Children.count(children) >= 1;
if (!title && !hasTabs) {
@@ -178,9 +178,9 @@ const CodeGroupHeader = ({
)}
</div>
);
};
}
const CodeGroupPanels = ({ children, ...props }: React.ComponentPropsWithoutRef<typeof CodePanel>) => {
function CodeGroupPanels({ children, ...props }: React.ComponentPropsWithoutRef<typeof CodePanel>) {
let hasTabs = Children.count(children) >= 1;
if (hasTabs) {
@@ -196,9 +196,9 @@ const CodeGroupPanels = ({ children, ...props }: React.ComponentPropsWithoutRef<
}
return <CodePanel {...props}>{children}</CodePanel>;
};
}
const usePreventLayoutShift = () => {
function usePreventLayoutShift() {
let positionRef = useRef<HTMLElement>(null);
let rafRef = useRef<number>();
@@ -227,7 +227,7 @@ const usePreventLayoutShift = () => {
});
},
};
};
}
const usePreferredLanguageStore = create<{
preferredLanguages: Array<string>;
@@ -243,7 +243,7 @@ const usePreferredLanguageStore = create<{
})),
}));
const useTabGroupProps = (availableLanguages: Array<string>) => {
function useTabGroupProps(availableLanguages: Array<string>) {
let { preferredLanguages, addPreferredLanguage } = usePreferredLanguageStore();
let [selectedIndex, setSelectedIndex] = useState(0);
let activeLanguage = [...availableLanguages].sort(
@@ -265,15 +265,15 @@ const useTabGroupProps = (availableLanguages: Array<string>) => {
preventLayoutShift(() => addPreferredLanguage(availableLanguages[newSelectedIndex]));
},
};
};
}
const CodeGroupContext = createContext(false);
export const CodeGroup = ({
export function CodeGroup({
children,
title,
...props
}: React.ComponentPropsWithoutRef<typeof CodeGroupPanels> & { title: string }) => {
}: React.ComponentPropsWithoutRef<typeof CodeGroupPanels> & { title: string }) {
let languages =
Children.map(children, (child) => getPanelTitle(isValidElement(child) ? child.props : {})) ?? [];
let tabGroupProps = useTabGroupProps(languages);
@@ -303,9 +303,9 @@ export const CodeGroup = ({
)}
</CodeGroupContext.Provider>
);
};
}
export const Code = ({ children, ...props }: React.ComponentPropsWithoutRef<"code">) => {
export function Code({ children, ...props }: React.ComponentPropsWithoutRef<"code">) {
let isGrouped = useContext(CodeGroupContext);
if (isGrouped) {
@@ -316,9 +316,9 @@ export const Code = ({ children, ...props }: React.ComponentPropsWithoutRef<"cod
}
return <code {...props}>{children}</code>;
};
}
export const Pre = ({ children, ...props }: React.ComponentPropsWithoutRef<typeof CodeGroup>) => {
export function Pre({ children, ...props }: React.ComponentPropsWithoutRef<typeof CodeGroup>) {
let isGrouped = useContext(CodeGroupContext);
if (isGrouped) {
@@ -326,4 +326,4 @@ export const Pre = ({ children, ...props }: React.ComponentPropsWithoutRef<typeo
}
return <CodeGroup {...props}>{children}</CodeGroup>;
};
}
+10 -14
View File
@@ -3,7 +3,7 @@
import { Transition } from "@headlessui/react";
import { Fragment, forwardRef, useState } from "react";
const CheckIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function CheckIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<circle cx="10" cy="10" r="10" strokeWidth="0" />
@@ -16,9 +16,9 @@ const CheckIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
const FeedbackButton = (props: Omit<React.ComponentPropsWithoutRef<"button">, "type" | "className">) => {
function FeedbackButton(props: Omit<React.ComponentPropsWithoutRef<"button">, "type" | "className">) {
return (
<button
type="submit"
@@ -26,12 +26,12 @@ const FeedbackButton = (props: Omit<React.ComponentPropsWithoutRef<"button">, "t
{...props}
/>
);
};
}
const FeedbackForm = forwardRef<
React.ElementRef<"form">,
Pick<React.ComponentPropsWithoutRef<"form">, "onSubmit">
>(({ onSubmit }, ref) => {
>(function FeedbackForm({ onSubmit }, ref) {
return (
<form
ref={ref}
@@ -47,9 +47,7 @@ const FeedbackForm = forwardRef<
);
});
FeedbackForm.displayName = "FeedbackForm";
const FeedbackThanks = forwardRef<React.ElementRef<"div">>((_props, ref) => {
const FeedbackThanks = forwardRef<React.ElementRef<"div">>(function FeedbackThanks(_props, ref) {
return (
<div ref={ref} className="absolute inset-0 flex justify-center md:justify-start">
<div className="flex items-center gap-3 rounded-full bg-teal-50/50 py-1 pl-1.5 pr-3 text-sm text-teal-900 ring-1 ring-inset ring-teal-500/20 dark:bg-teal-500/5 dark:text-teal-200 dark:ring-teal-500/30">
@@ -60,19 +58,17 @@ const FeedbackThanks = forwardRef<React.ElementRef<"div">>((_props, ref) => {
);
});
FeedbackThanks.displayName = "FeedbackThanks";
export const Feedback = () => {
export function Feedback() {
let [submitted, setSubmitted] = useState(false);
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
function onSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
// event.nativeEvent.submitter.dataset.response
// => "yes" or "no"
setSubmitted(true);
};
}
return (
<div className="relative h-8">
@@ -94,4 +90,4 @@ export const Feedback = () => {
</Transition>
</div>
);
};
}
+12 -12
View File
@@ -9,7 +9,7 @@ import { DiscordIcon } from "./icons/DiscordIcon";
import { GithubIcon } from "./icons/GithubIcon";
import { TwitterIcon } from "./icons/TwitterIcon";
const PageLink = ({
function PageLink({
label,
page,
previous = false,
@@ -17,7 +17,7 @@ const PageLink = ({
label: string;
page: { href: string; title: string };
previous?: boolean;
}) => {
}) {
return (
<>
<Button
@@ -36,9 +36,9 @@ const PageLink = ({
</Link>
</>
);
};
}
const PageNavigation = () => {
function PageNavigation() {
let pathname = usePathname();
let allPages = navigation.flatMap((group) => {
return group.links.flatMap((link) => {
@@ -72,9 +72,9 @@ const PageNavigation = () => {
)}
</div>
);
};
}
const SocialLink = ({
function SocialLink({
href,
icon: Icon,
children,
@@ -82,16 +82,16 @@ const SocialLink = ({
href: string;
icon: React.ComponentType<{ className?: string }>;
children: React.ReactNode;
}) => {
}) {
return (
<Link href={href} className="group">
<span className="sr-only">{children}</span>
<Icon className="h-5 w-5 fill-slate-700 transition group-hover:fill-slate-900 dark:group-hover:fill-slate-500" />
</Link>
);
};
}
const SmallPrint = () => {
function SmallPrint() {
const currentYear = new Date().getFullYear();
return (
@@ -112,13 +112,13 @@ const SmallPrint = () => {
</div>
</div>
);
};
}
export const Footer = () => {
export function Footer() {
return (
<footer className="mx-auto w-full max-w-2xl space-y-10 pb-16 lg:max-w-5xl">
<PageNavigation />
<SmallPrint />
</footer>
);
};
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { useId } from "react";
export const GridPattern = ({
export function GridPattern({
width,
height,
x,
@@ -13,7 +13,7 @@ export const GridPattern = ({
x: string | number;
y: string | number;
squares: Array<[x: number, y: number]>;
}) => {
}) {
let patternId = useId();
return (
@@ -40,4 +40,4 @@ export const GridPattern = ({
)}
</svg>
);
};
}
+2 -2
View File
@@ -24,7 +24,7 @@ const guides = [
},
];
export const Guides = () => {
export function Guides() {
return (
<div className="my-16 xl:max-w-none">
<Heading level={2} id="guides">
@@ -45,4 +45,4 @@ export const Guides = () => {
</div>
</div>
);
};
}
+6 -5
View File
@@ -11,7 +11,7 @@ import { Button } from "./Button";
import { MobileNavigation, useIsInsideMobileNavigation, useMobileNavigationStore } from "./MobileNavigation";
import { ThemeToggle } from "./ThemeToggle";
const TopLevelNavItem = ({ href, children }: { href: string; children: React.ReactNode }) => {
function TopLevelNavItem({ href, children }: { href: string; children: React.ReactNode }) {
return (
<li>
<Link
@@ -21,9 +21,12 @@ const TopLevelNavItem = ({ href, children }: { href: string; children: React.Rea
</Link>
</li>
);
};
}
export const Header = forwardRef<React.ElementRef<"div">, { className?: string }>(({ className }, ref) => {
export const Header = forwardRef<React.ElementRef<"div">, { className?: string }>(function Header(
{ className },
ref
) {
let { isOpen: mobileNavIsOpen } = useMobileNavigationStore();
let isInsideMobileNavigation = useIsInsideMobileNavigation();
@@ -88,5 +91,3 @@ export const Header = forwardRef<React.ElementRef<"div">, { className?: string }
</motion.div>
);
});
Header.displayName = "Header";
+9 -9
View File
@@ -7,15 +7,15 @@ import { useInView } from "framer-motion";
import Link from "next/link";
import { useEffect, useRef } from "react";
const AnchorIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function AnchorIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" fill="none" strokeLinecap="round" aria-hidden="true" {...props}>
<path d="m6.5 11.5-.964-.964a3.535 3.535 0 1 1 5-5l.964.964m2 2 .964.964a3.536 3.536 0 0 1-5 5L8.5 13.5m0-5 3 3" />
</svg>
);
};
}
const Eyebrow = ({ tag, label }: { tag?: string; label?: string }) => {
function Eyebrow({ tag, label }: { tag?: string; label?: string }) {
if (!tag && !label) {
return null;
}
@@ -27,9 +27,9 @@ const Eyebrow = ({ tag, label }: { tag?: string; label?: string }) => {
{label && <span className="font-mono text-xs text-zinc-400">{label}</span>}
</div>
);
};
}
const Anchor = ({ id, inView, children }: { id: string; inView: boolean; children: React.ReactNode }) => {
function Anchor({ id, inView, children }: { id: string; inView: boolean; children: React.ReactNode }) {
return (
<Link href={`#${id}`} className="group text-inherit no-underline hover:text-inherit">
{inView && (
@@ -42,9 +42,9 @@ const Anchor = ({ id, inView, children }: { id: string; inView: boolean; childre
{children}
</Link>
);
};
}
export const Heading = <Level extends 2 | 3>({
export function Heading<Level extends 2 | 3>({
children,
tag,
label,
@@ -57,7 +57,7 @@ export const Heading = <Level extends 2 | 3>({
label?: string;
level?: Level;
anchor?: boolean;
}) => {
}) {
level = level ?? (2 as Level);
let Component = `h${level}` as "h2" | "h3";
let ref = useRef<HTMLHeadingElement>(null);
@@ -88,4 +88,4 @@ export const Heading = <Level extends 2 | 3>({
</Component>
</>
);
};
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { GridPattern } from "./GridPattern";
export const HeroPattern = () => {
export function HeroPattern() {
return (
<div className="absolute inset-0 -z-10 mx-0 max-w-none overflow-hidden">
<div className="absolute left-1/2 top-0 ml-[-38rem] h-[25rem] w-[81.25rem] dark:[mask-image:linear-gradient(white,transparent)]">
@@ -28,4 +28,4 @@ export const HeroPattern = () => {
</div>
</div>
);
};
}
+4 -4
View File
@@ -10,13 +10,13 @@ import { Footer } from "./Footer";
import { Header } from "./Header";
import { type Section, SectionProvider } from "./SectionProvider";
export const Layout = ({
export function Layout({
children,
allSections,
}: {
children: React.ReactNode;
allSections: Record<string, Array<Section>>;
}) => {
}) {
let pathname = usePathname();
return (
@@ -32,7 +32,7 @@ export const Layout = ({
</Link>
</div>
<Header />
<Navigation className="hidden lg:mt-10 lg:block" isMobile={false} />
<Navigation className="hidden lg:mt-10 lg:block" />
</div>
</motion.header>
<div className="relative flex h-full flex-col px-4 pt-14 sm:px-6 lg:px-8">
@@ -42,4 +42,4 @@ export const Layout = ({
</div>
</SectionProvider>
);
};
}
+2 -2
View File
@@ -2,7 +2,7 @@ import logoDark from "@/images/logo/logo-dark.svg";
import logoLight from "@/images/logo/logo-light.svg";
import Image from "next/image";
export const Logo = ({ className }: { className?: string }) => {
export function Logo({ className }: { className?: string }) {
return (
<div>
<div className="block dark:hidden">
@@ -13,4 +13,4 @@ export const Logo = ({ className }: { className?: string }) => {
</div>
</div>
);
};
}
+2 -2
View File
@@ -1,6 +1,6 @@
import Image, { ImageProps } from "next/image";
import React from "react";
export const MdxImage = (props: ImageProps) => {
export function MdxImage(props: ImageProps) {
return <Image {...props} alt={props.alt} />;
};
}
+13 -13
View File
@@ -8,25 +8,25 @@ import { usePathname, useSearchParams } from "next/navigation";
import { Fragment, Suspense, createContext, useContext, useEffect, useRef } from "react";
import { create } from "zustand";
const MenuIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function MenuIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 10 9" fill="none" strokeLinecap="round" aria-hidden="true" {...props}>
<path d="M.5 1h9M.5 8h9M.5 4.5h9" />
</svg>
);
};
}
const XIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function XIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 10 9" fill="none" strokeLinecap="round" aria-hidden="true" {...props}>
<path d="m1.5 1 7 7M8.5 1l-7 7" />
</svg>
);
};
}
const IsInsideMobileNavigationContext = createContext(false);
const MobileNavigationDialog = ({ isOpen, close }: { isOpen: boolean; close: () => void }) => {
function MobileNavigationDialog({ isOpen, close }: { isOpen: boolean; close: () => void }) {
let pathname = usePathname();
let searchParams = useSearchParams();
let initialPathname = useRef(pathname).current;
@@ -38,7 +38,7 @@ const MobileNavigationDialog = ({ isOpen, close }: { isOpen: boolean; close: ()
}
}, [pathname, searchParams, close, initialPathname, initialSearchParams]);
const onClickDialog = (event: React.MouseEvent<HTMLDivElement>) => {
function onClickDialog(event: React.MouseEvent<HTMLDivElement>) {
if (!(event.target instanceof HTMLElement)) {
return;
}
@@ -51,7 +51,7 @@ const MobileNavigationDialog = ({ isOpen, close }: { isOpen: boolean; close: ()
) {
close();
}
};
}
return (
<Transition.Root show={isOpen} as={Fragment}>
@@ -90,18 +90,18 @@ const MobileNavigationDialog = ({ isOpen, close }: { isOpen: boolean; close: ()
<motion.div
layoutScroll
className="ring-zinc-900/7.5 fixed bottom-0 left-0 top-14 w-full overflow-y-auto bg-white px-4 pb-4 pt-6 shadow-lg shadow-zinc-900/10 ring-1 min-[416px]:max-w-sm sm:px-6 sm:pb-10 dark:bg-zinc-900 dark:ring-zinc-800">
<Navigation isMobile={true} />
<Navigation />
</motion.div>
</Transition.Child>
</Dialog.Panel>
</Dialog>
</Transition.Root>
);
};
}
export const useIsInsideMobileNavigation = () => {
export function useIsInsideMobileNavigation() {
return useContext(IsInsideMobileNavigationContext);
};
}
export const useMobileNavigationStore = create<{
isOpen: boolean;
@@ -115,7 +115,7 @@ export const useMobileNavigationStore = create<{
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
}));
export const MobileNavigation = () => {
export function MobileNavigation() {
let isInsideMobileNavigation = useIsInsideMobileNavigation();
let { isOpen, toggle, close } = useMobileNavigationStore();
let ToggleIcon = isOpen ? XIcon : MenuIcon;
@@ -136,4 +136,4 @@ export const MobileNavigation = () => {
)}
</IsInsideMobileNavigationContext.Provider>
);
};
}
+30 -55
View File
@@ -35,55 +35,39 @@ export interface NavGroup {
links: Array<LinkWithHref | LinkWithChildren>;
}
const useInitialValue = <T,>(value: T, condition = true) => {
function useInitialValue<T>(value: T, condition = true) {
let initialValue = useRef(value).current;
return condition ? initialValue : value;
};
}
const NavLink = ({
function NavLink({
href,
children,
active = false,
isAnchorLink = false,
}: {
href?: string;
href: string;
children: React.ReactNode;
active: boolean;
isAnchorLink?: boolean;
}) => {
const commonClasses = clsx(
"flex justify-between gap-2 py-1 pr-3 text-sm transition",
isAnchorLink ? "pl-7" : "pl-4",
active
? "rounded-r-md bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-white"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white"
}) {
return (
<Link
href={href}
aria-current={active ? "page" : undefined}
className={clsx(
"flex justify-between gap-2 py-1 pr-3 text-sm transition",
isAnchorLink ? "pl-7" : "pl-4",
active
? "rounded-r-md bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-white"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white"
)}>
<span className="flex w-full truncate">{children}</span>
</Link>
);
}
if (href) {
return (
<Link
href={href}
aria-current={active ? "page" : undefined}
className={clsx(
"flex justify-between gap-2 py-1 pr-3 text-sm transition",
isAnchorLink ? "pl-7" : "pl-4",
active
? "rounded-r-md bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-white"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-400 dark:hover:bg-slate-800 dark:hover:text-white"
)}>
<span className="flex w-full truncate">{children}</span>
</Link>
);
} else {
return (
<div aria-current={active ? "page" : undefined} className={commonClasses}>
<span className="flex w-full truncate">{children}</span>
</div>
);
}
};
const VisibleSectionHighlight = ({ group, pathname }: { group: NavGroup; pathname: string }) => {
function VisibleSectionHighlight({ group, pathname }: { group: NavGroup; pathname: string }) {
let [sections, visibleSections] = useInitialValue(
[useSectionStore((s) => s.sections), useSectionStore((s) => s.visibleSections)],
useIsInsideMobileNavigation()
@@ -115,9 +99,9 @@ const VisibleSectionHighlight = ({ group, pathname }: { group: NavGroup; pathnam
style={{ height, top }}
/>
);
};
}
const ActivePageMarker = ({ group, pathname }: { group: NavGroup; pathname: string }) => {
function ActivePageMarker({ group, pathname }: { group: NavGroup; pathname: string }) {
let itemHeight = remToPx(2);
let offset = remToPx(0.25);
let activePageIndex = group.links.findIndex(
@@ -138,16 +122,15 @@ const ActivePageMarker = ({ group, pathname }: { group: NavGroup; pathname: stri
style={{ top }}
/>
);
};
}
const NavigationGroup = ({
function NavigationGroup({
group,
className,
activeGroup,
setActiveGroup,
openGroups,
setOpenGroups,
isMobile,
}: {
group: NavGroup;
className?: string;
@@ -155,8 +138,7 @@ const NavigationGroup = ({
setActiveGroup: (group: NavGroup | null) => void;
openGroups: string[];
setOpenGroups: (groups: string[]) => void;
isMobile: boolean;
}) => {
}) {
const isInsideMobileNavigation = useIsInsideMobileNavigation();
const pathname = usePathname();
const isActiveGroup = activeGroup?.title === group.title;
@@ -189,15 +171,13 @@ const NavigationGroup = ({
{group.links.map((link) => (
<motion.li key={link.title} layout="position" className="relative">
{link.href ? (
<NavLink
href={isMobile && link.children ? "" : link.href}
active={!!pathname?.startsWith(link.href)}>
<NavLink href={link.href} active={!!pathname?.startsWith(link.href)}>
{link.title}
</NavLink>
) : (
<div onClick={() => toggleParentTitle(link.title)}>
<NavLink
href={!isMobile ? link.children?.[0]?.href || "" : undefined}
href={link.children?.[0]?.href || ""}
active={
!!(
isParentOpen(link.title) &&
@@ -217,7 +197,7 @@ const NavigationGroup = ({
</div>
)}
<AnimatePresence mode="popLayout" initial={false}>
{isActiveGroup && link.children && isParentOpen(link.title) && (
{link.children && isParentOpen(link.title) && (
<motion.ul
role="list"
initial={{ opacity: 0 }}
@@ -239,13 +219,9 @@ const NavigationGroup = ({
</div>
</li>
);
};
interface NavigationProps extends React.ComponentPropsWithoutRef<"nav"> {
isMobile: boolean;
}
export const Navigation = ({ isMobile, ...props }: NavigationProps) => {
export function Navigation(props: React.ComponentPropsWithoutRef<"nav">) {
const [activeGroup, setActiveGroup] = useState<NavGroup | null>(navigation[0]);
const [openGroups, setOpenGroups] = useState<string[]>([]);
@@ -261,7 +237,6 @@ export const Navigation = ({ isMobile, ...props }: NavigationProps) => {
setActiveGroup={setActiveGroup}
openGroups={openGroups}
setOpenGroups={setOpenGroups}
isMobile={isMobile}
/>
))}
<li className="sticky bottom-0 z-10 mt-6 min-[416px]:hidden">
@@ -276,4 +251,4 @@ export const Navigation = ({ isMobile, ...props }: NavigationProps) => {
</ul>
</nav>
);
};
}
+3 -3
View File
@@ -1,13 +1,13 @@
import clsx from "clsx";
export const Prose = <T extends React.ElementType = "div">({
export function Prose<T extends React.ElementType = "div">({
as,
className,
...props
}: Omit<React.ComponentPropsWithoutRef<T>, "as" | "className"> & {
as?: T;
className?: string;
}) => {
}) {
let Component = as ?? "div";
return (
@@ -21,4 +21,4 @@ export const Prose = <T extends React.ElementType = "div">({
{...props}
/>
);
};
}
+11 -11
View File
@@ -72,22 +72,22 @@ const resources: Array<Resource> = [
},
];
const ResourceIcon = ({ icon: Icon }: { icon: Resource["icon"] }) => {
function ResourceIcon({ icon: Icon }: { icon: Resource["icon"] }) {
return (
<div className="dark:bg-white/7.5 flex h-7 w-7 items-center justify-center rounded-full bg-zinc-900/5 ring-1 ring-zinc-900/25 backdrop-blur-[2px] transition duration-300 group-hover:bg-white/50 group-hover:ring-zinc-900/25 dark:ring-white/15 dark:group-hover:bg-teal-300/10 dark:group-hover:ring-teal-400">
<Icon className="h-5 w-5 fill-zinc-700/10 stroke-zinc-700 transition-colors duration-300 group-hover:stroke-zinc-900 dark:fill-white/10 dark:stroke-zinc-400 dark:group-hover:fill-teal-300/10 dark:group-hover:stroke-teal-400" />
</div>
);
};
}
const ResourcePattern = ({
function ResourcePattern({
mouseX,
mouseY,
...gridProps
}: Resource["pattern"] & {
mouseX: MotionValue<number>;
mouseY: MotionValue<number>;
}) => {
}) {
let maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)`;
let style = { maskImage, WebkitMaskImage: maskImage };
@@ -119,17 +119,17 @@ const ResourcePattern = ({
</motion.div>
</div>
);
};
}
const Resource = ({ resource }: { resource: Resource }) => {
function Resource({ resource }: { resource: Resource }) {
let mouseX = useMotionValue(0);
let mouseY = useMotionValue(0);
const onMouseMove = ({ currentTarget, clientX, clientY }: React.MouseEvent<HTMLDivElement>) => {
function onMouseMove({ currentTarget, clientX, clientY }: React.MouseEvent<HTMLDivElement>) {
let { left, top } = currentTarget.getBoundingClientRect();
mouseX.set(clientX - left);
mouseY.set(clientY - top);
};
}
return (
<div
@@ -150,9 +150,9 @@ const Resource = ({ resource }: { resource: Resource }) => {
</div>
</div>
);
};
}
export const Resources = () => {
export function Resources() {
return (
<div className="my-16 xl:max-w-none">
<Heading level={2} id="resources">
@@ -165,4 +165,4 @@ export const Resources = () => {
</div>
</div>
);
};
}
+2 -2
View File
@@ -1,5 +1,5 @@
// ResponsiveVideo.js
export const ResponsiveVideo = ({ src, title }) => {
export function ResponsiveVideo({ src, title }) {
return (
<div className="relative w-full overflow-hidden pt-[56.25%]">
<iframe
@@ -12,4 +12,4 @@ export const ResponsiveVideo = ({ src, title }) => {
allowFullScreen></iframe>
</div>
);
};
}
+6 -6
View File
@@ -16,19 +16,19 @@ interface HitProps {
children: React.ReactNode;
}
const Hit = ({ hit, children }: HitProps) => {
function Hit({ hit, children }: HitProps) {
return <Link href={hit.url}>{children}</Link>;
};
}
const SearchIcon = (props: any) => {
function SearchIcon(props: any) {
return (
<svg aria-hidden="true" viewBox="0 0 20 20" {...props}>
<path d="M16.293 17.707a1 1 0 0 0 1.414-1.414l-1.414 1.414ZM9 14a5 5 0 0 1-5-5H2a7 7 0 0 0 7 7v-2ZM4 9a5 5 0 0 1 5-5V2a7 7 0 0 0-7 7h2Zm5-5a5 5 0 0 1 5 5h2a7 7 0 0 0-7-7v2Zm8.707 12.293-3.757-3.757-1.414 1.414 3.757 3.757 1.414-1.414ZM14 9a4.98 4.98 0 0 1-1.464 3.536l1.414 1.414A6.98 6.98 0 0 0 16 9h-2Zm-1.464 3.536A4.98 4.98 0 0 1 9 14v2a6.98 6.98 0 0 0 4.95-2.05l-1.414-1.414Z" />
</svg>
);
};
}
export const Search = () => {
export function Search() {
let { resolvedTheme } = useTheme();
let isLightMode = resolvedTheme === "light";
@@ -132,4 +132,4 @@ export const Search = () => {
)}
</>
);
};
}
+12 -12
View File
@@ -27,7 +27,7 @@ interface SectionState {
}) => void;
}
const createSectionStore = (sections: Array<Section>) => {
function createSectionStore(sections: Array<Section>) {
return createStore<SectionState>()((set) => ({
sections,
visibleSections: [],
@@ -49,14 +49,14 @@ const createSectionStore = (sections: Array<Section>) => {
};
}),
}));
};
}
const useVisibleSections = (sectionStore: StoreApi<SectionState>) => {
function useVisibleSections(sectionStore: StoreApi<SectionState>) {
let setVisibleSections = useStore(sectionStore, (s) => s.setVisibleSections);
let sections = useStore(sectionStore, (s) => s.sections);
useEffect(() => {
const checkVisibleSections = () => {
function checkVisibleSections() {
let { innerHeight, scrollY } = window;
let newVisibleSections: string[] = [];
@@ -90,7 +90,7 @@ const useVisibleSections = (sectionStore: StoreApi<SectionState>) => {
}
setVisibleSections(newVisibleSections);
};
}
let raf = window.requestAnimationFrame(() => checkVisibleSections());
window.addEventListener("scroll", checkVisibleSections, { passive: true });
@@ -102,19 +102,19 @@ const useVisibleSections = (sectionStore: StoreApi<SectionState>) => {
window.removeEventListener("resize", checkVisibleSections);
};
}, [setVisibleSections, sections]);
};
}
const SectionStoreContext = createContext<StoreApi<SectionState> | null>(null);
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
export const SectionProvider = ({
export function SectionProvider({
sections,
children,
}: {
sections: Array<Section>;
children: React.ReactNode;
}) => {
}) {
let [sectionStore] = useState(() => createSectionStore(sections));
useVisibleSections(sectionStore);
@@ -124,9 +124,9 @@ export const SectionProvider = ({
}, [sectionStore, sections]);
return <SectionStoreContext.Provider value={sectionStore}>{children}</SectionStoreContext.Provider>;
};
}
export const useSectionStore = <T,>(selector: (state: SectionState) => T) => {
const store = useContext(SectionStoreContext);
export function useSectionStore<T>(selector: (state: SectionState) => T) {
let store = useContext(SectionStoreContext);
return useStore(store!, selector);
};
}
+3 -3
View File
@@ -39,7 +39,7 @@ const valueColorMap = {
DELETE: "rose",
} as Record<string, keyof typeof colorStyles>;
export const Tag = ({
export function Tag({
children,
variant = "medium",
color = valueColorMap[children] ?? "teal",
@@ -47,7 +47,7 @@ export const Tag = ({
children: keyof typeof valueColorMap & (string | {});
variant?: keyof typeof variantStyles;
color?: keyof typeof colorStyles;
}) => {
}) {
return (
<span
className={clsx(
@@ -58,4 +58,4 @@ export const Tag = ({
{children}
</span>
);
};
}
+2 -2
View File
@@ -1,6 +1,6 @@
import React from "react";
export const TellaVideo = ({ tellaVideoIdentifier }: { tellaVideoIdentifier: string }) => {
export function TellaVideo({ tellaVideoIdentifier }: { tellaVideoIdentifier: string }) {
return (
<div>
<iframe
@@ -15,4 +15,4 @@ export const TellaVideo = ({ tellaVideoIdentifier }: { tellaVideoIdentifier: str
title="Tella Video Help"></iframe>
</div>
);
};
}
+6 -6
View File
@@ -1,7 +1,7 @@
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
const SunIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function SunIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
<path d="M12.5 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z" />
@@ -11,17 +11,17 @@ const SunIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
const MoonIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function MoonIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
<path d="M15.224 11.724a5.5 5.5 0 0 1-6.949-6.949 5.5 5.5 0 1 0 6.949 6.949Z" />
</svg>
);
};
}
export const ThemeToggle = () => {
export function ThemeToggle() {
let { resolvedTheme, setTheme } = useTheme();
let otherTheme = resolvedTheme === "dark" ? "light" : "dark";
let [mounted, setMounted] = useState(false);
@@ -40,4 +40,4 @@ export const ThemeToggle = () => {
<MoonIcon className="hidden h-5 w-5 stroke-white dark:block" />
</button>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const BellIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function BellIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -14,4 +14,4 @@ export const BellIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const BoltIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function BoltIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -8,4 +8,4 @@ export const BoltIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const BookIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function BookIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -10,4 +10,4 @@ export const BookIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<path strokeLinecap="round" strokeLinejoin="round" d="m17.5 2.5-7.5 3v12l7.5-3v-12Z" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const CalendarIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function CalendarIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -15,4 +15,4 @@ export const CalendarIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<path fill="none" strokeLinecap="round" strokeLinejoin="round" d="M5.5 5.5v-3M14.5 5.5v-3" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const CartIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function CartIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -12,4 +12,4 @@ export const CartIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
@@ -1,4 +1,4 @@
export const ChatBubbleIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function ChatBubbleIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -9,4 +9,4 @@ export const ChatBubbleIcon = (props: React.ComponentPropsWithoutRef<"svg">) =>
<path fill="none" strokeLinecap="round" strokeLinejoin="round" d="M7.5 8.5h5M8.5 11.5h3" />
</svg>
);
};
}
+2 -2
View File
@@ -1,8 +1,8 @@
export const CheckIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function CheckIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path strokeLinecap="round" strokeLinejoin="round" d="M10 1.5a8.5 8.5 0 1 1 0 17 8.5 8.5 0 0 1 0-17Z" />
<path fill="none" strokeLinecap="round" strokeLinejoin="round" d="m7.5 10.5 2 2c1-3.5 3-5 3-5" />
</svg>
);
};
}
@@ -1,4 +1,4 @@
export const ChevronRightLeftIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function ChevronRightLeftIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -14,4 +14,4 @@ export const ChevronRightLeftIcon = (props: React.ComponentPropsWithoutRef<"svg"
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const ClipboardIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function ClipboardIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -14,4 +14,4 @@ export const ClipboardIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const CogIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function CogIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -16,4 +16,4 @@ export const CogIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<circle cx="10" cy="10" r="2.5" fill="none" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const CopyIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function CopyIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -14,4 +14,4 @@ export const CopyIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const DocumentIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function DocumentIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -9,4 +9,4 @@ export const DocumentIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<path fill="none" strokeLinecap="round" strokeLinejoin="round" d="m11.5 2.5 5 5" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const EnvelopeIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function EnvelopeIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -14,4 +14,4 @@ export const EnvelopeIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const FaceSmileIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function FaceSmileIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path strokeLinecap="round" strokeLinejoin="round" d="M10 1.5a8.5 8.5 0 1 1 0 17 8.5 8.5 0 0 1 0-17Z" />
@@ -10,4 +10,4 @@ export const FaceSmileIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const FolderIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function FolderIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -16,4 +16,4 @@ export const FolderIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const LinkIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function LinkIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -9,4 +9,4 @@ export const LinkIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const ListIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function ListIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -9,4 +9,4 @@ export const ListIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<path fill="none" strokeLinecap="round" strokeLinejoin="round" d="M6.5 6.5h7M6.5 13.5h7M6.5 10h7" />
</svg>
);
};
}
@@ -1,4 +1,4 @@
export const MagnifyingGlassIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function MagnifyingGlassIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path strokeWidth="0" d="M2.5 8.5a6 6 0 1 1 12 0 6 6 0 0 1-12 0Z" />
@@ -10,4 +10,4 @@ export const MagnifyingGlassIcon = (props: React.ComponentPropsWithoutRef<"svg">
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const MapPinIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function MapPinIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -16,4 +16,4 @@ export const MapPinIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<circle cx="10" cy="8" r="1.5" fill="none" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const PackageIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function PackageIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path strokeWidth="0" d="m10 9.5-7.5-4v9l7.5 4v-9ZM10 9.5l7.5-4v9l-7.5 4v-9Z" />
@@ -10,4 +10,4 @@ export const PackageIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
@@ -1,4 +1,4 @@
export const PaperAirplaneIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function PaperAirplaneIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -10,4 +10,4 @@ export const PaperAirplaneIcon = (props: React.ComponentPropsWithoutRef<"svg">)
<path strokeLinecap="round" strokeLinejoin="round" d="M11 19L8 12L17 3L11 19Z" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const PaperClipIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function PaperClipIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -9,4 +9,4 @@ export const PaperClipIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const ShapesIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function ShapesIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -14,4 +14,4 @@ export const ShapesIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const ShirtIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function ShirtIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -8,4 +8,4 @@ export const ShirtIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
@@ -1,4 +1,4 @@
export const SquaresPlusIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function SquaresPlusIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -9,4 +9,4 @@ export const SquaresPlusIcon = (props: React.ComponentPropsWithoutRef<"svg">) =>
<path fill="none" strokeLinecap="round" strokeLinejoin="round" d="M14.5 11.5v6M17.5 14.5h-6" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const TagIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function TagIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -16,4 +16,4 @@ export const TagIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<circle cx="7" cy="7" r="1.5" fill="none" />
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const UserIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function UserIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -21,4 +21,4 @@ export const UserIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
/>
</svg>
);
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
export const UsersIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
export function UsersIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
<path
@@ -21,4 +21,4 @@ export const UsersIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<path strokeLinecap="round" strokeLinejoin="round" d="M13 2a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z" />
</svg>
);
};
}
+16 -16
View File
@@ -8,7 +8,7 @@ export const a = Link;
export { Button } from "@/components/Button";
export { CodeGroup, Code as code, Pre as pre } from "@/components/Code";
export const wrapper = ({ children }: { children: React.ReactNode }) => {
export function wrapper({ children }: { children: React.ReactNode }) {
return (
<article className="flex h-full flex-col pb-10 pt-16">
<Prose className="flex-auto font-normal">{children}</Prose>
@@ -17,13 +17,13 @@ export const wrapper = ({ children }: { children: React.ReactNode }) => {
</footer>
</article>
);
};
}
export const h2 = (props: Omit<React.ComponentPropsWithoutRef<typeof Heading>, "level">) => {
export const h2 = function H2(props: Omit<React.ComponentPropsWithoutRef<typeof Heading>, "level">) {
return <Heading level={2} {...props} />;
};
const InfoIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
function InfoIcon(props: React.ComponentPropsWithoutRef<"svg">) {
return (
<svg viewBox="0 0 16 16" aria-hidden="true" {...props}>
<circle cx="8" cy="8" r="8" strokeWidth="0" />
@@ -37,34 +37,34 @@ const InfoIcon = (props: React.ComponentPropsWithoutRef<"svg">) => {
<circle cx="8" cy="4" r=".5" fill="none" />
</svg>
);
};
}
export const Note = ({ children }: { children: React.ReactNode }) => {
export function Note({ children }: { children: React.ReactNode }) {
return (
<div className="my-6 flex gap-2.5 rounded-2xl border border-teal-500/20 bg-teal-50/50 p-4 leading-6 text-teal-900 dark:border-teal-500/30 dark:bg-teal-500/5 dark:text-teal-200 dark:[--tw-prose-links-hover:theme(colors.teal.300)] dark:[--tw-prose-links:theme(colors.white)]">
<InfoIcon className="mt-1 h-4 w-4 flex-none fill-teal-500 stroke-white dark:fill-teal-200/20 dark:stroke-teal-200" />
<div className="[&>:first-child]:mt-0 [&>:last-child]:mb-0">{children}</div>
</div>
);
};
}
export const Row = ({ children }: { children: React.ReactNode }) => {
export function Row({ children }: { children: React.ReactNode }) {
return (
<div className="grid grid-cols-1 items-start gap-x-16 gap-y-10 xl:max-w-none xl:grid-cols-2">
{children}
</div>
);
};
}
export const Col = ({ children, sticky = false }: { children: React.ReactNode; sticky?: boolean }) => {
export function Col({ children, sticky = false }: { children: React.ReactNode; sticky?: boolean }) {
return (
<div className={clsx("[&>:first-child]:mt-0 [&>:last-child]:mb-0", sticky && "xl:sticky xl:top-24")}>
{children}
</div>
);
};
}
export const Properties = ({ children }: { children: React.ReactNode }) => {
export function Properties({ children }: { children: React.ReactNode }) {
return (
<div className="my-6">
<ul
@@ -74,9 +74,9 @@ export const Properties = ({ children }: { children: React.ReactNode }) => {
</ul>
</div>
);
};
}
export const Property = ({
export function Property({
name,
children,
type,
@@ -84,7 +84,7 @@ export const Property = ({
name: string;
children: React.ReactNode;
type?: string;
}) => {
}) {
return (
<li className="m-0 px-0 py-4 first:pt-0 last:pb-0">
<dl className="m-0 flex flex-wrap items-center gap-x-3 gap-y-2">
@@ -103,4 +103,4 @@ export const Property = ({
</dl>
</li>
);
};
}
+2 -2
View File
@@ -1,8 +1,8 @@
export const remToPx = (remValue: number) => {
export function remToPx(remValue: number) {
let rootFontSize =
typeof window === "undefined"
? 16
: parseFloat(window.getComputedStyle(document.documentElement).fontSize);
return remValue * rootFontSize;
};
}
+2 -2
View File
@@ -1,9 +1,9 @@
import * as mdxComponents from "@/components/mdx";
import { type MDXComponents } from "mdx/types";
export const useMDXComponents = (components: MDXComponents) => {
export function useMDXComponents(components: MDXComponents) {
return {
...components,
...mdxComponents,
};
};
}
+63 -53
View File
@@ -1,35 +1,42 @@
import { slugifyWithCounter } from "@sindresorhus/slugify";
import * as acorn from "acorn";
import { toString } from "mdast-util-to-string";
import { mdxAnnotations } from "mdx-annotations";
import { getHighlighter, renderToHtml } from "shiki";
import { visit } from "unist-util-visit";
import { slugifyWithCounter } from '@sindresorhus/slugify'
import * as acorn from 'acorn'
import { toString } from 'mdast-util-to-string'
import { mdxAnnotations } from 'mdx-annotations'
import { getHighlighter, renderToHtml } from 'shiki'
import { visit } from 'unist-util-visit'
const rehypeParseCodeBlocks = () => {
function rehypeParseCodeBlocks() {
return (tree) => {
visit(tree, "element", (node, _nodeIndex, parentNode) => {
if (node.tagName === "code" && node.properties.className) {
parentNode.properties.language = node.properties.className[0]?.replace(/^language-/, "");
visit(tree, 'element', (node, _nodeIndex, parentNode) => {
if (node.tagName === 'code' && node.properties.className) {
parentNode.properties.language = node.properties.className[0]?.replace(
/^language-/,
'',
)
}
});
};
};
})
}
}
let highlighter;
let highlighter
const rehypeShiki = () => {
function rehypeShiki() {
return async (tree) => {
highlighter = highlighter ?? (await getHighlighter({ theme: "css-variables" }));
highlighter =
highlighter ?? (await getHighlighter({ theme: 'css-variables' }))
visit(tree, "element", (node) => {
if (node.tagName === "pre" && node.children[0]?.tagName === "code") {
let codeNode = node.children[0];
let textNode = codeNode.children[0];
visit(tree, 'element', (node) => {
if (node.tagName === 'pre' && node.children[0]?.tagName === 'code') {
let codeNode = node.children[0]
let textNode = codeNode.children[0]
node.properties.code = textNode.value;
node.properties.code = textNode.value
if (node.properties.language) {
let tokens = highlighter.codeToThemedTokens(textNode.value, node.properties.language);
let tokens = highlighter.codeToThemedTokens(
textNode.value,
node.properties.language,
)
textNode.value = renderToHtml(tokens, {
elements: {
@@ -37,68 +44,71 @@ const rehypeShiki = () => {
code: ({ children }) => children,
line: ({ children }) => `<span>${children}</span>`,
},
});
})
}
}
});
};
};
})
}
}
const rehypeSlugify = () => {
function rehypeSlugify() {
return (tree) => {
let slugify = slugifyWithCounter();
visit(tree, "element", (node) => {
if (node.tagName === "h2" && !node.properties.id) {
node.properties.id = slugify(toString(node));
let slugify = slugifyWithCounter()
visit(tree, 'element', (node) => {
if (node.tagName === 'h2' && !node.properties.id) {
node.properties.id = slugify(toString(node))
}
});
};
};
})
}
}
const rehypeAddMDXExports = (getExports) => {
function rehypeAddMDXExports(getExports) {
return (tree) => {
let exports = Object.entries(getExports(tree));
let exports = Object.entries(getExports(tree))
for (let [name, value] of exports) {
for (let node of tree.children) {
if (node.type === "mdxjsEsm" && new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value)) {
return;
if (
node.type === 'mdxjsEsm' &&
new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value)
) {
return
}
}
let exportStr = `export const ${name} = ${value}`;
let exportStr = `export const ${name} = ${value}`
tree.children.push({
type: "mdxjsEsm",
type: 'mdxjsEsm',
value: exportStr,
data: {
estree: acorn.parse(exportStr, {
sourceType: "module",
ecmaVersion: "latest",
sourceType: 'module',
ecmaVersion: 'latest',
}),
},
});
})
}
};
};
}
}
const getSections = (node) => {
let sections = [];
function getSections(node) {
let sections = []
for (let child of node.children ?? []) {
if (child.type === "element" && child.tagName === "h2") {
if (child.type === 'element' && child.tagName === 'h2') {
sections.push(`{
title: ${JSON.stringify(toString(child))},
id: ${JSON.stringify(child.properties.id)},
...${child.properties.annotation}
}`);
}`)
} else if (child.children) {
sections.push(...getSections(child));
sections.push(...getSections(child))
}
}
return sections;
};
return sections
}
export const rehypePlugins = [
mdxAnnotations.rehype,
@@ -111,4 +121,4 @@ export const rehypePlugins = [
sections: `[${getSections(tree).join()}]`,
}),
],
];
]
+60 -59
View File
@@ -1,36 +1,51 @@
import { slugifyWithCounter } from "@sindresorhus/slugify";
import glob from "fast-glob";
import * as fs from "fs";
import { toString } from "mdast-util-to-string";
import * as path from "path";
import { remark } from "remark";
import remarkMdx from "remark-mdx";
import { createLoader } from "simple-functional-loader";
import { filter } from "unist-util-filter";
import { SKIP, visit } from "unist-util-visit";
import * as url from "url";
import { slugifyWithCounter } from '@sindresorhus/slugify'
import glob from 'fast-glob'
import * as fs from 'fs'
import { toString } from 'mdast-util-to-string'
import * as path from 'path'
import { remark } from 'remark'
import remarkMdx from 'remark-mdx'
import { createLoader } from 'simple-functional-loader'
import { filter } from 'unist-util-filter'
import { SKIP, visit } from 'unist-util-visit'
import * as url from 'url'
const extractSections = () => {
const __filename = url.fileURLToPath(import.meta.url)
const processor = remark().use(remarkMdx).use(extractSections)
const slugify = slugifyWithCounter()
function isObjectExpression(node) {
return (
node.type === 'mdxTextExpression' &&
node.data?.estree?.body?.[0]?.expression?.type === 'ObjectExpression'
)
}
function excludeObjectExpressions(tree) {
return filter(tree, (node) => !isObjectExpression(node))
}
function extractSections() {
return (tree, { sections }) => {
slugify.reset();
slugify.reset()
visit(tree, (node) => {
if (node.type === "heading" || node.type === "paragraph") {
let content = toString(excludeObjectExpressions(node));
if (node.type === "heading" && node.depth <= 2) {
let hash = node.depth === 1 ? null : slugify(content);
sections.push([content, hash, []]);
if (node.type === 'heading' || node.type === 'paragraph') {
let content = toString(excludeObjectExpressions(node))
if (node.type === 'heading' && node.depth <= 2) {
let hash = node.depth === 1 ? null : slugify(content)
sections.push([content, hash, []])
} else {
sections.at(-1)?.[2].push(content);
sections.at(-1)?.[2].push(content)
}
return SKIP;
return SKIP
}
});
};
};
})
}
}
export const Search = (nextConfig = {}) => {
let cache = new Map();
export default function Search(nextConfig = {}) {
let cache = new Map()
return Object.assign({}, nextConfig, {
webpack(config, options) {
@@ -38,26 +53,26 @@ export const Search = (nextConfig = {}) => {
test: __filename,
use: [
createLoader(function () {
let appDir = path.resolve("./src/app");
this.addContextDependency(appDir);
let appDir = path.resolve('./src/app')
this.addContextDependency(appDir)
let files = glob.sync("**/*.mdx", { cwd: appDir });
let files = glob.sync('**/*.mdx', { cwd: appDir })
let data = files.map((file) => {
let url = "/" + file.replace(/(^|\/)page\.mdx$/, "");
let mdx = fs.readFileSync(path.join(appDir, file), "utf8");
let url = '/' + file.replace(/(^|\/)page\.mdx$/, '')
let mdx = fs.readFileSync(path.join(appDir, file), 'utf8')
let sections = [];
let sections = []
if (cache.get(file)?.[0] === mdx) {
sections = cache.get(file)[1];
sections = cache.get(file)[1]
} else {
let vfile = { value: mdx, sections };
processor.runSync(processor.parse(vfile), vfile);
cache.set(file, [mdx, sections]);
let vfile = { value: mdx, sections }
processor.runSync(processor.parse(vfile), vfile)
cache.set(file, [mdx, sections])
}
return { url, sections };
});
return { url, sections }
})
// When this file is imported within the application
// the following module is loaded:
@@ -91,7 +106,7 @@ export const Search = (nextConfig = {}) => {
}
}
export const search = (query, options = {}) => {
export function search(query, options = {}) {
let result = sectionIndex.search(query, {
...options,
enrich: true,
@@ -105,30 +120,16 @@ export const Search = (nextConfig = {}) => {
pageTitle: item.doc.pageTitle,
}))
}
`;
`
}),
],
});
})
if (typeof nextConfig.webpack === "function") {
return nextConfig.webpack(config, options);
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options)
}
return config;
return config
},
});
};
const __filename = url.fileURLToPath(import.meta.url);
const processor = remark().use(remarkMdx).use(extractSections);
const slugify = slugifyWithCounter();
const isObjectExpression = (node) => {
return (
node.type === "mdxTextExpression" && node.data?.estree?.body?.[0]?.expression?.type === "ObjectExpression"
);
};
const excludeObjectExpressions = (tree) => {
return filter(tree, (node) => !isObjectExpression(node));
};
})
}
+1 -1
View File
@@ -3,7 +3,7 @@ import nextMDX from "@next/mdx";
import { recmaPlugins } from "./mdx/recma.mjs";
import { rehypePlugins } from "./mdx/rehype.mjs";
import { remarkPlugins } from "./mdx/remark.mjs";
import { Search as withSearch } from "./mdx/search.mjs";
import withSearch from "./mdx/search.mjs";
const withMDX = nextMDX({
options: {
+2 -4
View File
@@ -1,12 +1,10 @@
import { MetadataRoute } from "next";
const robots = (): MetadataRoute.Robots => {
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: "*",
allow: "/",
},
};
};
export default robots;
}
+1 -1
View File
@@ -7,5 +7,5 @@ declare module "@/mdx/search.mjs" {
pageTitle?: string;
};
export const search: (query: string, options?: SearchOptions) => Array<Result>;
export function search(query: string, options?: SearchOptions): Array<Result>;
}
+2 -4
View File
@@ -1,6 +1,6 @@
import { type PluginUtils } from "tailwindcss/types/config";
const typographyStyles = ({ theme }: PluginUtils) => {
export default function typographyStyles({ theme }: PluginUtils) {
return {
DEFAULT: {
css: {
@@ -367,6 +367,4 @@ const typographyStyles = ({ theme }: PluginUtils) => {
},
},
};
};
export default typographyStyles;
}
+4 -4
View File
@@ -5,11 +5,10 @@ import { dirname, join } from "path";
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
const getAbsolutePath = (value: string): any => {
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, "package.json")));
};
export const config: StorybookConfig = {
}
const config: StorybookConfig = {
stories: ["../../../packages/ui/**/stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
getAbsolutePath("@storybook/addon-links"),
@@ -25,3 +24,4 @@ export const config: StorybookConfig = {
autodocs: "tag",
},
};
export default config;
+3 -1
View File
@@ -2,7 +2,7 @@ import type { Preview } from "@storybook/react";
import "../src/index.css";
export const preview: Preview = {
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
@@ -13,3 +13,5 @@ export const preview: Preview = {
},
},
};
export default preview;
+4 -2
View File
@@ -2,7 +2,7 @@ import { useState } from "react";
import "./App.css";
export const App = () => {
function App() {
const [count, setCount] = useState(0);
return (
@@ -23,4 +23,6 @@ export const App = () => {
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</>
);
};
}
export default App;
+4 -3
View File
@@ -18,11 +18,12 @@ RUN turbo prune @formbricks/web --docker
FROM base AS installer
# Enable corepack and prepare pnpm
RUN corepack enable
RUN corepack enable && corepack prepare pnpm@latest --activate
# Install necessary build tools and compilers
RUN apk update && apk add --no-cache g++ cmake make gcc python3 openssl-dev jq
# Set hardcoded environment variables
ENV DATABASE_URL="postgresql://placeholder:for@build:5432/gets_overwritten_at_runtime?schema=public"
ENV NEXTAUTH_SECRET="placeholder_for_next_auth_of_64_chars_get_overwritten_at_runtime"
@@ -58,7 +59,7 @@ RUN jq -r '.devDependencies.prisma' packages/database/package.json > /prisma_ver
## step 3: setup production runner
#
FROM base AS runner
RUN corepack enable
RUN corepack enable && corepack prepare pnpm@latest --activate
RUN apk add --no-cache curl \
&& apk add --no-cache supercronic \
@@ -91,5 +92,5 @@ RUN mkdir -p /home/nextjs/apps/web/uploads/
VOLUME /home/nextjs/apps/web/uploads/
CMD supercronic -quiet /app/docker/cronjobs & \
(cd packages/database && npm run db:migrate:deploy) && \
(cd packages/database && pnpm db:migrate:deploy) && \
exec node apps/web/server.js
@@ -1,5 +1,5 @@
import { FormbricksClient } from "@/app/(app)/components/FormbricksClient";
import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
import FormbricksClient from "@/app/(app)/components/FormbricksClient";
import PosthogIdentify from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/components/ResponseFilterContext";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
@@ -10,9 +10,9 @@ import { getEnvironment } from "@formbricks/lib/environment/service";
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
import { AuthorizationError } from "@formbricks/types/errors";
import { DevEnvironmentBanner } from "@formbricks/ui/DevEnvironmentBanner";
import { ToasterClient } from "@formbricks/ui/ToasterClient";
import ToasterClient from "@formbricks/ui/ToasterClient";
const EnvLayout = async ({ children, params }) => {
export default async function EnvLayout({ children, params }) {
const session = await getServerSession(authOptions);
if (!session || !session.user) {
return redirect(`/auth/login`);
@@ -54,6 +54,4 @@ const EnvLayout = async ({ children, params }) => {
</ResponseFilterProvider>
</>
);
};
export default EnvLayout;
}
@@ -30,11 +30,11 @@ import { TProduct } from "@formbricks/types/product";
import { TBaseFilters, TSegmentUpdateInput, ZSegmentFilters } from "@formbricks/types/segment";
import { TSurvey } from "@formbricks/types/surveys";
export const surveyMutateAction = async (survey: TSurvey): Promise<TSurvey> => {
export async function surveyMutateAction(survey: TSurvey): Promise<TSurvey> {
return await updateSurvey(survey);
};
}
export const updateSurveyAction = async (survey: TSurvey): Promise<TSurvey> => {
export async function updateSurveyAction(survey: TSurvey): Promise<TSurvey> {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
@@ -45,7 +45,7 @@ export const updateSurveyAction = async (survey: TSurvey): Promise<TSurvey> => {
if (!hasCreateOrUpdateAccess) throw new AuthorizationError("Not authorized");
return await updateSurvey(survey);
};
}
export const deleteSurveyAction = async (surveyId: string) => {
const session = await getServerSession(authOptions);
@@ -190,7 +190,7 @@ export const resetBasicSegmentFiltersAction = async (surveyId: string) => {
return await resetSegmentInSurvey(surveyId);
};
export const getImagesFromUnsplashAction = async (searchQuery: string, page: number = 1) => {
export async function getImagesFromUnsplashAction(searchQuery: string, page: number = 1) {
if (!UNSPLASH_ACCESS_KEY) {
throw new Error("Unsplash access key is not set");
}
@@ -231,9 +231,9 @@ export const getImagesFromUnsplashAction = async (searchQuery: string, page: num
} catch (error) {
throw new Error("Error getting images from Unsplash");
}
};
}
export const triggerDownloadUnsplashImageAction = async (downloadUrl: string) => {
export async function triggerDownloadUnsplashImageAction(downloadUrl: string) {
try {
const response = await fetch(`${downloadUrl}/?client_id=${UNSPLASH_ACCESS_KEY}`, {
method: "GET",
@@ -249,9 +249,9 @@ export const triggerDownloadUnsplashImageAction = async (downloadUrl: string) =>
} catch (error) {
throw new Error("Error downloading image from Unsplash");
}
};
}
export const createActionClassAction = async (action: TActionClassInput) => {
export async function createActionClassAction(action: TActionClassInput) {
const session = await getServerSession(authOptions);
if (!session) throw new AuthorizationError("Not authorized");
@@ -259,4 +259,4 @@ export const createActionClassAction = async (action: TActionClassInput) => {
if (!isAuthorized) throw new AuthorizationError("Not authorized");
return await createActionClass(action.environmentId, action);
};
}
@@ -14,7 +14,7 @@ interface AddQuestionButtonProps {
product: TProduct;
}
export const AddQuestionButton = ({ addQuestion, product }: AddQuestionButtonProps) => {
export default function AddQuestionButton({ addQuestion, product }: AddQuestionButtonProps) {
const [open, setOpen] = useState(false);
return (
@@ -59,4 +59,4 @@ export const AddQuestionButton = ({ addQuestion, product }: AddQuestionButtonPro
</Collapsible.CollapsibleContent>
</Collapsible.Root>
);
};
}
@@ -1,7 +1,7 @@
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
import { LogicEditor } from "./LogicEditor";
import { UpdateQuestionId } from "./UpdateQuestionId";
import LogicEditor from "./LogicEditor";
import UpdateQuestionId from "./UpdateQuestionId";
interface AdvancedSettingsProps {
question: TSurveyQuestion;
@@ -9,7 +9,7 @@ import { TSurveyBackgroundBgType, TSurveyStyling } from "@formbricks/types/surve
import { Badge } from "@formbricks/ui/Badge";
import { Slider } from "@formbricks/ui/Slider";
import { SurveyBgSelectorTab } from "./SurveyBgSelectorTab";
import SurveyBgSelectorTab from "./SurveyBgSelectorTab";
interface BackgroundStylingCardProps {
open: boolean;
@@ -75,7 +75,7 @@ export const BackgroundStylingCard = ({
asChild
disabled={disabled}
className={cn(
"w-full cursor-pointer rounded-lg hover:bg-slate-50",
"h-full w-full cursor-pointer rounded-lg hover:bg-slate-50",
disabled && "cursor-not-allowed opacity-60 hover:bg-white"
)}>
<div className="inline-flex px-4 py-4">

Some files were not shown because too many files have changed in this diff Show More