diff --git a/apps/web/package.json b/apps/web/package.json index 541cf9c0b..be773984d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -28,6 +28,7 @@ "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.3", "@tanstack/react-query": "^4.29.5", + "colord": "^2.9.3", "cookie-es": "^1.0.0", "formidable": "^2.1.1", "framer-motion": "^10.12.8", @@ -53,7 +54,7 @@ "react-icons": "^4.11.0", "react-rnd": "^10.4.1", "recharts": "^2.7.2", - "remeda": "^2.12.1", + "remeda": "^2.17.3", "sharp": "0.32.6", "ts-pattern": "^5.0.5", "ua-parser-js": "^1.0.37", @@ -74,4 +75,4 @@ "@types/ua-parser-js": "^0.7.39", "typescript": "5.2.2" } -} +} \ No newline at end of file diff --git a/apps/web/src/app/embed/base-schema.ts b/apps/web/src/app/embed/base-schema.ts index 542e70724..003553301 100644 --- a/apps/web/src/app/embed/base-schema.ts +++ b/apps/web/src/app/embed/base-schema.ts @@ -1,8 +1,12 @@ import { z } from 'zod'; +import { ZCssVarsSchema } from './css-vars'; + export const ZBaseEmbedDataSchema = z.object({ + darkModeDisabled: z.boolean().optional().default(false), css: z .string() .optional() .transform((value) => value || undefined), + cssVars: ZCssVarsSchema.optional().default({}), }); diff --git a/apps/web/src/app/embed/css-vars.ts b/apps/web/src/app/embed/css-vars.ts new file mode 100644 index 000000000..711a3e51f --- /dev/null +++ b/apps/web/src/app/embed/css-vars.ts @@ -0,0 +1,59 @@ +import { colord } from 'colord'; +import { toSnakeCase } from 'remeda'; +import { z } from 'zod'; + +export const ZCssVarsSchema = z + .object({ + background: z.string().optional().describe('Base background color'), + foreground: z.string().optional().describe('Base text color'), + muted: z.string().optional().describe('Muted/subtle background color'), + mutedForeground: z.string().optional().describe('Muted/subtle text color'), + popover: z.string().optional().describe('Popover/dropdown background color'), + popoverForeground: z.string().optional().describe('Popover/dropdown text color'), + card: z.string().optional().describe('Card background color'), + cardBorder: z.string().optional().describe('Card border color'), + cardBorderTint: z.string().optional().describe('Card border tint/highlight color'), + cardForeground: z.string().optional().describe('Card text color'), + fieldCard: z.string().optional().describe('Field card background color'), + fieldCardBorder: z.string().optional().describe('Field card border color'), + fieldCardForeground: z.string().optional().describe('Field card text color'), + widget: z.string().optional().describe('Widget background color'), + widgetForeground: z.string().optional().describe('Widget text color'), + border: z.string().optional().describe('Default border color'), + input: z.string().optional().describe('Input field border color'), + primary: z.string().optional().describe('Primary action/button color'), + primaryForeground: z.string().optional().describe('Primary action/button text color'), + secondary: z.string().optional().describe('Secondary action/button color'), + secondaryForeground: z.string().optional().describe('Secondary action/button text color'), + accent: z.string().optional().describe('Accent/highlight color'), + accentForeground: z.string().optional().describe('Accent/highlight text color'), + destructive: z.string().optional().describe('Destructive/danger action color'), + destructiveForeground: z.string().optional().describe('Destructive/danger text color'), + ring: z.string().optional().describe('Focus ring color'), + radius: z.string().optional().describe('Border radius size in REM units'), + warning: z.string().optional().describe('Warning/alert color'), + }) + .describe('Custom CSS variables for theming'); + +export type TCssVarsSchema = z.infer; + +export const toNativeCssVars = (vars: TCssVarsSchema) => { + const cssVars: Record = {}; + + const { radius, ...colorVars } = vars; + + for (const [key, value] of Object.entries(colorVars)) { + if (value) { + const color = colord(value); + const { h, s, l } = color.toHsl(); + + cssVars[`--${toSnakeCase(key)}`] = `${h} ${s} ${l}`; + } + } + + if (radius) { + cssVars[`--radius`] = `${radius}`; + } + + return cssVars; +}; diff --git a/apps/web/src/app/embed/direct/[[...url]]/client.tsx b/apps/web/src/app/embed/direct/[[...url]]/client.tsx index 0f71c0e89..40f8ee7ca 100644 --- a/apps/web/src/app/embed/direct/[[...url]]/client.tsx +++ b/apps/web/src/app/embed/direct/[[...url]]/client.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useLayoutEffect, useState } from 'react'; import { useSearchParams } from 'next/navigation'; @@ -38,6 +38,7 @@ import { Logo } from '~/components/branding/logo'; import { EmbedClientLoading } from '../../client-loading'; import { EmbedDocumentCompleted } from '../../completed'; import { EmbedDocumentFields } from '../../document-fields'; +import { injectCss } from '../../util'; import { ZDirectTemplateEmbedDataSchema } from './schema'; export type EmbedDirectTemplateClientPageProps = { @@ -47,6 +48,8 @@ export type EmbedDirectTemplateClientPageProps = { recipient: Recipient; fields: Field[]; metadata?: DocumentMeta | TemplateMeta | null; + hidePoweredBy?: boolean; + isPlatformOrEnterprise?: boolean; }; export const EmbedDirectTemplateClientPage = ({ @@ -56,6 +59,8 @@ export const EmbedDirectTemplateClientPage = ({ recipient, fields, metadata, + hidePoweredBy = false, + isPlatformOrEnterprise = false, }: EmbedDirectTemplateClientPageProps) => { const { _ } = useLingui(); const { toast } = useToast(); @@ -249,7 +254,7 @@ export const EmbedDirectTemplateClientPage = ({ } }; - useEffect(() => { + useLayoutEffect(() => { const hash = window.location.hash.slice(1); try { @@ -264,6 +269,17 @@ export const EmbedDirectTemplateClientPage = ({ setFullName(data.name); setIsNameLocked(!!data.lockName); } + + if (data.darkModeDisabled) { + document.documentElement.classList.add('dark-mode-disabled'); + } + + if (isPlatformOrEnterprise) { + injectCss({ + css: data.css, + cssVars: data.cssVars, + }); + } } catch (err) { console.error(err); } @@ -452,10 +468,12 @@ export const EmbedDirectTemplateClientPage = ({ /> -
- Powered by - -
+ {!hidePoweredBy && ( +
+ Powered by + +
+ )} ); }; diff --git a/apps/web/src/app/embed/direct/[[...url]]/page.tsx b/apps/web/src/app/embed/direct/[[...url]]/page.tsx index ead83463e..bd4cf610d 100644 --- a/apps/web/src/app/embed/direct/[[...url]]/page.tsx +++ b/apps/web/src/app/embed/direct/[[...url]]/page.tsx @@ -2,8 +2,11 @@ import { notFound } from 'next/navigation'; import { match } from 'ts-pattern'; +import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; +import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; +import { getTeamById } from '@documenso/lib/server-only/team/get-team'; import { getTemplateByDirectLinkToken } from '@documenso/lib/server-only/template/get-template-by-direct-link-token'; import { DocumentAccessAuth } from '@documenso/lib/types/document-auth'; import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth'; @@ -51,6 +54,14 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem documentAuth: template.authOptions, }); + const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([ + isDocumentPlatform(template), + isUserEnterprise({ + userId: template.userId, + teamId: template.teamId ?? undefined, + }), + ]); + const isAccessAuthValid = match(derivedRecipientAccessAuth) .with(DocumentAccessAuth.ACCOUNT, () => user !== null) .with(null, () => true) @@ -72,6 +83,12 @@ export default async function EmbedDirectTemplatePage({ params }: EmbedDirectTem const fields = template.Field.filter((field) => field.recipientId === directTemplateRecipientId); + const team = template.teamId + ? await getTeamById({ teamId: template.teamId, userId: template.userId }).catch(() => null) + : null; + + const hidePoweredBy = team?.teamGlobalSettings?.brandingHidePoweredBy ?? false; + return ( diff --git a/apps/web/src/app/embed/sign/[[...url]]/client.tsx b/apps/web/src/app/embed/sign/[[...url]]/client.tsx index e10f4745c..81dcb129d 100644 --- a/apps/web/src/app/embed/sign/[[...url]]/client.tsx +++ b/apps/web/src/app/embed/sign/[[...url]]/client.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useLayoutEffect, useState } from 'react'; import { Trans, msg } from '@lingui/macro'; import { useLingui } from '@lingui/react'; @@ -28,6 +28,7 @@ import { Logo } from '~/components/branding/logo'; import { EmbedClientLoading } from '../../client-loading'; import { EmbedDocumentCompleted } from '../../completed'; import { EmbedDocumentFields } from '../../document-fields'; +import { injectCss } from '../../util'; import { ZSignDocumentEmbedDataSchema } from './schema'; export type EmbedSignDocumentClientPageProps = { @@ -38,6 +39,8 @@ export type EmbedSignDocumentClientPageProps = { fields: Field[]; metadata?: DocumentMeta | TemplateMeta | null; isCompleted?: boolean; + hidePoweredBy?: boolean; + isPlatformOrEnterprise?: boolean; }; export const EmbedSignDocumentClientPage = ({ @@ -48,6 +51,8 @@ export const EmbedSignDocumentClientPage = ({ fields, metadata, isCompleted, + hidePoweredBy = false, + isPlatformOrEnterprise = false, }: EmbedSignDocumentClientPageProps) => { const { _ } = useLingui(); const { toast } = useToast(); @@ -131,7 +136,7 @@ export const EmbedSignDocumentClientPage = ({ } }; - useEffect(() => { + useLayoutEffect(() => { const hash = window.location.hash.slice(1); try { @@ -144,6 +149,17 @@ export const EmbedSignDocumentClientPage = ({ // Since a recipient can be provided a name we can lock it without requiring // a to be provided by the parent application, unlike direct templates. setIsNameLocked(!!data.lockName); + + if (data.darkModeDisabled) { + document.documentElement.classList.add('dark-mode-disabled'); + } + + if (isPlatformOrEnterprise) { + injectCss({ + css: data.css, + cssVars: data.cssVars, + }); + } } catch (err) { console.error(err); } @@ -325,10 +341,12 @@ export const EmbedSignDocumentClientPage = ({ -
- Powered by - -
+ {!hidePoweredBy && ( +
+ Powered by + +
+ )} ); }; diff --git a/apps/web/src/app/embed/sign/[[...url]]/page.tsx b/apps/web/src/app/embed/sign/[[...url]]/page.tsx index d7acbabe4..0f3351a83 100644 --- a/apps/web/src/app/embed/sign/[[...url]]/page.tsx +++ b/apps/web/src/app/embed/sign/[[...url]]/page.tsx @@ -2,11 +2,14 @@ import { notFound } from 'next/navigation'; import { match } from 'ts-pattern'; +import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise'; +import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { getServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token'; import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; +import { getTeamById } from '@documenso/lib/server-only/team/get-team'; import { DocumentAccessAuth } from '@documenso/lib/types/document-auth'; import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth'; import { DocumentStatus } from '@documenso/prisma/client'; @@ -56,6 +59,14 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen return ; } + const [isPlatformDocument, isEnterpriseDocument] = await Promise.all([ + isDocumentPlatform(document), + isUserEnterprise({ + userId: document.userId, + teamId: document.teamId ?? undefined, + }), + ]); + const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({ documentAuth: document.authOptions, }); @@ -74,6 +85,12 @@ export default async function EmbedSignDocumentPage({ params }: EmbedSignDocumen ); } + const team = document.teamId + ? await getTeamById({ teamId: document.teamId, userId: document.userId }).catch(() => null) + : null; + + const hidePoweredBy = team?.teamGlobalSettings?.brandingHidePoweredBy ?? false; + return ( diff --git a/apps/web/src/app/embed/util.ts b/apps/web/src/app/embed/util.ts new file mode 100644 index 000000000..099ecb9f8 --- /dev/null +++ b/apps/web/src/app/embed/util.ts @@ -0,0 +1,20 @@ +import { type TCssVarsSchema, toNativeCssVars } from './css-vars'; + +export const injectCss = (options: { css?: string; cssVars?: TCssVarsSchema }) => { + const { css, cssVars } = options; + + if (css) { + const style = document.createElement('style'); + style.innerHTML = css; + + document.head.appendChild(style); + } + + if (cssVars) { + const nativeVars = toNativeCssVars(cssVars); + + for (const [key, value] of Object.entries(nativeVars)) { + document.documentElement.style.setProperty(key, value); + } + } +}; diff --git a/package-lock.json b/package-lock.json index fd2842078..35d60f1c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -457,6 +457,7 @@ "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.3", "@tanstack/react-query": "^4.29.5", + "colord": "^2.9.3", "cookie-es": "^1.0.0", "formidable": "^2.1.1", "framer-motion": "^10.12.8", @@ -482,7 +483,7 @@ "react-icons": "^4.11.0", "react-rnd": "^10.4.1", "recharts": "^2.7.2", - "remeda": "^2.12.1", + "remeda": "^2.17.3", "sharp": "0.32.6", "ts-pattern": "^5.0.5", "ua-parser-js": "^1.0.37", @@ -14218,6 +14219,12 @@ "color-support": "bin.js" } }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -30153,18 +30160,18 @@ } }, "node_modules/remeda": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.12.1.tgz", - "integrity": "sha512-hKFAbxbQe8PMd4+CYO1DYCrCbcZsUSa7e21g7+4co91GBy7BD+Ub6JdaLy76yPOp7PCPTAXRz/9NXtZ9w15jbg==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.17.3.tgz", + "integrity": "sha512-xyi2rCQkz2j4BEWbWxPw6JCapv1yBuSwr4Uf9BX00AkesAJaiKvc6Il6thsBidwVZAtNiSaCIXvslkKL0ybz8w==", "license": "MIT", "dependencies": { - "type-fest": "^4.26.1" + "type-fest": "^4.27.0" } }, "node_modules/remeda/node_modules/type-fest": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", - "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.27.0.tgz", + "integrity": "sha512-3IMSWgP7C5KSQqmo1wjhKrwsvXAtF33jO3QY+Uy++ia7hqvgSK6iXbbg5PbDBc1P2ZbNEDgejOrN4YooXvhwCw==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -36805,7 +36812,7 @@ "pg": "^8.11.3", "playwright": "1.43.0", "react": "^18", - "remeda": "^2.12.1", + "remeda": "^2.17.3", "sharp": "0.32.6", "stripe": "^12.7.0", "ts-pattern": "^5.0.5", @@ -37051,7 +37058,7 @@ "react-hook-form": "^7.45.4", "react-pdf": "7.7.3", "react-rnd": "^10.4.1", - "remeda": "^1.27.1", + "remeda": "^2.17.3", "tailwind-merge": "^1.12.0", "tailwindcss-animate": "^1.0.5", "ts-pattern": "^5.0.5", @@ -37104,12 +37111,6 @@ "node": ">=6" } }, - "packages/ui/node_modules/remeda": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.61.0.tgz", - "integrity": "sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A==", - "license": "MIT" - }, "packages/ui/node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", diff --git a/packages/ee/server-only/stripe/get-document-related-prices.ts.ts b/packages/ee/server-only/stripe/get-document-related-prices.ts.ts index 3d32193e7..682086734 100644 --- a/packages/ee/server-only/stripe/get-document-related-prices.ts.ts +++ b/packages/ee/server-only/stripe/get-document-related-prices.ts.ts @@ -9,6 +9,7 @@ export const getDocumentRelatedPrices = async () => { return await getPricesByPlan([ STRIPE_PLAN_TYPE.REGULAR, STRIPE_PLAN_TYPE.COMMUNITY, + STRIPE_PLAN_TYPE.PLATFORM, STRIPE_PLAN_TYPE.ENTERPRISE, ]); }; diff --git a/packages/ee/server-only/stripe/get-platform-plan-prices.ts b/packages/ee/server-only/stripe/get-platform-plan-prices.ts new file mode 100644 index 000000000..7a55caa07 --- /dev/null +++ b/packages/ee/server-only/stripe/get-platform-plan-prices.ts @@ -0,0 +1,13 @@ +import { STRIPE_PLAN_TYPE } from '@documenso/lib/constants/billing'; + +import { getPricesByPlan } from './get-prices-by-plan'; + +export const getPlatformPlanPrices = async () => { + return await getPricesByPlan(STRIPE_PLAN_TYPE.PLATFORM); +}; + +export const getPlatformPlanPriceIds = async () => { + const prices = await getPlatformPlanPrices(); + + return prices.map((price) => price.id); +}; diff --git a/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts b/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts index 6d00386f7..166f5c8ef 100644 --- a/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts +++ b/packages/ee/server-only/stripe/get-primary-account-plan-prices.ts @@ -9,6 +9,7 @@ export const getPrimaryAccountPlanPrices = async () => { return await getPricesByPlan([ STRIPE_PLAN_TYPE.REGULAR, STRIPE_PLAN_TYPE.COMMUNITY, + STRIPE_PLAN_TYPE.PLATFORM, STRIPE_PLAN_TYPE.ENTERPRISE, ]); }; diff --git a/packages/ee/server-only/stripe/get-team-related-prices.ts b/packages/ee/server-only/stripe/get-team-related-prices.ts index b10ab06f4..debbac6ea 100644 --- a/packages/ee/server-only/stripe/get-team-related-prices.ts +++ b/packages/ee/server-only/stripe/get-team-related-prices.ts @@ -6,7 +6,11 @@ import { getPricesByPlan } from './get-prices-by-plan'; * Returns the Stripe prices of items that affect the amount of teams a user can create. */ export const getTeamRelatedPrices = async () => { - return await getPricesByPlan([STRIPE_PLAN_TYPE.COMMUNITY, STRIPE_PLAN_TYPE.ENTERPRISE]); + return await getPricesByPlan([ + STRIPE_PLAN_TYPE.COMMUNITY, + STRIPE_PLAN_TYPE.PLATFORM, + STRIPE_PLAN_TYPE.ENTERPRISE, + ]); }; /** diff --git a/packages/ee/server-only/util/is-document-platform.ts b/packages/ee/server-only/util/is-document-platform.ts new file mode 100644 index 000000000..3cea7a081 --- /dev/null +++ b/packages/ee/server-only/util/is-document-platform.ts @@ -0,0 +1,61 @@ +import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; +import { subscriptionsContainsActivePlan } from '@documenso/lib/utils/billing'; +import { prisma } from '@documenso/prisma'; +import type { Document, Subscription } from '@documenso/prisma/client'; + +import { getPlatformPlanPriceIds } from '../stripe/get-platform-plan-prices'; + +export type IsDocumentPlatformOptions = Pick; + +/** + * Whether the user is platform, or has permission to use platform features on + * behalf of their team. + * + * It is assumed that the provided user is part of the provided team. + */ +export const isDocumentPlatform = async ({ + userId, + teamId, +}: IsDocumentPlatformOptions): Promise => { + let subscriptions: Subscription[] = []; + + if (!IS_BILLING_ENABLED()) { + return true; + } + + if (teamId) { + subscriptions = await prisma.team + .findFirstOrThrow({ + where: { + id: teamId, + }, + select: { + owner: { + include: { + Subscription: true, + }, + }, + }, + }) + .then((team) => team.owner.Subscription); + } else { + subscriptions = await prisma.user + .findFirstOrThrow({ + where: { + id: userId, + }, + select: { + Subscription: true, + }, + }) + .then((user) => user.Subscription); + } + + if (subscriptions.length === 0) { + return false; + } + + const platformPlanPriceIds = await getPlatformPlanPriceIds(); + + return subscriptionsContainsActivePlan(subscriptions, platformPlanPriceIds); +}; diff --git a/packages/lib/constants/billing.ts b/packages/lib/constants/billing.ts index 17178662d..f552479a1 100644 --- a/packages/lib/constants/billing.ts +++ b/packages/lib/constants/billing.ts @@ -7,5 +7,6 @@ export enum STRIPE_PLAN_TYPE { REGULAR = 'regular', TEAM = 'team', COMMUNITY = 'community', + PLATFORM = 'platform', ENTERPRISE = 'enterprise', } diff --git a/packages/lib/package.json b/packages/lib/package.json index 0dc897f98..e4142199b 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -51,7 +51,7 @@ "pg": "^8.11.3", "playwright": "1.43.0", "react": "^18", - "remeda": "^2.12.1", + "remeda": "^2.17.3", "sharp": "0.32.6", "stripe": "^12.7.0", "ts-pattern": "^5.0.5", diff --git a/packages/tailwind-config/index.cjs b/packages/tailwind-config/index.cjs index 067034a8b..6d5840163 100644 --- a/packages/tailwind-config/index.cjs +++ b/packages/tailwind-config/index.cjs @@ -108,6 +108,9 @@ module.exports = { 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', }, borderRadius: { + DEFAULT: 'calc(var(--radius) - 3px)', + '2xl': 'calc(var(--radius) + 4px)', + xl: 'calc(var(--radius) + 2px)', lg: 'var(--radius)', md: 'calc(var(--radius) - 2px)', sm: 'calc(var(--radius) - 4px)', diff --git a/packages/ui/components/field/field.tsx b/packages/ui/components/field/field.tsx index ac67171de..eb6468c03 100644 --- a/packages/ui/components/field/field.tsx +++ b/packages/ui/components/field/field.tsx @@ -34,7 +34,7 @@ const getCardClassNames = ( const baseClasses = 'field-card-container relative z-20 h-full w-full transition-all'; const insertedClasses = - 'bg-documenso/20 border-documenso ring-documenso-200 ring-offset-documenso-200 ring-2 ring-offset-2 dark:shadow-none'; + 'bg-primary/20 border-primary ring-primary/20 ring-offset-primary/20 ring-2 ring-offset-2 dark:shadow-none'; const nonRequiredClasses = 'border-yellow-300 shadow-none ring-2 ring-yellow-100 ring-offset-2 ring-offset-yellow-100 dark:border-2'; const validatingClasses = 'border-orange-300 ring-1 ring-orange-300'; diff --git a/packages/ui/package.json b/packages/ui/package.json index 2a7fce3fa..aca306853 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -74,7 +74,7 @@ "react-hook-form": "^7.45.4", "react-pdf": "7.7.3", "react-rnd": "^10.4.1", - "remeda": "^1.27.1", + "remeda": "^2.17.3", "tailwind-merge": "^1.12.0", "tailwindcss-animate": "^1.0.5", "ts-pattern": "^5.0.5", diff --git a/packages/ui/primitives/card.tsx b/packages/ui/primitives/card.tsx index 90619d2bf..155597562 100644 --- a/packages/ui/primitives/card.tsx +++ b/packages/ui/primitives/card.tsx @@ -36,11 +36,11 @@ const Card = React.forwardRef( className={cn( 'bg-background text-foreground group relative rounded-lg border-2 backdrop-blur-[2px]', { - 'gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.documenso.DEFAULT/50%)_5%,theme(colors.border/80%)_30%)]': + 'gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/50%)_5%,theme(colors.border/80%)_30%)]': gradient, - 'dark:gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.documenso.DEFAULT/70%)_5%,theme(colors.border/80%)_30%)]': + 'dark:gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/70%)_5%,theme(colors.border/80%)_30%)]': gradient, - 'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_theme(colors.primary.DEFAULT/70%)]': + 'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_var(colors.primary.DEFAULT/70%)]': true, 'dark:shadow-[0]': true, }, diff --git a/packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx b/packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx index 89b2770f3..04529098a 100644 --- a/packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx +++ b/packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx @@ -141,7 +141,7 @@ export const RadioFieldAdvancedSettings = ({ {values.map((value) => (
handleCheckedChange(Boolean(checked), value.id)} /> diff --git a/packages/ui/styles/theme.css b/packages/ui/styles/theme.css index b5142b60c..a7d802e71 100644 --- a/packages/ui/styles/theme.css +++ b/packages/ui/styles/theme.css @@ -138,7 +138,7 @@ --new-surface-white: 0, 0%, 91%; } - .dark { + .dark:not(.dark-mode-disabled) { --background: 0 0% 14.9%; --foreground: 0 0% 97%;